Guidance for agents and contributors working in the NVIDIA Personal AI Router (PAIR) repository. It holds an Electron desktop application and the Go services it runs on, built from one checkout.
This file is the short orientation. CONTRIBUTING.md is the contribution policy and Developer Guide is the code tour; both are normative where this file is only a summary.
Personal-AI-Router/
├── desktop/ Electron + React application
├── services/ Go broker, workers, proxies, terminal interface, installers
├── docs/ Product, architecture, and build documentation
├── scripts/ Repository-level helper scripts
└── Makefile Wraps the build and check commands (Linux and macOS)
services/ is the source of truth for runtime behavior and desktop/ is a
client of it. The application relays commands and renders reported state — it
does not reimplement routing, scheduling, discovery, or cryptography in
TypeScript. If you find yourself writing any of those in the desktop tree, the
change belongs in a Go service instead.
- Electron starts only
nvpair-ui-brokerfromdesktop/cli-bin/as a Go service. Operator tools are separate from this supervised service inventory. - The broker supervises the other Go workers: discovery, proxies, engines, cluster, settings, manual nodes, workloads, errors, and the scheduler.
- The broker attempts to start 12 workers, including
omlx-proxy. Standalone, only the scanner is required: missing default auxiliary binaries and initial auxiliary spawn failures are non-fatal. Invalid explicit worker paths are fatal; scanner's initial spawn failure is also fatal. Optional initial spawn failures retry under the same budget as crashes and eligible hang restarts: 1 s → 16 s backoff, five replacement attempts. Reset requires 60 s uptime and a successful health probe since that spawn. Missing default binaries staymissingwithout retries untilworkers:restartfinds them. - Desktop additionally prevalidates broker, scanner, Ollama proxy, node-info, and node-settings binaries. Do not confuse its requirements with standalone mode.
nvpair-tuiis bundled but never supervised. It owns its own broker.- Interprocess communication is newline-delimited JSON-RPC 2.0 over stdio
(optional
--ipc), using relay protocol v2: notifications carryepochandseq; requests/responses carry neither.broker:resyncreportsprotocol: 2. Both Go and desktop readers treat envelope mismatches as terminal (desktop fails pending calls and exits cleanly without reconnecting). Clients recover domain snapshots on epoch changes, not by polling on sequence gaps. - The broker emits a heartbeat every 15 seconds. Desktop owns recovery after 45 seconds of inbound silence; this is not worker-heartbeat monitoring or a promise to reconnect automatically after every broker crash.
- Separately, the broker probes each worker's
worker/healthevery 30 s (--worker-health-interval, minimum 1 s), with a 5 s deadline capped at the interval. Two consecutive failures reportdegraded; a successful probe restoresrunningand clears the failure count. Three failures restart scanner, errors, settings, node-info, workload-manager, manual-nodes, cluster-manager, and scheduler. Engine-manager and the three proxies never auto-restart on a hang; desktop shows "Not responding — restart manually". workers:get-statusand full-snapshotworkers:updateexpose all 12 workers;workers:restartrequests an explicit retry/restart, including after exhaustion. Desktop renders these under Settings → Service → Workers; TUI Overview uses the same registry, withRand confirmation to restart. Unix cross-process hang tests do not establish Windows behavior.- The desktop build compiles the Go binaries from the sibling
services/directory withnpm run build:modular-binaries. There is no submodule.
Never launch the same worker from both Electron and the broker, and do not add a fallback path for a missing broker.
cd desktop
npm install
npm startnpm start builds the service binaries first, so you do not need to build
services/ separately.
- Checks:
npm run lint,npm run typecheck,npm run test:unit,npm run dead-code:check,npm run service-contracts:check,npm run licenses:check(release check) - Contract tooling:
npm run service-contracts/:write/:check - Attribution tooling:
npm run licenses/npm run licenses:check
Use npm run typecheck rather than tsc directly, because the project has
split Node and web targets. dead-code:check fails on any exported value or
type that nothing imports, so an export added out of habit breaks it: delete it,
drop the export when it is only used inside its own file, or justify it in
desktop/dead-code-omissions.json.
Never edit desktop/docs/services-api.md by hand. It is generated by
npm run service-contracts:write.
Fourteen service executables: broker + 12 workers + TUI. Each component is its
own module, with its tests beside its source and a README.md describing its
JSON-RPC surface. Shared packages live in
shared/, and tests/ holds cross-process tests that drive real binaries.
Prefer the Go source when a README disagrees with it.
Build from services/ — build.bat on Windows, ./build.sh on Linux and
macOS. Staged binaries land in services/build/bin/. Requires Go 1.25 or newer
and jq.
Run go test ./... from a component directory, and from services/tests for
cross-process coverage. Some tests skip themselves rather than fail; refer to
Testing, and treat a skip as untested rather than
passing. Live engine and multi-node tests can modify real system state, so run
them only when you intend to and only against systems and networks you control.
On Linux and macOS the repository-root Makefile is the shortest path to a clean
run. Run make on its own to list every target.
make check # SPDX headers, build-script verify, lint, typecheck, contracts, desktop tests
make test # desktop unit tests plus go test in every services modulemake check does not include npm run dead-code:check, so run that as well.
make licenses-check (cd desktop && npm run licenses:check) is a release
check that verifies third-party notices against attribution-manifest.json; the
gate now passes cleanly across all dependencies.
On Windows, run the underlying npm and go test commands directly.
- No type casting. No
as Type,as any,as unknown, or: any, and nounknownin signatures. If a cast seems necessary, the types are wrong. - Absolute imports across directories. Use the
@/...alias for anything that would otherwise need../. Within one directory,./siblingis correct. - Static imports only. No
await import(). - No renderer imports from
src/electron/. Shared code belongs insrc/shared/or behind the preload bridge. - Say engine, not backend, in user-facing copy and new code. Existing wire names, payload fields, and Go symbols keep their spelling: they are external contracts.
- No legacy fallbacks. One canonical path. Delete what you replace instead of leaving a compatibility branch behind.
- Never log prompts, messages, response bodies, pairing PINs, or key material
in standard system log sinks (
slog,applog, stderr, file logs). Telemetry spans may optionally capture prompt and completion payloads directly to the local OpenTelemetry collector ONLY when explicitly enabled by the user for local AI metrics and evaluation. Pairing PINs, auth keys, and credentials remain strictly forbidden in all sinks at all times. - Every file carries a two-line SPDX header. Check the tree with
node scripts/spdx-headers.mjs, and insert missing headers with--fix. - When changing a JSON-RPC method or payload, update the producing Go
service, the broker relay, every consumer, the desktop bridge under
desktop/src/electron/service-bridge/, the tests, and the documentation in the same change. Then runnpm run service-contracts:checkfromdesktop/. - Bump the version of any service binary whose compiled output you change in
services/versions.json, and describe any user-facing change in the pull request so it reaches the release notes. Versioning gives the rules. - Sign off every commit with
git commit -s. The Developer Certificate of Origin trailer has to match the commit author, so a missing or mismatched sign-off means rewriting the commit rather than fixing it in review. - Prefer small, focused commits. Work from a fork on a branch off
main, and open a pull request; only maintainers merge.
Track open release decisions in Next release readiness. Documentation corrections are not runtime fixes or artifact certification.
- Keep released changelog entries separate from Unreleased work. A source change or passing unit test is not evidence that a released asset contains it.
- Do not publish benchmark percentages, zero-overhead, or zero-I/O guarantees without reproducible measurements. Distinguish reduced idle work from removed polling: trust refresh remains periodic and mDNS loops still run.
cd desktop && npm run licensesgenerates the canonical repository-rootTHIRD_PARTY_NOTICES.md; do not create competing generated notice files.- Inspect the actual fork release assets, architecture, signatures, and
notarization rather than inheriting upstream release claims. The current fork
release is 0.8.0, macOS arm64, unsigned and unnotarized; it is not an
NVIDIA-certified distribution. Public packaging does not grant NVIDIA signing
credentials, notarization, or privileged-helper approval. Downloading with
curlgrants none of these either. - Local-only evaluation payload capture requires explicit user consent, enforces loopback-only exporter transport, truncates payloads to 16 KB, allowlists headers, and records no spans on cluster serving nodes. See Architecture and SECURITY.md before changing telemetry.
Windows, Linux, and macOS, on x64 and arm64. Engine support is narrower: the bundled oMLX engine manifest supports macOS arm64 only.
- Overview — what PAIR does and the vocabulary the other documents assume
- Developer Guide — where the code lives and how a change travels through the layers
- Architecture — process model, discovery, routing, and trust boundaries
- Building and Running — prerequisites, source builds, and running the services without the desktop application
- CONTRIBUTING.md — policy, pull requests, and review
- SECURITY.md — what PAIR does and does not defend against
- Services and Desktop — component references, each indexing its own documents
README.md indexes the full documentation set in reading order.