Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,27 @@ supervises up to 12 workers. The service bundle contains 14 executables, includi
broker, scanner, Ollama proxy, node-info, and node-settings; standalone broker
requirements are narrower.

## Node identity and verification

Desktop node ids mirror Go's `DirectoryNode.RecordKey()`: local and verified
records use `hostUuid` (reserved `preview:` / `manual:` principals are invalid);
all other records need a namespaced `peerRecordId`. Invalid records are dropped.
The shared TypeScript helper and Go test consume the same record-key fixture.
A verified `peerRecordId` is a proof binding, not a host id.

Broker discovery owns identity and lifetime. Proxy events only enrich established
peer records; matching names, addresses, timestamps, or claimed UUIDs never merge
records. Membership, workload attribution, metrics, and engine actions use the
canonical record key. Priorities and routing selection remain service-owned;
the desktop exposes no scheduler-priority or proxy-selection action.

Previews show **Not in your cluster** and offer **Pair** by address through the
existing PIN exchange. Manual engines show **Engine only — added by address**,
without PAIR identity, peer telemetry, or peer controls. Local and verified peer
cards retain their existing controls. Older peers remain unverified until the
services can verify them; the payload does not identify why proof is unavailable,
so the UI does not guess that an update is needed.

## Develop

```bash
Expand Down
48 changes: 25 additions & 23 deletions desktop/docs/services-parity.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,20 +59,18 @@ allows the user to restart the service.
Personal AI Router consumes `discovery:get-nodes` and `discovery:nodes-changed` from the
broker. The bridge:

- keys every node by the backend's stable per-host UUID (`AvailableNode.hostUuid`),
the same identity workloads (`originatedFrom`/`scheduledOn`), errors (`nodeId`),
proxy routing (`Node.ID`), `/v1/node-info` (`hostUuid`), and cluster membership
(`ClusterNode.nodeUuid`) all use; the hostname is display only;
- mirrors Go `DirectoryNode.RecordKey()`: local/verified records use `hostUuid`
unless reserved; all others require a `preview:` / `manual:` `peerRecordId`.
Invalid keys are dropped. Proof bindings are not host ids, and unverified
claimed UUIDs and display names never correlate with peer records;
- preserves the backend's canonical reachable address;
- reflects the discovery `trusted` (locally pinned peer) and `clustered` (belongs
to some cluster) flags, and uses `clustered` to mark an already-clustered
discovered node non-invitable. `trusted` is a display annotation the scanner
re-derives whenever the cluster-manager reports a pin-set change, and again on
its periodic sweep of each peer's reported membership; it is not the routing
gate. Each proxy answers "do I hold a pin for this peer?" from its own
mesh when it resolves candidates, so PAIR must never treat `trusted` as
authoritative for whether work can reach a node;
- merges per-engine proxy presence;
- reflects broker-reported `identityVerification`, `peerRecordId`, `trusted`,
and `clustered`, without establishing trust in TypeScript. Previews show
**Not in your cluster** with **Pair** by address using the existing PIN flow;
public cluster claims do not suppress that action. Manual engines show
**Engine only — added by address** and no Pair action;
- merges per-engine proxy presence only into broker-established peer identities.
Proxy events cannot create or resurrect records after broker withdrawal;
- carries flat and per-engine remote model inventories;
- emits node and discovery pushes to the renderer.

Expand All @@ -81,7 +79,12 @@ at startup and resolved as soon as the broker is ready, so self is identified by
the same UUID key rather than a hostname guess.

The broker discovery surface does not include full dynamic telemetry, so
Electron polls `/v1/node-info` for CPU, system memory, and per-adapter GPU values.
Electron polls `/v1/node-info` for CPU, system memory, and per-adapter GPU values
only for local/verified records. Preview/manual cards never mount peer telemetry,
engine controls, or workload-targeting UI. Older peers are unverified until the
services can prove their identity; no update diagnosis is inferred from missing
metadata. Missing/invalid record keys are dropped rather than shown under a
claimed UUID. Membership and workload UUIDs only resolve peer-keyed records.

Go classifies each GPU's `memory_kind`; the bridge preserves it as `memoryKind`
without name-based inference. Dedicated, unified, shared, and unavailable memory
Expand Down Expand Up @@ -156,9 +159,10 @@ Personal AI Router consequences (all reflection, no security implementation):
- The proxy `node/*` presence port is the peer's promoted proxy port. Personal AI Router
surfaces it as `EngineStatusData.proxyPort` and never labels it as the engine
port; a remote engine's port comes only from `ec` facts.
- Because both peers must speak the mTLS channel, mixed-version clusters cannot
run inference across the version boundary. Local use and the shared
nearby-model list are unaffected.
- Older peers without authenticated service metadata stay unverified previews,
not routing/model-inventory peers. Local engine use is unaffected. The UI
cannot distinguish an old peer from another unverified PAIR machine and does
not infer an upgrade recommendation from that state alone.

## Engine lifecycle

Expand Down Expand Up @@ -458,16 +462,14 @@ provide an equivalent client-facing contract:
| Serve the model hub (Ollama committed list, LM Studio live) | `src/electron/model-hub/` |
| Accumulate and reconcile receiver-side pending invites | `modular-state.ts`, `modular-supervisor.ts` |
| Mirror backend-coupled runtime defaults not yet reported | `modular-runtime.ts` |
| Collapse a superseded node row before the scanner proves it | `modular-state.ts`, `modular-runtime.ts` |

These are current integration boundaries. Do not add compatibility shims or
duplicate backend services around them.

The last row is a display-side collapse, not a directory decision. The scanner
treats a matching address, hostname, and `lastSeen` gap as grounds to ask the
machine who it is, and evicts only on a node-info identity mismatch; the bridge
has no such proof and hides the row on the suspicion alone. It must never be
extended into anything a consumer reads as an eviction.
Node identity and lifetime belong to broker discovery. The desktop does not
collapse rows by address, name, or age; independent records stay visible until
the broker withdraws them. The shared TypeScript key helper and Go
`DirectoryNode.RecordKey()` consume one table-driven fixture.

## Unused backend methods

Expand Down
Loading
Loading