Repository navigation
Conversation
stevbev
force-pushed
the
feature/custom-engine
branch
2 times, most recently
from
November 7, 2026 18:49
deee79c to
54b2ae6
Compare
macOS 14+ silently denies an app's mDNS and local-network traffic unless the bundle declares it: peers never appear and the log fills with 'sendto: no route to host' with no prompt. Add NSLocalNetworkUsageDescription and NSBonjourServices (_nvpair-node._tcp) to the macOS extendInfo, and document the Local Network permission in the getting-started pairing steps and the troubleshooting guide (including the unsigned-build workaround). Signed-off-by: Steven Beverly <thestevbev@gmail.com>
Register a fourth engine kind, Custom, in the shared engine registry (nvpair-shared/engines) and the mDNS TXT key set (nvpair-shared/noderec, key 'cu'). Custom is declared Unmanaged with no engine port base: PAIR never installs, starts, stops, or relocates what sits behind it, and launcher-shaped consumers must skip unmanaged kinds. nvpair-proxy grows the custom-proxy facade for it. The facade fronts user-configured OpenAI-compatible endpoints instead of a local process: the endpoint set (URL, optional API key, declared models) arrives as a wholesale replacement over the broker's control channel, ingress routes a model request to the first endpoint that declares it and refuses to guess between endpoints when no declared owner answers, and forwarding is strictly terminal. The endpoint's key is injected only on the owning node's last hop; the caller's own Authorization never leaves the trusted hop. Peer-addressed Custom entries stay internal routing bookkeeping and never surface on the outward node wire. Updates the proxy README and specification, and the architecture rule file that records the facade and key policy. Signed-off-by: Steven Beverly <thestevbev@gmail.com>
…oss nodes The configuration plane behind the Custom facade, per node: - nvpair-ui-broker owns the endpoint set in a 0600 configs file (the only sanctioned home for an endpoint API key besides in-memory request plumbing). Port is a mandatory explicit field; an endpoint that would forward to one of this node's own facade addresses is refused at add. The Custom facade's own port is configurable and reserved away from sibling proxies, engines, and the node's own endpoints; unmanaged engine kinds get a clear error on engine-port edits instead of a relocation attempt. - nvpair-manual-nodes becomes the probe cache for that configuration: one /v/models probe per endpoint per cycle, manual-node failure semantics, no persistence across restarts. - nvpair-engine-manager merges the broker-pushed custom model union into every ModelsResult under the custom engine; key presence is the published semantic, so clearing the set stops peer attribution. The node-scanner copies it wholesale, pinned by a regression test. - Cross-node management: a peer-addressed custom/list/refresh/add/remove/ get-port/set-port is relayed over the pinned mTLS control channel through engine-manager's new custom carrier to the owner node, where it runs through the same local plane. The API key rides only an add request and never returns in a reply (views carry keySet) or a log. - Advertisement follows routability, not configuration: the cu= TXT key, the model inventory, and the ingress backend set are gated on facade readiness, so peers stop routing when the endpoints go unhealthy. collectlogs learns the custom engine token so it is never swept as a hostname or model, and the credential file stays out of bundles by the log allow-list. READMEs, service specs, and the architecture doc describe the new plane. Signed-off-by: Steven Beverly <thestevbev@gmail.com>
Teaches the whole desktop stack the fourth engine kind: - Types/contract: `custom` joins EngineTypes, EnabledEngineTypes, and the proxy-engine identity (custom-proxy source); new `custom:list/refresh/ add/remove/get-port/set-port` invoke channels with a CustomEndpointInfo that carries keySet, never a key. A new MANIFEST_ENGINE_TYPES keeps the compact engine surfaces (tray Engines n/m, node toggle row) counting only installable engines. - Service bridge: custom channels execute locally or relay to a paired peer through the broker with a dedicated 45 s relay budget; a self-addressed nodeId runs the local path with addressing stripped. Reply entries are rebuilt from known fields only, so an apiKey a malformed reply smuggles cannot cross into the renderer, and a peer-addressed read failure goes read-only rather than pretending the peer has no endpoints. redact-log gains apiKey/api_key as a second layer keeping keys out of every log sink. - UI: a per-node Custom endpoints section (add/edit form with explicit port and key-clear edit, health and model chips per card, editable proxy port field), wired through pairApi; welcome and engine- capability records learn Custom with no install/model operations. - Tests cover the endpoint model, the UI section, and the updated engine - identity and demo-lifecycle expectations; the generated service API doc, contract exceptions, and parity notes are regenerated. Signed-off-by: Steven Beverly <thestevbev@gmail.com>
A node that fronts Custom endpoints lists them, read-only, between the hardware readout and the Engines/Models panes: name, address, `down` when the last probe failed, `[key]` where a key is configured, plus the shared custom-proxy port — movable with `c` on this machine only while the facade is listening. Configuration itself stays with the owning node's desktop app. Model operations against an unmanaged engine kind answer `custom endpoints manage their own models` before either the local or the relay path, and demo runs skip unmanaged engines so the self-check never spends the operator's money on a third-party service. The Jobs tab lists the Custom facade's endpoint line. README and the terminal-interface guide updated. Signed-off-by: Steven Beverly <thestevbev@gmail.com>
Four suites over the real binaries in services/tests: the advertise plane (custom attribution received through the scanner copy and published by a live engine-manager), the end-to-end config-to-routing chain with outage recovery on probe misses, peer-origin routing over real cluster mTLS between two paired nodes (key injection on the owner's last hop, caller Authorization stripped, unpinned peers refused), and remote endpoint editing across the carrier, asserting the API key lands only in the owner's 0600 store and never on an output stream. Signed-off-by: Steven Beverly <thestevbev@gmail.com>
The per-node "Engines (n/m)" line was built from the padded manifest row list: it fabricated an `initializing` placeholder for every manifest engine type, so the denominator was always 3 even for nodes with nothing installed, and it never saw user-configured Custom endpoints. A node running only one custom endpoint read "Engines (0/3)" instead of "Engines (1/1)". Count the engines a node actually has instead: manifest entries the node reported with an installed process state, plus the node's per-endpoint custom:list snapshot (configured = entry, online = healthy verdict). Custom health has no push feed, so the tray pulls that snapshot itself on node-set change, on window focus, and on a 30s cadence while the surface could be visible; a failed peer read keeps the previous snapshot, because unknown is not "no custom engines". Also drop the dead single-engine label branch: the enabled engine list has four kinds, so its condition could never hold. Signed-off-by: Steven Beverly <thestevbev@gmail.com>
Clear the ten prettier warnings the custom-engine commits left in redact-log, ws-channels, custom-endpoints-api, CustomEndpointSection, and their tests. Formatting only; typecheck, unit tests, and a clean `npm run lint` verified after the fix. Signed-off-by: Steven Beverly <thestevbev@gmail.com>
stevbev
force-pushed
the
feature/custom-engine
branch
from
November 10, 2026 02:29
54b2ae6 to
73175cb
Compare
Collaborator
|
I think this idea is something we do want to do in the future, we are thinking of how exactly we want to engineer it though. We'd love effectively a universal adapter and need to take some time to design that internally. When we have ideas about the exact execution we would love to work with you to implement it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
This branch adds Custom — a fourth engine kind that fronts user-configured
external OpenAI-compatible endpoints (vLLM, llama.cpp, mtplx, strata, hosted APIs)
— and makes the cluster-status surfaces report real engine state.
Before this, PAIR only knew about the three installable managed engines. The
menu-bar / tray cluster-status window always rendered "Engines (n/3)": it
fabricated a placeholder for every manifest engine type, so a node with
nothing installed read "0/3", a node running a custom endpoint read "0/3" as
well, and custom engines were invisible everywhere. The custom proxy plane,
its configuration storage, relay, probe, and UI layers are all new in this
branch; the tray line and the TUI node-detail screen now count and list what
a node actually has. It also carries two macOS packaging fixes needed for
multi-node use: the Local Network permission declaration and a monochrome
menu-bar icon.
Release intent
Changelog title
Custom inference engines; truthful engine counts in the cluster-status window
Changelog body
You can now add external OpenAI-compatible endpoints (vLLM, llama.cpp servers, hosted APIs) as Custom engines on any node: add, edit, and remove them from the desktop app, set the shared custom-proxy port, and see health and model chips per endpoint. The TUI lists Custom endpoints on the node detail screen. A Custom endpoint's API key stays on the node that owns it.
Bumps
Bump rationale: broker, proxy, engine-manager, manual-nodes, and tui each ship
new user-visible IPC/HTTP behavior (custom:list/add/remove/refresh,
custom/get-port/set-port, the custom facade and its routing, the custom model
union, the probe source, the TUI section). job-scheduler and node-scanner have
no source change beyond tests; they carry patch because their compiled engines
table / TXT parse set now includes the
customkind (scheduler:get-statusgains a
customentry; node-record TXT gains thecu=key).Resolves
#182
#165
#77
#39
#24
#2
Scope
Included: the Custom engine kind in the shared engine registry and mDNS TXT
key set; the custom-proxy facade (terminal forwarding, per-model routing,
key injection on the owning node's last hop); the broker-owned endpoint
config store (0600 file), the peer relay, and the configurable facade port;
engine-manager's custom model union and carrier; manual-nodes as the probe
cache; the desktop service bridge, management UI, and the tray/TUI count and
listing fixes; generated service docs, specs, and user docs.
Excluded: model operations and install lifecycle for Custom (unmanaged by
design); any push feed for custom health (the tray polls at 30s while
visible); changes to managed-engine behavior beyond skipping unmanaged kinds
in launcher-shaped consumers;
services/versions.json/CHANGELOG.md(automation-owned).
Validation
Environment: 3 macOS arm64 machines; branch tip
6a41a9e+style commit, basedon
develop(039f9fe). 1 Windows 11 amd64 machine running 1.0 release.From
desktop/:npm run typecheck— passed.npm run lint— passed, 0 errors / 0 warnings after applying.npm run test:unit— 51 files / 384 tests passed.npm run dead-code:check— passed.npm run service-contracts:check— passed (doc fresh).node scripts/spdx-headers.mjs— passed.From
services/,go test ./...in the affected modules:shared/engines,shared/noderec,nvpair-ui-broker,nvpair-proxy,nvpair-manual-nodes,nvpair-node-scanner,nvpair-tui,nvpair-job-scheduler— all passed.nvpair-engine-manager— one failure,TestUninstall_TerminatesRunningInstance,which reproduces identically on the
developbase commit on this machine; itis a pre-existing environment-dependent executor test, not introduced here.
The new cross-process suites in
services/tests(advertise, interop, peeringress over mTLS, remote edit) require a paired multi-node harness and are
left to CI.
Manual UAT on macOS with one custom endpoint and no managed engines: the
tray window showed "Engines (1/1)". During a manual LM Studio
install/start/stop/uninstall cycle the same node went 1/2 → 2/2 → 1/2 → 1/1,
confirming installing/stopped states count in the denominator and custom
endpoints persist correctly across it.
Risk
cu=service key anda new emit position. Old peers parse unknown keys through the
forward-compat path; mixed-version behavior is pinned by the new
advertise/interop suites rather than manual reasoning.
addrequest and never returnin replies (views carry
keySet) or in logs (reply entries are rebuilt fromknown fields;
redact-loggainedapiKey/api_key). Keys live only in theowner's 0600 store, and forwarding injects the key only on the owning
node's last hop with the caller's Authorization stripped. Adding an endpoint
that would forward to one of the node's own facade addresses is refused, so
the facade cannot loop on itself.
there set the per-node custom port instead, which is reserved away from
engines, sibling proxies, and the node's own endpoints.
(documented in getting-started and troubleshooting, including the
unsigned-build workaround). A denied grant means no peers, not a crash.
run one-at-a-time, and keep the previous snapshot on a failed read
(unknown ≠ "no custom engines").
Checklist
git commit -s), certifying the Developer Certificate of Origin.services/versions.jsonis written by automation — do not edit it by hand.