Skip to content
Open
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
2 changes: 1 addition & 1 deletion .cursor/rules/proxy-inference-routing.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ SPDX-License-Identifier: Apache-2.0
# Proxy and Inference Routing

Routing is owned by `nvpair-proxy` (one process hosting a facade per engine,
addressed by clients as `ollama-proxy:` / `lmstudio-proxy:`),
addressed by clients as `ollama-proxy:` / `lmstudio-proxy:` / `llamacpp-proxy:`),
`nvpair-job-scheduler`, and `nvpair-ui-broker`.

For model-bearing inference, each facade first filters a request-local discovery
Expand Down
2 changes: 1 addition & 1 deletion .cursor/rules/system-architecture.mdc
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Broker-owned workers:
- `nvpair-proxy`, one process hosting a facade per enabled engine. It starts
with no engine and no listener; the broker sends a `facade/enable` per engine
carrying that engine's port. Clients still address each facade as
`ollama-proxy:` / `lmstudio-proxy:`, and one supervisor covers them all, so a
`ollama-proxy:` / `lmstudio-proxy:` / `llamacpp-proxy:`, and one supervisor covers them all, so a
crash is reported against `nvpair-proxy` and restarts every facade together;
- `nvpair-node-scanner`;
- `nvpair-node-info`;
Expand Down
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

# Windows-built packages must ship executable Unix shell scripts.
*.sh text eol=lf
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ one, and both report live GPU and memory use throughout.
| **Architectures** | x64 and arm64 on all three. Windows on ARM is experimental. |
| **Installers** | Windows `.exe`; Linux `.deb`; macOS `.dmg`. On other Linux distributions, [build from source](docs/building.mdx). |
| **Mixing nodes** | Windows, Linux, and macOS nodes can all be paired with each other |
| **Inference engines** | Ollama and LM Studio |
| **Inference engines** | Ollama, LM Studio and managed llama.cpp |

**PAIR running on a machine does not mean an engine will.** PAIR itself runs on
any supported Windows, Linux, or macOS machine. Each engine sets its own requirements
Expand All @@ -49,6 +49,11 @@ before assuming a node can serve a model. A node only becomes a candidate for a
request once it is actually running a compatible engine, and PAIR prefers the
nodes it already knows hold the model.

