From dda20b5d62508fbd826ee71e5ec122d6d9f48e87 Mon Sep 17 00:00:00 2001 From: Michael Neale Date: Tue, 4 Aug 2026 16:47:48 +1000 Subject: [PATCH] feat(mesh): mesh capacity framing, honest usage signal, clickable card MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Card now leads with pool capacity ("115 GB · 1 device") rather than this machine's participation, and drops the model name — it belongs in the detail view, not a 256px card. Fixes an inverted usage signal: routing_metrics is incremented only by this node's own OpenAI ingress, so remote/endpoint attempts mean this machine is CONSUMING, not being consumed. The old activeConsumers check read them as "someone is using your compute" — exactly backwards. mesh-llm exposes no inbound counter, so participation copy now uses inflight ("working now") and request_count ("N requests this session") and never claims work was served for other members. A test pins that wording. Headline text block is now a button for the forthcoming mesh detail view. Signed-off-by: Michael Neale --- .../mesh-compute/meshCardModel.test.mjs | 75 ++++++++++++++++--- .../features/mesh-compute/meshCardModel.ts | 67 ++++++++++++++--- .../ui/SidebarMeshComputeCard.tsx | 34 +++++++-- desktop/src/shared/api/tauriMesh.ts | 22 +++++- 4 files changed, 164 insertions(+), 34 deletions(-) diff --git a/desktop/src/features/mesh-compute/meshCardModel.test.mjs b/desktop/src/features/mesh-compute/meshCardModel.test.mjs index 2ad7aadbd..ec97c30df 100644 --- a/desktop/src/features/mesh-compute/meshCardModel.test.mjs +++ b/desktop/src/features/mesh-compute/meshCardModel.test.mjs @@ -15,6 +15,7 @@ import assert from "node:assert/strict"; import { describeMeshCapacity, + describeParticipation, describeReadyModels, deriveMeshCardModel, formatCapacityGb, @@ -68,7 +69,8 @@ function derive(overrides = {}) { toggle: OFF_TOGGLE, pendingAction: null, canShare: true, - activeConsumers: false, + busyNow: false, + requestsRouted: 0, ...overrides, }); } @@ -78,13 +80,13 @@ test("capacity headline counts devices and sums reported memory", () => { describeMeshCapacity( snapshot({ sharingDeviceCount: 3, sharedCapacityGb: 42.3 }), ), - "3 devices sharing · 42 GB", + "42 GB · 3 devices", ); assert.equal( describeMeshCapacity( snapshot({ sharingDeviceCount: 1, sharedCapacityGb: 18 }), ), - "1 device sharing · 18 GB", + "18 GB · 1 device", ); }); @@ -92,7 +94,7 @@ test("unknown capacity degrades to a device count, never 0 GB", () => { const text = describeMeshCapacity( snapshot({ sharingDeviceCount: 3, sharedCapacityGb: null }), ); - assert.equal(text, "3 devices sharing"); + assert.equal(text, "Mesh capacity · 3 devices"); assert.ok(!text.includes("0 GB"), "must not claim zero capacity"); }); @@ -101,9 +103,9 @@ test("an empty mesh reads as an honest empty state", () => { describeMeshCapacity( snapshot({ sharingDeviceCount: 0, sharedCapacityGb: null }), ), - "No compute shared yet", + "No mesh capacity yet", ); - assert.equal(describeMeshCapacity(null), "No compute shared yet"); + assert.equal(describeMeshCapacity(null), "No mesh capacity yet"); }); test("capacity formatting keeps small figures meaningful", () => { @@ -145,7 +147,7 @@ test("consuming never renders as sharing", () => { assert.equal(model.switchDisabled, false); }); -test("sharing states name the model and the community capacity", () => { +test("sharing leads with mesh capacity and omits the model name", () => { const model = derive({ toggle: SHARING_TOGGLE, status: { @@ -158,16 +160,20 @@ test("sharing states name the model and the community capacity", () => { consoleUrl: null, }, snapshot: snapshot({ + sharingDeviceCount: 1, + sharedCapacityGb: 115, devices: [device({ isSelf: true })], includesSelf: true, }), }); assert.equal(model.tone, "sharing"); assert.equal(model.switchOn, true); - assert.match(model.detail, /Gemma 4 26B A4B/); + assert.equal(model.headline, "115 GB · 1 device"); + // The model belongs in the detail view, not on a 256px card. + assert.ok(!/Gemma/i.test(model.detail ?? ""), "card must not name the model"); }); -test("active consumers are called out in the headline", () => { +test("inflight work reads as working, never as 'someone is using you'", () => { const model = derive({ toggle: SHARING_TOGGLE, status: { @@ -179,9 +185,54 @@ test("active consumers are called out in the headline", () => { apiBaseUrl: null, consoleUrl: null, }, - activeConsumers: true, + busyNow: true, }); - assert.match(model.headline, /in use now/); + assert.match(model.detail, /working now/); +}); + +test("routed requests are a subtle used-ness signal, not a served claim", () => { + const model = derive({ + toggle: SHARING_TOGGLE, + status: { + state: "running", + mode: "serve", + health: { status: "ok", reason: null }, + modelId: "m", + modelName: null, + apiBaseUrl: null, + consoleUrl: null, + }, + requestsRouted: 7, + }); + assert.match(model.detail, /7 requests this session/); +}); + +test("participation copy never claims work was served for other members", () => { + // mesh-llm exposes no inbound counter, so no wording here may imply that + // another member consumed this machine's compute. + const claims = [ + describeParticipation({ busyNow: false, requestsRouted: 0 }), + describeParticipation({ busyNow: true, requestsRouted: 3 }), + describeParticipation({ busyNow: false, requestsRouted: 3 }), + ]; + assert.deepEqual(claims, [ + "Sharing · ready", + "Sharing · working now", + "Sharing · 3 requests this session", + ]); + for (const claim of claims) { + assert.ok( + !/served|for (others|members|someone)|consumed/i.test(claim), + `must not claim served-for-others: ${claim}`, + ); + } +}); + +test("one routed request is singular", () => { + assert.equal( + describeParticipation({ busyNow: false, requestsRouted: 1 }), + "Sharing · 1 request this session", + ); }); test("a serve node with no advertised model is warming up, not serving", () => { @@ -227,7 +278,7 @@ test("the idle invitation leads with what the community already has", () => { snapshot: snapshot({ sharingDeviceCount: 2, sharedCapacityGb: 54 }), }); assert.equal(model.tone, "idle"); - assert.equal(model.headline, "2 devices sharing · 54 GB"); + assert.equal(model.headline, "54 GB · 2 devices"); }); test("the switch is disabled until a model can be resolved", () => { diff --git a/desktop/src/features/mesh-compute/meshCardModel.ts b/desktop/src/features/mesh-compute/meshCardModel.ts index 1e30af551..f2e14dd16 100644 --- a/desktop/src/features/mesh-compute/meshCardModel.ts +++ b/desktop/src/features/mesh-compute/meshCardModel.ts @@ -65,7 +65,11 @@ function plural(n: number, one: string, many = `${one}s`): string { } /** - * The community headline: how much compute is actually available right now. + * The community headline: how much compute is actually reachable right now. + * + * Named "Mesh capacity" rather than "sharing" because the figure describes the + * pool, not this machine's participation — the switch already says whether you + * are in it. * * Counts only devices advertising a routable model — the same standard routing * uses — so the number never promises capacity that cannot be reached. Never @@ -73,12 +77,14 @@ function plural(n: number, one: string, many = `${one}s`): string { */ export function describeMeshCapacity(snapshot: MeshSnapshot | null): string { if (!snapshot || snapshot.sharingDeviceCount === 0) { - return "No compute shared yet"; + return "No mesh capacity yet"; } const { sharingDeviceCount: count, sharedCapacityGb: gb } = snapshot; - const devices = `${count} ${plural(count, "device")} sharing`; + const devices = `${count} ${plural(count, "device")}`; // Unknown capacity degrades to the device count rather than printing 0 GB. - return gb === null ? devices : `${devices} · ${formatCapacityGb(gb)}`; + return gb === null + ? `Mesh capacity · ${devices}` + : `${formatCapacityGb(gb)} · ${devices}`; } /** Short label for what is ready to run, or null when nothing is. */ @@ -95,6 +101,30 @@ export function describeReadyModels( return `${models.length} models ready`; } +/** + * The sharing detail line: proof this machine is participating, and that the + * mesh has actually been used. + * + * Scrupulously avoids claiming someone else consumed this machine's compute — + * mesh-llm exposes no inbound counter, so "requests routed" is the strongest + * true statement available. Worded as "requests" without "served for others". + */ +export function describeParticipation({ + busyNow, + requestsRouted, +}: { + busyNow: boolean; + requestsRouted: number; +}): string { + if (busyNow) { + return "Sharing · working now"; + } + if (requestsRouted > 0) { + return `Sharing · ${requestsRouted} ${plural(requestsRouted, "request")} this session`; + } + return "Sharing · ready"; +} + /** * Trim a model reference down to something that fits a 256px sidebar. * `unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q4_K_M` → `Gemma 4 26B A4B`. @@ -125,7 +155,8 @@ export function deriveMeshCardModel({ toggle, pendingAction, canShare, - activeConsumers, + busyNow, + requestsRouted, }: { snapshot: MeshSnapshot | null; status: MeshNodeStatus | null; @@ -133,8 +164,21 @@ export function deriveMeshCardModel({ pendingAction: "start" | "stop" | null; /** False when no model can be resolved yet (catalog still loading). */ canShare: boolean; - /** Another member is actively using this machine's compute right now. */ - activeConsumers: boolean; + /** + * This node has inference in flight (`inflight > 0`). + * + * Honest but coarse: mesh-llm's inflight counter does not say whether the + * work is for a local agent or a remote member, so this only ever claims + * "working", never "someone is using your compute". + */ + busyNow: boolean; + /** + * Requests this node's ingress has routed this session (`request_count`). + * + * Outbound routing, NOT work served for others — mesh-llm exposes no inbound + * counter. Used only as a subtle "this has been used" signal. + */ + requestsRouted: number; }): MeshCardModel { const devices = snapshot?.devices ?? []; const participantCount = devices.length; @@ -219,14 +263,13 @@ export function deriveMeshCardModel({ showSoloHint: false, }; } - const model = status?.modelName ?? status?.modelId; + // Model name is deliberately absent: it belongs in the detail view, not on + // a 256px card whose job is "am I in the mesh, and is it alive". return { ...base, tone: "sharing", - headline: activeConsumers - ? "Sharing · in use now" - : "Sharing this computer", - detail: model ? `${shortModelLabel(model)} · ${capacity}` : capacity, + headline: capacity, + detail: describeParticipation({ busyNow, requestsRouted }), showSoloHint: isSolo, }; } diff --git a/desktop/src/features/mesh-compute/ui/SidebarMeshComputeCard.tsx b/desktop/src/features/mesh-compute/ui/SidebarMeshComputeCard.tsx index 3698c3259..13fcddbc8 100644 --- a/desktop/src/features/mesh-compute/ui/SidebarMeshComputeCard.tsx +++ b/desktop/src/features/mesh-compute/ui/SidebarMeshComputeCard.tsx @@ -62,9 +62,12 @@ const TONE_RING_CLASS: Record = { export function SidebarMeshComputeCard({ className, onOpenComputeSettings, + onOpenDetail, }: { className?: string; onOpenComputeSettings?: () => void; + /** Open the full mesh view (topology, capacity, per-device detail). */ + onOpenDetail?: () => void; }) { const shouldReduceMotion = useReducedMotion(); const { status, refresh: refreshStatus } = useMeshNodeStatus(); @@ -82,9 +85,15 @@ export function SidebarMeshComputeCard({ }); const toggle = deriveMeshShareToggle(status); - const usage = useMeshServingUsage(toggle.isSharing); - const activeConsumers = - (usage?.remoteAttempts ?? 0) > 0 || (usage?.endpointAttempts ?? 0) > 0; + // Poll whenever a runtime slot is occupied, not just when sharing: a + // consuming node also has inflight work worth showing. + const usage = useMeshServingUsage(toggle.isSharing || toggle.isConsuming); + // `inflight` is the only honest "working" signal: mesh-llm's routing metrics + // are all OUTBOUND (incremented by this node's own ingress), so a remote/ + // endpoint attempt means this machine is CONSUMING, not being consumed. + // Reading them as "someone is using my compute" is exactly backwards. + const busyNow = (usage?.inflight ?? 0) > 0; + const requestsRouted = usage?.requestsServed ?? 0; // One-shot catalog fetch: the card needs the hardware-appropriate // recommendation so the switch can start sharing without a model picker. @@ -116,7 +125,8 @@ export function SidebarMeshComputeCard({ toggle, pendingAction, canShare: Boolean(recommended), - activeConsumers, + busyNow, + requestsRouted, }); async function handleToggle(next: boolean) { @@ -146,7 +156,7 @@ export function SidebarMeshComputeCard({ } // A transport failure is worth showing; an empty mesh is not an error and is - // already expressed by the headline ("No compute shared yet"). + // already expressed by the headline ("No mesh capacity yet"). const errorText = actionError ?? snapshotError; return ( @@ -178,7 +188,17 @@ export function SidebarMeshComputeCard({ {TONE_ICON[model.tone]} -
+ {/* + Clickable region is the text block, not the whole card: wrapping the + Switch in a button would nest interactive elements. + */} +
+ { } /** - * Host-side usage of the compute this machine is sharing. The - * local/remote/endpoint attempt split distinguishes this machine's own agents - * (local) from another member consuming this machine's compute (remote/endpoint). + * This machine's own routing activity — **outbound**, not work done for others. + * + * Every counter here comes from mesh-llm's `routing_metrics`, which is + * incremented only by the local OpenAI ingress (`network/openai/transport.rs`) + * when *this* node dispatches a request. The inbound peer-serving path + * (`mesh/stage_transport.rs`) never touches it — it only observes inflight. + * + * So the attempt split means where MY requests went, not who asked me: + * - `localAttempts` — I ran it on my own GPU + * - `remoteAttempts` — I sent it to a peer (i.e. I am CONSUMING) + * - `endpointAttempts` — I sent it to an endpoint + * + * `tokensServed` is likewise `completion_tokens_observed`: tokens I received, + * not tokens I produced for someone else. + * + * mesh-llm exposes no inbound "requests I served for others" counter today, so + * never label any field here as proof that this machine's compute was used by + * another member. `inflight` is the one honest "busy right now" signal, and it + * does not distinguish who the work is for. */ export type MeshServingUsage = { inflight: number;