From d830359a23d2e5f75cb15c6a6946622a96668e07 Mon Sep 17 00:00:00 2001 From: Michael Neale Date: Tue, 4 Aug 2026 17:05:57 +1000 Subject: [PATCH] fix(mesh): correct share-compute copy and unknown-capacity state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two UX-only fixes, no behavior change: An unfetched snapshot (null) reported "No mesh capacity yet", conflating "not checked yet" with "the community has nothing" — a verdict on everyone else's machines rendered during startup. It now reads "Checking mesh capacity…" and only claims an empty mesh once a snapshot actually says so. Sharing copy described the wrong mechanism: a sharing machine contributes inference capacity, it does not host other members' agent processes. "Turn on to let members run agents on this computer" becomes "Share compute to run models", and the settings card description is corrected to match. Also plumbs two data additions the detail view needs: snapshot.memberCount (NIP-43 roster size, the denominator for "N of M sharing") and the routing_metrics.pressure split (locallyServed / remotelyServed / endpointServed) — completed requests rather than attempts, which is the honest basis for "this ran on my machine" vs "this ran on someone else's". Signed-off-by: Michael Neale --- desktop/src-tauri/src/mesh_llm/snapshot.rs | 8 +++++++ desktop/src-tauri/src/mesh_llm/usage.rs | 16 +++++++++++++ .../mesh-compute/meshCardModel.test.mjs | 4 +++- .../features/mesh-compute/meshCardModel.ts | 10 ++++++-- .../ui/MeshComputeSettingsCard.tsx | 2 +- desktop/src/shared/api/tauriMesh.ts | 24 +++++++++++++++++++ 6 files changed, 60 insertions(+), 4 deletions(-) diff --git a/desktop/src-tauri/src/mesh_llm/snapshot.rs b/desktop/src-tauri/src/mesh_llm/snapshot.rs index 5b824d6b0..bc95d6d42 100644 --- a/desktop/src-tauri/src/mesh_llm/snapshot.rs +++ b/desktop/src-tauri/src/mesh_llm/snapshot.rs @@ -80,6 +80,13 @@ pub struct MeshSnapshot { pub devices: Vec, /// True when the local member's own device is among the sharing devices. pub includes_self: bool, + /// Members in the community right now (NIP-43 roster size). + /// + /// The denominator for "N of M sharing". Deliberately NOT used to estimate + /// unshared capacity: a member who never starts a node publishes no status + /// note, so their hardware is unknown by design — they never consented to + /// disclose it. Ghost nodes in the topology are a count, never GB. + pub member_count: usize, /// Why the snapshot is empty, when it is. Never a hard error: an empty /// mesh is a normal, expected state. pub reason: Option, @@ -238,6 +245,7 @@ pub fn snapshot_from_events( }; MeshSnapshot { + member_count: members.len(), sharing_device_count, shared_capacity_gb, models, diff --git a/desktop/src-tauri/src/mesh_llm/usage.rs b/desktop/src-tauri/src/mesh_llm/usage.rs index 830f224ef..56046cc71 100644 --- a/desktop/src-tauri/src/mesh_llm/usage.rs +++ b/desktop/src-tauri/src/mesh_llm/usage.rs @@ -29,6 +29,18 @@ pub struct MeshServingUsage { pub endpoint_attempts: u64, /// Other nodes currently visible as peers. pub peers: u64, + /// Completed requests this node's own GPU answered. + /// + /// From `routing_metrics.pressure`. Unlike the `*_attempts` counters these + /// count finished requests rather than tries, so they are the honest basis + /// for "this ran here" vs "this ran on someone else's machine". + pub locally_served: u64, + /// Completed requests a peer answered for this node — i.e. this machine + /// CONSUMED another member's compute. The one true "I used someone else's + /// hardware" figure available today. + pub remotely_served: u64, + /// Completed requests answered by a configured endpoint (not a mesh peer). + pub endpoint_served: u64, } /// Pure extractor: project a raw SDK status payload into [`MeshServingUsage`]. @@ -40,6 +52,7 @@ pub fn serving_usage_from_payload(payload: &serde_json::Value) -> MeshServingUsa let u64_at = |v: &serde_json::Value| v.as_u64().unwrap_or(0); let rm = payload.get("routing_metrics"); let local = rm.and_then(|m| m.get("local_node")); + let pressure = rm.and_then(|m| m.get("pressure")); let get_u64 = |obj: Option<&serde_json::Value>, key: &str| { obj.and_then(|o| o.get(key)).map(u64_at).unwrap_or(0) }; @@ -64,5 +77,8 @@ pub fn serving_usage_from_payload(payload: &serde_json::Value) -> MeshServingUsa .and_then(serde_json::Value::as_array) .map(|a| a.len() as u64) .unwrap_or(0), + locally_served: get_u64(pressure, "locally_served_request_count"), + remotely_served: get_u64(pressure, "remotely_served_request_count"), + endpoint_served: get_u64(pressure, "endpoint_request_count"), } } diff --git a/desktop/src/features/mesh-compute/meshCardModel.test.mjs b/desktop/src/features/mesh-compute/meshCardModel.test.mjs index ec97c30df..e7f7e3eac 100644 --- a/desktop/src/features/mesh-compute/meshCardModel.test.mjs +++ b/desktop/src/features/mesh-compute/meshCardModel.test.mjs @@ -105,7 +105,9 @@ test("an empty mesh reads as an honest empty state", () => { ), "No mesh capacity yet", ); - assert.equal(describeMeshCapacity(null), "No mesh capacity yet"); + // `null` is "not fetched yet" and must not be reported as an empty mesh: + // that would pass judgement on everyone else's machines during startup. + assert.equal(describeMeshCapacity(null), "Checking mesh capacity…"); }); test("capacity formatting keeps small figures meaningful", () => { diff --git a/desktop/src/features/mesh-compute/meshCardModel.ts b/desktop/src/features/mesh-compute/meshCardModel.ts index f2e14dd16..91406627e 100644 --- a/desktop/src/features/mesh-compute/meshCardModel.ts +++ b/desktop/src/features/mesh-compute/meshCardModel.ts @@ -76,7 +76,13 @@ function plural(n: number, one: string, many = `${one}s`): string { * says "total": the underlying query is capped at 100 members. */ export function describeMeshCapacity(snapshot: MeshSnapshot | null): string { - if (!snapshot || snapshot.sharingDeviceCount === 0) { + // `null` is "not fetched yet", NOT "the community has nothing". Claiming an + // empty mesh before the first snapshot lands reads as a verdict on everyone + // else's machines while the card is still starting up. + if (!snapshot) { + return "Checking mesh capacity…"; + } + if (snapshot.sharingDeviceCount === 0) { return "No mesh capacity yet"; } const { sharingDeviceCount: count, sharedCapacityGb: gb } = snapshot; @@ -280,7 +286,7 @@ export function deriveMeshCardModel({ ...base, tone: "idle", headline: capacity, - detail: ready ?? "Turn on to let members run agents on this computer.", + detail: ready ?? "Share compute to run models.", showSoloHint: false, }; } diff --git a/desktop/src/features/mesh-compute/ui/MeshComputeSettingsCard.tsx b/desktop/src/features/mesh-compute/ui/MeshComputeSettingsCard.tsx index bf2d8e7c2..7419916cb 100644 --- a/desktop/src/features/mesh-compute/ui/MeshComputeSettingsCard.tsx +++ b/desktop/src/features/mesh-compute/ui/MeshComputeSettingsCard.tsx @@ -227,7 +227,7 @@ export function MeshComputeSettingsCard() {
{error ? ( diff --git a/desktop/src/shared/api/tauriMesh.ts b/desktop/src/shared/api/tauriMesh.ts index 53a4caeea..ea7e40c35 100644 --- a/desktop/src/shared/api/tauriMesh.ts +++ b/desktop/src/shared/api/tauriMesh.ts @@ -83,6 +83,22 @@ export type MeshServingUsage = { remoteAttempts: number; endpointAttempts: number; peers: number; + /** + * Completed requests this machine's own GPU answered. + * + * From `routing_metrics.pressure`. Unlike the `*Attempts` counters these are + * finished requests rather than tries, which makes them the honest basis for + * "this ran here" vs "this ran on someone else's machine". + */ + locallyServed: number; + /** + * Completed requests a peer answered for this machine — i.e. this machine + * CONSUMED another member's compute. The one true "I used someone else's + * hardware" figure available today. + */ + remotelyServed: number; + /** Completed requests answered by a configured endpoint, not a mesh peer. */ + endpointServed: number; }; export async function meshServingUsage(): Promise { @@ -156,6 +172,14 @@ export type MeshSnapshot = { models: string[]; devices: MeshSnapshotDevice[]; includesSelf: boolean; + /** + * Members in the community right now (NIP-43 roster size). + * + * The denominator for "N of M sharing". Never used to estimate unshared + * capacity: a member who never starts a node publishes no status note, so + * their hardware is unknown by design. Ghosts are a count, never GB. + */ + memberCount: number; reason: string | null; };