Managed llama.cpp provides an official CPU app for Intel Macs and confirmed
non-NVIDIA Windows ARM hardware. NVIDIA Windows ARM remains CUDA-required;
a failed driver or hardware query does not silently select CPU. Apple Silicon
uses the official Metal app. See the [managed engine requirements and policies](services/nvpair-engine-manager/README.md#managed-llama-app).

## Quick start

Download a released build and use the desktop application. That is the path we
Expand Down
20 changes: 14 additions & 6 deletions desktop/docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ subscribes to broker relays after `app:ready`, and converts backend responses
into stable UI contracts.

Electron reports the service connected after broker `app:ready`. The
broker-owned Ollama and LM Studio proxies remain asynchronous capabilities; a
broker-owned Ollama, LM Studio, and llama.cpp proxies remain asynchronous capabilities; a
late or failed proxy does not misreport the broker startup as failed. If
`app:ready` does not arrive within the startup deadline, Overview opens Settings

Expand Down Expand Up @@ -227,16 +227,18 @@ ordinary environment assignments can be edited locally or by a pinned peer.
authoritative settings operation rather than forwarding to the engine manager,
so both entry points validate, restart, and persist identically.

The Ollama and LM Studio proxies are cluster-aware. For model-bearing inference,
each proxy first keeps only nodes whose per-engine discovery inventory advertises
the requested model. Empty and non-matching inventories are excluded; an empty
owner set returns a local `502`. Routing precedence within the eligible set is:
The Ollama, LM Studio, and llama.cpp proxies are cluster-aware. For
model-bearing inference, each proxy first keeps only nodes whose per-engine
discovery inventory advertises the requested model. Empty and non-matching
inventories are excluded; an
empty owner set returns a local `502`. Routing precedence within the eligible set
is:

1. a user-selected manual node;
2. the priority list emitted by `nvpair-job-scheduler`;
3. the proxy's deterministic default ordering.

The scheduler combines total pending (queued and running) workload across both
The scheduler combines total pending (queued and running) workload across all
engines with a smoothed 0–3 pressure derived from the busiest GPU. Missing,
invalid, or older-than-10-second telemetry has neutral pressure. It emits the
order, pending count, and pressure, reranking on meaningful workload, discovery,
Expand Down Expand Up @@ -298,6 +300,12 @@ cannot yet be reported are centralized in
`src/shared/constants/modular-runtime.ts`.

- Ollama-compatible clients use the proxy port reported by the broker.
- llama.cpp clients use the OpenAI-compatible proxy at `http://127.0.0.1:8080/v1`
by default. Engine Manager installs the official `llama` app, serves its
managed router on the separately reported engine port (default `8081`), and
owns model download, load/unload and removal. The proxy routes to the models
the engine advertises; a cold model loads on its first request. Existing external listeners remain externally owned;
their presence does not authorize PAIR to mutate them.
- Cluster pairing currently uses port `14321`.
- Node telemetry is read from `/v1/node-info` at each discovered node's
advertised port.
Expand Down
1 change: 1 addition & 0 deletions desktop/docs/service-contract-exceptions.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"engine:restore-enabled": "Broker-internal startup restoration. nvpair-ui-broker emits engine:restore-enabled directly to its supervised engine-manager after the managed Ollama port gate and on manager respawn; it is not a renderer/UI notification.",
"ollama-proxy:ready": "Consumed, not missing: the broker relays it and normalizeBrokerProxy (modular-supervisor.ts) strips the `ollama-proxy:` prefix, so the bridge handles the de-prefixed `ready` (sets proxyPort). The literal `ollama-proxy:ready` is intentionally absent from our TS — extractor limitation, not a gap.",
"lmstudio-proxy:ready": "Consumed, not missing: the LM Studio counterpart of ollama-proxy:ready, de-prefixed by the same normalizeBrokerProxy loop. It only became visible to the checker when METHOD_RE started accepting hyphens in a namespace segment; before that the whole lmstudio-proxy:* surface was silently unmatched.",
"llamacpp-proxy:ready": "Consumed, not missing: the llama.cpp counterpart of the two above, de-prefixed by the same normalizeBrokerProxy loop, which iterates PROXY_NODE_SOURCES and so covers every engine without naming one. llama.cpp ships no proxy binary — one nvpair-proxy process hosts a facade per engine — but `llamacpp-proxy` remains the facade's relay namespace, which is what the broker emits and this entry names.",
"node/selection-changed": "Automatic routing has no selected-node UI, so PAIR deliberately does not consume proxy selection changes.",
"proxy/request": "Per-request proxy telemetry is not rendered; workload lifecycle uses the broker workloads stream.",
"proxy/request-started": "Per-request proxy telemetry is not rendered; see proxy/request.",
Expand Down
10 changes: 10 additions & 0 deletions desktop/docs/services-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@
- ⚠️ nvpair-ui-broker → engine:set-reserved-port
- ⚠️ nvpair-ui-broker → engine:unsubscribe
- ⚠️ nvpair-ui-broker → internal:set-reserved-port
- ⚠️ nvpair-ui-broker → llamacpp-proxy:get-status
- ⚠️ nvpair-ui-broker → llamacpp-proxy:set-port
- ⚠️ nvpair-ui-broker → llamacpp-proxy:unsubscribe
- ⚠️ nvpair-ui-broker → lmstudio-proxy:get-status
- ⚠️ nvpair-ui-broker → lmstudio-proxy:set-port
- ⚠️ nvpair-ui-broker → lmstudio-proxy:unsubscribe
Expand Down Expand Up @@ -109,6 +112,7 @@
| `engine:prepare-shutdown` | request (we call) | ✅ yes |
| `engine:preview-launch` | request (we call) | ⚠️ not called |
| `engine:remote-apply-settings` | request (we call) | ⚠️ not called |
| `engine:remote-cancel-pull` | request (we call) | ✅ yes |
| `engine:remote-delete-model` | request (we call) | ✅ yes |
| `engine:remote-get-installed` | request (we call) | ✅ yes |
| `engine:remote-get-settings` | request (we call) | ⚠️ not called |
Expand Down Expand Up @@ -233,6 +237,7 @@
| `cluster:identity-changed` | request (we call) | ✅ yes |
| `cluster:invite-received` | request (we call) | ✅ yes |
| `engine:install-progress` | request (we call) | ✅ yes |
| `engine:models-changed` | request (we call) | ✅ yes |
| `engine:pull-progress` | request (we call) | ✅ yes |
| `engine:state-changed` | request (we call) | ✅ yes |
| `error` | request (we call) | ✅ yes |
Expand All @@ -253,6 +258,7 @@
| `errors:clear` | notification (we consume) | ✅ yes |
| `errors:report` | notification (we consume) | ✅ yes |
| `errors:update` | notification (we consume) | ✅ yes |
| `llamacpp-proxy:ready` | notification (we consume) | ➖ ignored |
| `lmstudio-proxy:ready` | notification (we consume) | ➖ ignored |
| `ollama-proxy:ready` | notification (we consume) | ➖ ignored |
| `workloads:upsert` | notification (we consume) | ✅ yes |
Expand All @@ -273,6 +279,10 @@
| `engine:unsubscribe` | request (we call) | ⚠️ not called |
| `errors:get-initial` | request (we call) | ✅ yes |
| `internal:set-reserved-port` | request (we call) | ⚠️ not called |
| `llamacpp-proxy:get-status` | request (we call) | ⚠️ not called |
| `llamacpp-proxy:set-port` | request (we call) | ⚠️ not called |
| `llamacpp-proxy:subscribe` | request (we call) | ✅ yes |
| `llamacpp-proxy:unsubscribe` | request (we call) | ⚠️ not called |
| `lmstudio-proxy:get-status` | request (we call) | ⚠️ not called |
| `lmstudio-proxy:set-port` | request (we call) | ⚠️ not called |
| `lmstudio-proxy:subscribe` | request (we call) | ✅ yes |
Expand Down
18 changes: 12 additions & 6 deletions desktop/docs/services-backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ engine, workload, cluster, and error relays. The bridge then emits renderer push
events from backend notifications.

Connector readiness follows the broker contract: `app:ready` establishes the
service connection, while Ollama and LM Studio proxy readiness remains an
asynchronous capability signal. Personal AI Router waits up to the canonical
service connection, while Ollama, LM Studio, and llama.cpp proxy readiness
remains an asynchronous capability signal. Personal AI Router waits up to the canonical
startup deadline in `src/shared/constants/modular-runtime.ts` for
`app:ready`; an outright failure or stalled broker startup is surfaced in
Settings > Service with retry and log access. If a stalled broker reports ready
Expand All @@ -116,7 +116,7 @@ reserved for inference clients.
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| `app:ready` | Complete broker startup and refresh snapshots | `state:request-refresh` |
| `discovery:nodes-changed` | Replace discovery snapshot and diff nodes | `discovery:nodes-changed`, `nodes:upsert`, `nodes:remove` |
| `ollama-proxy:ready` / `lmstudio-proxy:ready` | Record engine proxy port | `engines:state-changed` |
| `ollama-proxy:ready` / `lmstudio-proxy:ready` / `llamacpp-proxy:ready` | Record engine proxy port | `engines:state-changed` |
| proxy `node/*` | Update per-engine node presence; the advertised port is the peer's promoted proxy port (not the engine's private loopback port) | node and engine pushes |
| `engine:ready` / `engine:state-changed` | Update engine facts and models | `engines:state-changed` |
| `engine:settings-changed` | Validate and republish the owning node's settings snapshot | `engines:settings-changed` |
Expand All @@ -128,7 +128,7 @@ reserved for inference clients.
| `nodes:changed` | Replace membership snapshot | `nodes:changed` |
| `workloads:upsert` / `workloads:remove` | Update workload catalog | workload pushes |

