fix(desktop): shared-compute usability — share toggle, usage indicator, model resync#2448
Conversation
Consuming a peer's compute (selecting Buzz compute as an agent's provider) spins up a client-mode node in the single mesh runtime slot, which reports state:"running". The Share toggle keyed off state alone, so it lit up during a consume session — and toggling it off ran the generic mesh_stop_node, tearing down that session. Toggle now reads mode: serve = sharing (on), client = consuming (off). A serve node that also routes a peer's model stays on. mesh_stop_node refuses to tear down a client node. A failed serve node still occupies the single slot, so it reads as sharing (switch stays on and turn-off-able to clear/retry) and blocks a fresh start; slotOccupied gates the model inputs. Consume copy states the fact rather than promising a stop action that doesn't exist. - deriveMeshShareToggle predicate (+ slotOccupied) + unit table - backend share_stop_should_teardown guard + serde contract test - e2e regression: consuming does not light the Share toggle
Adds a read-only, host-side usage indicator to the Share compute card so you can see when someone (or your own agent) is using the compute this machine is sharing, and where it's coming from. - new read-only mesh_serving_usage Tauri command projects the serving node's own routing_metrics (request/token counts, inflight, and the local vs remote/endpoint attempt split). No new trust surface; stubbed for non-mesh-llm builds. - serving_usage_from_payload: pure, defensive extractor (missing fields -> 0, so SDK shape drift degrades to 'no usage' not an error). - deriveServingIndicator: pure label derivation. Distinguishes this machine's OWN agents (local, shown softly) from another member consuming this machine's compute (remote/endpoint -> the headline 'in use by another member' case). - useMeshServingUsage: 4s poll, only while sharing. - indicator rendered under the Share toggle status line. - tests: 3 Rust (extractor local/remote/default), 7 TS (indicator label table), e2e mock answers mesh_serving_usage + seed hook. Verified live: firing a completion at the mesh API moves exactly the fields the extractor reads (req/tokens/local_attempt), local-only traffic correctly reads as not-a-remote-consumer. Local-only exploration on the mesh-state-fix branch; not part of PR #2448.
The card mirrored the running node's modelId into the field only when the field was empty, so switching models (node restarts on the new one) left the field showing the stale model. Sync whenever the served model differs from the field; safe from clobbering a mid-edit because the field is disabled while the slot is occupied.
buzz-agent's output is its tool calls; streamed assistant content is observability-only and normally never posted. Capable models reliably call `buzz messages send`, but weaker local models (e.g. via Buzz shared compute) often answer a conversational prompt in plain content and never call the send tool, silently dropping the reply. Add a content-delivery fallback in buzz-acp: track per-turn whether the model published its own message and buffer streamed content; on a normal turn end with content but no publish tool call, post that content as a threaded kind-9 reply (reusing buzz_sdk::build_message, signed with the agent keys, best-effort, mirroring post_failure_notice). Skips bare acknowledgements the base prompt forbids publishing. Live-validated against a small Gemma on a real community: the fallback fires when the model answers in prose and stays dormant when it calls the send tool. Unit tests cover send-tool detection (incl. read-vs-send discrimination) and bare-ack filtering.
Apply desktop Tauri rustfmt to mod_tests.rs — the desktop crate has a separate fmt gate (desktop-tauri-fmt-check) that the workspace fmt does not cover.
… 429
When a Buzz-compute agent starts, wait_for_mesh_inference polled the local
mesh ingress for up to 120s and, on failure, surfaced a raw "HTTP 429: model
not currently available" while the node still reported Running/Ok. In the GUI
this reads as a frozen dialog that eventually dies with a meaningless error —
the observed "consume never comes up / every model select fails" symptom.
Cross-machine testing (M5 serve; mini + M4 consume; native mesh-llm control)
showed two physically distinct readiness failures:
* catalog never syncs — the client connects to the host at the control
level (ping/RTT fine) but the peer gossip stream never establishes across
the network (iroh MultipathNotNegotiated / unreachable direct path), so
the served model never enters the local /v1/models catalog and every
request is rejected. Root cause is the network path to the host.
* routing never completes — the model syncs into the catalog but per-request
routing/transport to the host keeps failing.
wait_for_mesh_inference now tracks whether the served model was ever visible in
the local catalog during the wait and, on timeout, returns a stage-specific,
actionable message via a pure classifier (classify_mesh_readiness_failure) and
message builder (mesh_readiness_failure_message), both unit-tested. This does
not fix the underlying transport limitation (which lives in the mesh-llm/iroh
SDK, and reproduces with the native mesh-llm client too) — it makes the failure
legible instead of a silent 120s wall.
Note: the cross-machine consume path is NOT yet reliably working; this commit
is the user-facing diagnosis mitigation only.
|
almost there.... |
|
one missing piece sometimes blocks the connection... standby... |
Repins the mesh-llm-* git deps from tag v0.73.1 to rev c441ea7f328692b18b7f49ad819c7b1a603cbdcb, the v0.73.1-based backport of the owner-control / main-mesh relay endpoint-id collision fix (Mesh-LLM/mesh-llm#1064). Without the fix, a consumer with an owner identity (buzz always sets one for admission) starts a second iroh endpoint sharing the node's endpoint id; that endpoint evicts the main mesh endpoint's relay registration, silently killing relay fallback. Any consumer that cannot reach the host directly then never joins and every inference 429/503s. The rev still reports version 0.73.1, so the native runtime download resolves to the existing v0.73.1 release unchanged. Hand-verified in the release app on the meshllm community: mini consumed M5's shared gemma with owner identity present on both sides (the exact pre-fix failure condition), inference returned 200. NOTE: interim pin to an unmerged rev; retarget to a v0.73.x tag once #1064 merges.
|
OK this is now confirmed to work end to end. THere will be more holepiunch improvements coming, they dont need to block this however, as this will unblock people. |
|
Reviewed at head e07638b (all CI green; Parts 1–3 (toggle, usage indicator, model resync): clean, ship-ready. The Part 4 (content-delivery fallback): right concept, but the publish detection is too narrow, and the miss direction is user-visible double-posting.
Related: a deliberate-silence turn (base prompt: "silence is usually correct") that streamed any assistant text gets that text force-posted; the ≤40-char bare-ack list doesn't cover, e.g., "Nothing new here, ending the turn." Two fixes, either sufficient, both better together:
Minor:
Mechanics of the fallback are otherwise correct: EndTurn-only, take-once semantics mean no double-fire across the two EndTurn arms, and cancelled/truncated turns can't leak partial text. Verdict: parts 1–3 mergeable today; part 4 needs the detection fix or the scope gate first. |
|
thanks @tlongwell-block - on 4 now. and yeah it will go to a released version in future for sure (which will have may other fixes and split model support for frontier class, another day). (writing by hand old school) |
Review findings from #2448 (Wren + Eva): the content-delivery fallback inferred delivery from the send tool call's INPUT, which was wrong in both directions: - False negative (silent drop): turn_sent_message was set the moment a tool_call's rawInput matched 'messages send' — intent, not delivery. A send that subsequently failed (relay/network/auth/CLI error) still suppressed the fallback, dropping the exact reply the feature exists to save. - False positive (double post): the substring match missed argv-style publishes (subprocess.run(['buzz','messages','send',...])), so a capable model that narrates between tool calls and publishes via argv would get its whole turn narration re-posted by the fallback. Fix: registering a publish is now two-phase. - tool_call input matching (normalized so shell strings AND argv forms match) only registers a CANDIDATE toolCallId. - Delivery is confirmed solely from the candidate's terminal tool_call_update outcome: failed/cancelled → not delivered (fallback stays armed); completed → inspect rawOutput.isError, the CLI's {"accepted":...} response envelope, and any reported exit_code; a bare completed with no inspectable output keeps the status-quo direction (suppress, i.e. today's behavior). Tests: outcome classifier matrix (pending/failed/cancelled/completed x envelope/isError/exit_code) plus handler-level lifecycle tests through handle_session_update + take_undelivered_turn_message covering success, failure, cancel, failed-then-retry, and argv-style publish. Co-authored-by: Tyler Longwell <tlongwell@block.xyz> Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
|
@tlongwell-block this looks good to me now - I am not happy with any approach I have found for scoping it yet, so one of those sufficient fixes I think makes it worth it, so up to you if/when to include it (am having an agent have oner more look) |
|
@tlongwell-block decoupled the prose capture into PR #2681 to get it out of this - hope that makes sense |
Shared-compute usability fixes, discovered while getting a local agent to reliably run and reply on Buzz shared compute. Three related but distinct changes:
1. Share toggle reflects serve mode, not slot occupancy
running— so consuming a peer's compute wrongly lit the Share toggle.serve= sharing (on),client= consuming (off, disabled with an explanatory line).mesh_stop_noderefuses to tear down a client node, so a stale UI can't kill a consume session.2. Surface who is using shared compute on the Share card
mesh_serving_usagecommand projects the node's ownrouting_metrics(inflight, tokens served, tok/s, and the local-vs-remote/endpoint split that distinguishes your agent from a peer consuming your compute).3. Resync the Share model field when the served model changes
modelIdinto the field only when the field was empty, so switching models (node restarts on the new one) left a stale model shown.Testing
Dependency bump
Pins the six
mesh-llmcrates (sdk,host-runtime,client,node,system,events) from tagv0.73.1→ revc441ea7(v0.73.1 + 1 commit) —the patched mesh build these shared-compute fixes were developed and validated
against.