feat(mesh): agent call-to-action and shared-compute decorator

Sharing compute that no agent can use is a dead end, and the sidebar card is
where someone would notice it. The card now offers to create an agent — but
only when the setup is genuinely incomplete: capacity exists (shared by this
machine or the community) and no persona carries the relay-mesh provider.

The CTA's value is that it stays quiet, so most of its tests pin silence. It
says nothing once a mesh agent exists, nothing when there is no capacity
anywhere (that would relocate the dead end rather than fix it), and nothing
while the persona list is still loading, since prompting someone to create an
agent they may already have is worse than a moment's quiet.

Agents that run on shared compute are now decorated in ManagedAgentRow. Where
an agent's inference runs is otherwise invisible without opening its config,
and shared compute is the one provider whose health the owner can act on
directly from the card.

Detection reads the persisted provider id (relay-mesh, the same constant the
backend gates mesh model discovery on) rather than inferring intent from a
model string.

Signed-off-by: Michael Neale <michael.neale@gmail.com>
This commit is contained in:
Michael Neale
2026-08-04 18:23:24 +10:00
parent ebf34346ca
commit 67186f83d5
4 changed files with 248 additions and 1 deletions

View File

@@ -5,12 +5,14 @@ import {
ChevronDown,
ChevronRight,
RefreshCw,
Share2,
} from "lucide-react";
import { useAppNavigation } from "@/app/navigation/useAppNavigation";
import { PresenceDot } from "@/features/presence/ui/PresenceBadge";
import { Badge } from "@/shared/ui/badge";
import { AgentStatusBadge } from "@/features/agents/ui/AgentStatusBadge";
import { usesMeshCompute } from "@/features/mesh-compute/meshAgents";
import { useAgentWorking } from "@/features/agents/agentWorkingSignal";
import { useOpenAgentActivity } from "@/features/agents/useOpenAgentActivity";
import { formatElapsed } from "@/features/agents/ui/agentSessionUtils";
@@ -407,8 +409,23 @@ function RuntimeBlock({
<p className="truncate font-mono text-xs text-foreground">
{agent.agentCommand}
</p>
{runtimeSource || agent.model ? (
{runtimeSource || agent.model || usesMeshCompute(agent) ? (
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
{/*
Where an agent's inference runs is otherwise invisible without
opening its config, and "on shared compute" is the one provider
whose health the user can actually act on from the sidebar card.
*/}
{usesMeshCompute(agent) ? (
<span
className="inline-flex items-center gap-1 text-emerald-600 dark:text-emerald-400"
data-testid="agent-uses-mesh"
title="Runs on Buzz shared compute"
>
<Share2 aria-hidden className="h-3 w-3" />
Shared compute
</span>
) : null}
{runtimeSource ? <span>{runtimeSource}</span> : null}
{agent.model ? <span>{agent.model}</span> : null}
</div>

View File

@@ -0,0 +1,111 @@
import assert from "node:assert/strict";
import test from "node:test";
/**
* Contract tests for mesh agent detection and the card's call to action.
*
* The CTA's value is that it stays quiet. A nudge that renders every time the
* card does becomes furniture, so most of these tests pin *silence*.
*/
import {
deriveMeshCallToAction,
summarizeMeshAgents,
usesMeshCompute,
} from "./meshAgents.ts";
function persona(overrides = {}) {
return {
id: "p1",
displayName: "Hop",
provider: "relay-mesh",
...overrides,
};
}
test("mesh agents are identified by provider, not by model string", () => {
const summary = summarizeMeshAgents([
persona({ id: "a", displayName: "Zoe" }),
persona({ id: "b", displayName: "Ada" }),
persona({ id: "c", displayName: "Other", provider: "anthropic" }),
// A model that merely mentions a mesh model is not a mesh agent: intent
// lives in the persisted provider, not in a free-text model field.
persona({ id: "d", displayName: "Lookalike", provider: null }),
]);
assert.equal(summary.count, 2);
assert.equal(summary.hasAny, true);
assert.deepEqual(summary.names, ["Ada", "Zoe"], "names sort for stable copy");
});
test("provider matching tolerates whitespace and absence", () => {
assert.equal(usesMeshCompute(persona({ provider: " relay-mesh " })), true);
assert.equal(usesMeshCompute(persona({ provider: "anthropic" })), false);
assert.equal(usesMeshCompute(persona({ provider: null })), false);
assert.equal(usesMeshCompute(null), false);
assert.equal(usesMeshCompute(undefined), false);
});
test("an empty persona list is not an error", () => {
const summary = summarizeMeshAgents([]);
assert.equal(summary.hasAny, false);
assert.deepEqual(summary.names, []);
assert.equal(summarizeMeshAgents(undefined).hasAny, false);
});
test("the CTA appears only when compute has no consumer", () => {
// Sharing, but nothing can use it — the dead end worth naming.
assert.deepEqual(
deriveMeshCallToAction({
personas: [persona({ provider: "anthropic" })],
isSharing: true,
meshHasCapacity: false,
}),
{ kind: "createAgent", label: "Set up an agent to use it" },
);
// Not sharing, but the community has capacity this person could use.
assert.equal(
deriveMeshCallToAction({
personas: [],
isSharing: false,
meshHasCapacity: true,
}).kind,
"createAgent",
);
});
test("the CTA stays silent once a mesh agent exists", () => {
assert.deepEqual(
deriveMeshCallToAction({
personas: [persona()],
isSharing: true,
meshHasCapacity: true,
}),
{ kind: "none" },
);
});
test("no capacity anywhere means the CTA would just move the dead end", () => {
// Telling someone to build an agent for a mesh with nothing in it does not
// help them; it relocates the problem.
assert.deepEqual(
deriveMeshCallToAction({
personas: [],
isSharing: false,
meshHasCapacity: false,
}),
{ kind: "none" },
);
});
test("an unloaded persona list never prompts a possible duplicate", () => {
// `undefined` is "not fetched yet". Prompting someone to create an agent they
// may already have is worse than staying quiet for a moment.
assert.deepEqual(
deriveMeshCallToAction({
personas: undefined,
isSharing: true,
meshHasCapacity: true,
}),
{ kind: "none" },
);
});

View File

@@ -0,0 +1,95 @@
import type { AgentPersona } from "@/shared/api/types";
/**
* Which agents are set up to run on Buzz shared compute.
*
* A persona points at the mesh by carrying `provider === "relay-mesh"` — the
* same id the backend gates mesh model discovery on
* (`managed_agents::RELAY_MESH_PROVIDER_ID`), so this asks the persistent
* record rather than inferring intent from a model string.
*
* The point is a call to action: sharing compute that no agent can use is a
* dead end, and the card is where someone would notice. So the card needs to
* know not just *whether* mesh agents exist but whether the whole setup is
* inert — capacity with no consumer, or a consumer with no capacity.
*/
/** The provider id a persona carries when it runs on shared compute. */
export const RELAY_MESH_PROVIDER_ID = "relay-mesh";
export type MeshAgentSummary = {
/** Agents configured to run on shared compute. */
count: number;
/** Display names, for naming one specifically. Sorted for stable copy. */
names: string[];
/** True when at least one such agent exists. */
hasAny: boolean;
};
export function summarizeMeshAgents(
personas: AgentPersona[] | undefined,
): MeshAgentSummary {
const matching = (personas ?? []).filter(
(persona) => persona.provider?.trim() === RELAY_MESH_PROVIDER_ID,
);
const names = matching
.map((persona) => persona.displayName)
.sort((a, b) => a.localeCompare(b));
return { count: matching.length, names, hasAny: matching.length > 0 };
}
/**
* Whether a given persona runs on shared compute.
*
* Used to decorate an agent wherever it appears, so "this one is running on the
* mesh" is legible without opening its config.
*/
export function usesMeshCompute(
persona: Pick<AgentPersona, "provider"> | null | undefined,
): boolean {
return persona?.provider?.trim() === RELAY_MESH_PROVIDER_ID;
}
/**
* The card's call to action, or null when none is warranted.
*
* Deliberately silent in the common case. A nudge that appears every time the
* card renders becomes furniture, so this only speaks when the setup is
* genuinely incomplete in a way the person can act on:
*
* - sharing (or capacity exists) but no agent can use it → offer to make one
* - an agent exists but nothing is shared anywhere → offer to share
*
* Returns null while the picture is still unknown (`personas === undefined`),
* because prompting someone to create an agent they may already have is worse
* than staying quiet for a moment.
*/
export type MeshCallToAction =
| { kind: "createAgent"; label: string }
| { kind: "none" };
export function deriveMeshCallToAction({
personas,
isSharing,
meshHasCapacity,
}: {
personas: AgentPersona[] | undefined;
isSharing: boolean;
/** Anyone in the community is sharing, including this machine. */
meshHasCapacity: boolean;
}): MeshCallToAction {
// Unknown yet — say nothing rather than nudge toward a duplicate.
if (personas === undefined) {
return { kind: "none" };
}
if (summarizeMeshAgents(personas).hasAny) {
return { kind: "none" };
}
// Compute with no consumer is the dead end worth naming. Only prompt when
// there is actually capacity to use — telling someone to build an agent for
// a mesh with nothing in it just moves the dead end.
if (isSharing || meshHasCapacity) {
return { kind: "createAgent", label: "Set up an agent to use it" };
}
return { kind: "none" };
}

View File

@@ -8,6 +8,9 @@ import { meshStartNode, meshStopNode } from "@/shared/api/tauriMesh";
import type { MeshModelCatalog } from "@/shared/api/tauriMesh";
import { meshModelCatalog } from "@/shared/api/tauriMesh";
import { usePersonasQuery } from "@/features/agents/hooks";
import { requestOpenCreateAgent } from "@/features/agents/openCreateAgentEvent";
import { deriveMeshCallToAction } from "../meshAgents";
import { useMeshDownloadProgress } from "../hooks/useMeshDownloadProgress";
import { useMeshLiveView } from "../hooks/useMeshLiveView";
import { useMeshNodeStatus } from "../hooks/useMeshNodeStatus";
@@ -99,6 +102,16 @@ export function SidebarMeshComputeCard({
const { progress: downloadProgress, reset: resetDownloadProgress } =
useMeshDownloadProgress();
// Sharing compute no agent can use is a dead end, and this card is where
// someone would notice. The CTA stays silent unless the setup is actually
// incomplete — see deriveMeshCallToAction.
const { data: personas } = usePersonasQuery();
const callToAction = deriveMeshCallToAction({
personas,
isSharing: toggle.isSharing,
meshHasCapacity: (snapshot?.sharingDeviceCount ?? 0) > 0,
});
// Inbound work has no counter in mesh-llm, so it is inferred by elimination
// across two samples: serving, inflight > 0, and our own dispatch count flat
// means the work is not ours. Keeping the previous sample in a ref (not
@@ -261,6 +274,17 @@ export function SidebarMeshComputeCard({
showSoloHint={model.showSoloHint}
/>
{callToAction.kind === "createAgent" ? (
<button
className="mt-2 text-2xs font-medium text-foreground/80 underline-offset-2 transition-colors hover:text-foreground hover:underline focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring"
data-testid="mesh-card-create-agent"
onClick={() => requestOpenCreateAgent()}
type="button"
>
{callToAction.label}
</button>
) : null}
{errorText ? (
<p
className="mt-2 line-clamp-2 text-2xs leading-snug text-destructive"