`nvpair-job-scheduler` combines queued and running work across both engines with
`nvpair-job-scheduler` combines queued and running work across all engines with
a smoothed 0–3 pressure from the busiest GPU. Invalid, missing, or
older-than-10-second telemetry receives neutral pressure. It emits
`schedule:priority` with order, pending count, and pressure; the broker applies
Expand All @@ -150,8 +150,14 @@ waiting for authoritative state. Pending state clears on matching engine state,
progress, or error pushes.

Local engine operations include install, start, stop, uninstall, update, port
changes, and model actions. Remote cluster operations use the engine manager's
remote control surface where supported.
changes, and model actions. Engine Manager owns the official llama app install,
router process, managed cache, downloads, load/unload and delete. llama.cpp has
no managed update: the bridge refuses `update` for it rather than substituting
an uninstall and reinstall. The desktop uses reported install support and
ownership; external runtimes remain read-only. Downloaded cache entries remain
distinct from runtime residency. The app endpoint is `http://127.0.0.1:8080/v1`;
a downloaded model loads on the first request that names it. Remote cluster
operations use the engine manager's remote control surface where supported.

### Engine settings

Expand Down
49 changes: 42 additions & 7 deletions desktop/docs/services-parity.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ history.
| Manual nodes | Complete with local persistence | Broker owns probing and proxy registration; Electron persists entries for replay |
| Ollama routing | Complete | Broker relay and backend scheduler drive proxy routing |
| LM Studio routing | Complete | Parallel broker relay and scheduler path |
| llama.cpp routing | Source integration; native validation pending | `nvpair-proxy` facade on llama.cpp's own `8080`, relayed as `llamacpp-proxy:` |
| Local engine lifecycle | Complete | Install, start, stop, uninstall, update, and port configuration |
| Remote engine lifecycle | Partial | Remote install, start, stop, status, and model pull are supported |
| Engine models | Partial | Core list, pull, load, unload, and supported delete actions are wired |
Expand Down Expand Up @@ -97,23 +98,50 @@ Manual nodes use the broker's `node/add`, `node/remove`, and `nodes/list`
surface. Electron persists user entries and replays them after broker startup so
they survive worker restarts.

### Workload display

The desktop displays workload snapshots and live updates, retaining origin,
engine, proxy run and request identity. Execution labels and connection lines
use the reported destination, not the request origin. Workload cancellation is
not exposed by the desktop API or UI. Engine lifecycle and model-download
cancellation are separate controls and remain supported.

### Multi-node UI acceptance

Engine integration must preserve each participating desktop's view of the
cluster, not only the request origin's view. During the same bounded inference
run, verify every available participating desktop independently:

- Cluster and member UUIDs, engine availability and loaded-model ownership agree
after discovery converges; offline members do not look live or routable.
- New workloads agree by their full `(originatedFrom, engine, runId, id)`
identity on model, destination and terminal state. Record propagation delay;
do not require identical historical catalog totals or instantaneous equality
during a state transition.
- Capture each desktop's original Performance view during actual work. A remote
backend response or the origin's aggregate UI does not prove another native UI.

Record unavailable or untested desktop cells explicitly. This checklist states
the acceptance requirement; it does not assert that every platform has passed.

## Routing and inference

Both text-engine facades are broker-owned and cluster-aware. They live in one
Every text-engine facade is broker-owned and cluster-aware. They live in one
`nvpair-proxy` process, each enabled after spawn on its own port, and each
serves its engine's dialect:

- the Ollama facade serves the Ollama-compatible surface;
- the LM Studio facade serves the LM Studio/OpenAI-compatible surface.
- the LM Studio facade serves the LM Studio/OpenAI-compatible surface;
- the llama.cpp facade serves its OpenAI-compatible surface on `8080`.

Sharing a process is what lets them share the burst reservations the scheduler
depends on: two facades bursting at once compete for the same node's GPU, so a
dispatch through either has to be visible to the other.
depends on: facades bursting at once compete for the same node's GPU, so a
dispatch through any of them has to be visible to the others.

Routing precedence is manual selection, scheduler priority, then deterministic
proxy ordering. Personal AI Router leaves proxies in automatic mode.

`nvpair-job-scheduler` combines total queued and running workload across both
`nvpair-job-scheduler` combines total queued and running workload across all
engines with a smoothed 0–3 GPU-pressure signal. The backend scanner and manual
node worker provide maximum-GPU utilization, while invalid, missing, or
older-than-10-second samples receive neutral pressure. The scheduler emits order,
Expand Down Expand Up @@ -159,6 +187,10 @@ Personal AI Router supports local:
- desired-state restoration across app restarts;
- engine and model progress.

Managed update covers Ollama and LM Studio. llama.cpp has no managed update;
the bridge refuses `update` for it instead of substituting an uninstall and
reinstall.

Before shutdown, Personal AI Router calls `engine:prepare-shutdown`. This stops managed engine
processes without changing the persisted desired state; the broker restores
enabled engines on the next launch. The broker also self-initiates
Expand Down Expand Up @@ -209,9 +241,12 @@ Personal AI Router uses:
- `list_models`;
- `pull_model`;
- Ollama `run_model`, `unload_model` (`keep_alive: 0`), and `delete_model`;
- LM Studio `load_model`, `unload_model`, and `delete_model` (`remove_path`).
- LM Studio `load_model`, `unload_model`, and `delete_model` (`remove_path`);
- llama.cpp `load_model`, `unload_model` (router `/models/load` and
`/models/unload`, settled on observed residency), `delete_model`,
`pull_model` and `import_model` (managed cache builtin), and `cancel_pull`.

Both engines expose Load, Eject, and Delete in the model manager when the
All three engines expose Load, Eject, and Delete in the model manager when the
backend action exists. Keep-alive / expiry controls remain unsupported.

LM Studio's `delete_model` declares `restart_after`, so the engine manager
Expand Down
Loading
Loading