Skip to content

Add editable engine launch settings with rollback on failure - #48

Open
JaredP94 wants to merge 10 commits into
mainfrom
feat/engine-launch-settings
Open

JaredP94 wants to merge 10 commits into
mainfrom
feat/engine-launch-settings

Conversation

@JaredP94

@JaredP94 JaredP94 commented Nov 4, 2026 •

Copy link
Copy Markdown
Owner

Description

Add engine launch settings on this device and verified paired devices. Users can edit server/proxy ports and literal launch arguments/environment assignments, validate before saving, confirm only required engine restarts, follow apply progress, and see one safe failure message. Concurrent edits reload saved settings rather than overwrite them. oMLX exposes only its proxy port; server settings stay in the oMLX app.

Port and adapt the launch-settings work from NVIDIA/Personal-AI-Router@0df32b0, authored by Noah (Terve) Tervalon. The implementation uses the fork's existing components and three separate proxy workers.

Scope

  • Go: node-owned get/preview/apply settings coordinator; private revisioned intent/outcome persistence and interrupted-operation recovery; engine lifecycle, proxy rebind and advertisement coordination; literal launch-text validation; verified-directory and pinned-mTLS peer access; single safe validation errors and launch-value log redaction. See services/nvpair-ui-broker/ENGINE_SETTINGS.md.
  • Desktop: typed renderer API, epoch/sequence-aware snapshot store and Settings panel; local/verified-peer eligibility; preview errors, restart confirmation, progress/failure and revision-conflict handling; oMLX read-only server note and hidden launch editor.
  • Compensating rollback (c322a84): if saving, the proxy rebind, engine start or readiness fails after the engine stopped, the previous configuration, proxy port, listening scope and running state are restored under the operation lock, and the failure says so. If restoration itself fails, the engine stays stopped and is never left listening more widely than before. Persistence is atomic. An additive internal rollback relay restores only the broker-captured previous proxy port; public request/result fields are unchanged.
  • Replace the superseded port-editing components with the Settings panel, without a UI fallback. Keep narrow service port commands intact.
  • Update desktop usage/API documentation and isolated regression tests.
  • Retain the existing unreleased additive versions: broker 2.5.0 and engine-manager 1.4.0 (already minor-bumped relative to v0.8.0). Desktop-only work changes no service binary or release version.

Validation

Go, on macOS arm64 in isolated worktrees:

  • go test -race ./... and go test -count=1 ./... in services/nvpair-engine-manager and services/nvpair-ui-broker, plus every other touched module — passed.
  • go vet ./... with GOOS=darwin, linux and windows — passed; gofmt -l clean.
  • TestSettingsApplyRestoresPreviousState/{persist,rebind,start,readiness,rollback} failed on cb6452e and pass with the rollback fix.

Desktop checks on macOS arm64, Node v26.7.0 / npm 11.19.0, from an isolated worktree with its own npm ci --no-audit --no-fund:

  • cd desktop && npm run -s typecheck — passed.
  • cd desktop && npm run -s lint — passed, zero warnings.
  • cd desktop && npm run -s test:unit — passed, 892 tests in 89 files.
  • cd desktop && npm run -s dead-code:check — passed.
  • cd desktop && npm run -s service-contracts:check — passed.
  • node scripts/spdx-headers.mjs — zero missing headers.
  • git diff --check — passed.
  • cd desktop && npm run -s build:renderer — passed.
  • New progress-cover, delayed-preview/disconnect, and reload-overwriting-new-edits regressions were observed failing before their fixes and passing afterward. Panel coverage includes loading/editing, preview errors, restart confirmation, apply progress/failure, revision conflicts, oMLX restrictions, preview/manual exclusion and verified peers.

No live engine installation/start, Electron end-to-end interaction, or real paired-device runtime was exercised. PR CI covers the cross-process suite on Linux and macOS.

Risk

Settings changes can restart an engine or rebind its proxy; the owning Go node coordinates those operations. Unsupported formats/devices and disconnected devices remain read-only. Revisions guard concurrent edits, and launch values remain private configuration displayed only in their editor. Live multi-device behavior and platform-specific engine launches still need runtime validation.

Checklist

  • I have read the Contributing Guidelines.
  • Every commit is signed off (git commit -s), certifying the Developer Certificate of Origin.
  • New or existing tests cover the change.
  • Relevant documentation is updated.
  • I checked the diff, changed filenames, and commit messages for credentials, private data, internal URLs, internal issue identifiers, and generated artifacts.
  • I recorded the validation commands and results above.
  • I bumped any affected component in services/versions.json, and described user-visible changes above so they reach the release notes. Existing appropriate unreleased minor bumps are retained as explained above.

JaredP94 and others added 5 commits November 4, 2026 20:03
Port the launch-settings service contract from NVIDIA/Personal-AI-Router@0df32b0. Validate literal engine arguments and environment assignments in Go, preserve private per-platform overrides, and coordinate revisioned persistence, engine lifecycle and proxy rebind through the broker. Keep the fork's three proxy workers and their existing set-port protocol.

Reject managed bind, port and proxy-wiring controls in launch text. Gate remote settings on verified peer inventory and pinned cluster mTLS. Extend the existing permission-read correlation path with method demultiplexing, retain live LAN-bind permission checks, and keep oMLX's app launch unchanged with only its proxy port editable. Redact settings frames and exclude custom vendor output from logged startup failures.

Add the typed Electron relay and regenerate service contracts without changing the settings panel. Go suites, native/Linux/Windows vet, targeted race tests, desktop gates and SPDX checks pass. Relay, authoritative-validation and diagnostic regressions were observed failing without their fixes. Retain the existing unreleased minor versions for engine-manager and broker, already bumped appropriately from v0.8.0.

Co-authored-by: Noah (Terve) Tervalon <ntervalon@nvidia.com>
Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
Adapt the settings panel and snapshot store from NVIDIA/Personal-AI-Router@0df32b0 to the fork's node-owned settings contract. Let local and verified paired devices preview ports and literal launch options before applying, confirm only required engine restarts, and render operation progress and one failure message. Reload revision conflicts instead of overwriting another editor's choices.

Keep oMLX's server settings read-only and expose only its proxy port. Replace the superseded port editor without a fallback, keep launch values inside their editor, and cover panel states, remote eligibility, snapshot ordering and delayed-response races with isolated desktop tests. Document the workflow and typed renderer API.

Co-authored-by: Noah (Terve) Tervalon <ntervalon@nvidia.com>
Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
Verify that a failed apply renders the owning node's restoration outcome once while retaining the revisioned desired settings for a retry. Match failed snapshot fixtures with failed receipts so the tests exercise the authoritative operation contract.

Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
A delayed refresh could overwrite input entered after the refresh began. Correlate the draft reset with its editor generation so newer edits survive, while the snapshot store still records the authority's latest revision. Add a regression observed failing before the guard and passing afterward.

Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
Failed launch settings applies stopped the previous engine but retained the new configuration after persistence, proxy rebind, startup or readiness errors. Compensate under the same operation lock: restore the exact atomic override, previous proxy port and running state, preserving the effective listening address and a cancellation-independent recovery deadline.

Keep failures in the existing receipt and snapshot fields, report failed restoration safely, and reconcile previous component settings before automatic startup without rewriting failed receipts. Replace failure-retention expectations with restoration regressions for every failure stage, rollback failure, cancellation and bind scope. The failure-stage tests were also run against cb6452e and failed as expected.

Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
@JaredP94 JaredP94 changed the title Add node-owned engine launch settings with safe rollback Add editable engine launch settings with rollback on failure Nov 4, 2026
@JaredP94
JaredP94 marked this pull request as ready for review November 4, 2026 19:11
Startup ignored failed reconciliation and interrupted operations discarded their recovery errors. Pass per-engine exclusions to enabled-engine restoration, withdraw unsafe advertisements and proxy destinations, and report value-free recovery errors through the existing errors channel. Keep saved ON/OFF intent intact and let successfully reconciled engines restore independently.

Retry reconciliation on startup, manager respawn and explicit engine start or restart, including requests arriving before startup recovery. Block all three settings-managed engines when their journal cannot be read. Preserve failed receipts and document the internal restoration gate and retry policy.

The startup-caller regression fails for both failed and interrupted operations on c322a84. Cover independent engine recovery, explicit retries, unavailable managers, unreadable journals, result-save failures and saved enabled intent. Broker and manager race suites, fresh Go tests, cross-platform vet, all desktop checks, gofmt and SPDX checks pass.

Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
Apply Prettier to the engine settings state tests to remove the branch's 19 formatting warnings without changing assertions. Desktop lint now reports zero warnings; typecheck, all 892 unit tests, dead-code and service-contract checks pass.

Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
A paired device could bypass the broker-only start check and launch an engine whose settings had not reconciled. Require broker authorization at Executor.doStart, the common launch and adoption chokepoint, rather than patching only the remote handler. Missing authority, failed reconciliation and revoked or wrong-engine grants fail closed with the existing value-free recovery error.

Keep the journal and proxy transaction authoritative in the broker, not a second recovery implementation in engine-manager. Ordinary operations retry recovery through prepare-start before taking the engine operation lock; authorize-start checks the engine-scoped reconciliation generation at doStart. Configure acceptance and failed recovery revoke ordinary grants. The active broker configure token authorizes both its candidate and rollback restart using only the relay-state mutex, so neither can recursively recover or deadlock against settingsApplyMu, engineConfigMu or the engine operation lock. Legacy broker lifecycle relays release settings locks across worker calls and refresh snapshots afterward. Unit executors now explicitly provide test-only broker authority; production has no missing-parent fallback.

Production start caller inventory: StartWith is called by Executor.Start, Manager.runOp's start closure (engine:start and engine:install with start=true), controlServer.handleStart (the target of engine:remote-start), and controlServer.handleInstall (remote install autostart). Manager.runOp also calls Restart; Manager.runSetPort calls SetPort; Action calls restartAfterAction; RestoreEnabled calls restoreEnabled. Direct doStart callers are StartWith, Restart, restoreEnabled, restartAfterAction, SetPort's new-port restart and persistence-failure old-port restart, and ConfigureLaunch's candidate restart and rollback restart. All of them pass the same doStart authorization check.

The broker has no direct Go Start or StartWith call. relayToEngineNow forwards local start/restart, remote-start and legacy lifecycle requests. restoreEnabledEngines requests saved-ON restoration from initial startup through restoreEnabledEnginesAfterPortGate/runEngineAvailabilityAfterPortGates and from spawnEngineMgr on worker respawn. runSettingsOperationLocked requests ConfigureLaunch for Apply, recovery and handleSettingsPortRPC's server/proxy port changes. Legacy internal engine:set-port calls in prepareManagedLMStudioFacadeWithPortCheck and reconcileProxyPortOnReady (including its rollback) also reach the guarded SetPort restart. No client configure-launch request is allowed through.

TestRecoveryGateBlocksLocalAndRemoteStart covers failed and successful recovery for both entry points. On unmodified 3e9f700 with only the final test file added, go test -count=1 -run '^TestRecoveryGateBlocksLocalAndRemoteStart$/remote=true$' ./... fails with 'unreconciled engine started'; it passes with this fix. TestRecoveryGateAllowsApplyRollbackRestart exercises real compensation. TestRecoveryGateCoversLifecycleStarts, TestRecoveryGateRejectsMissingBrokerAndRevokedGrant and TestStartChokepointRefusesMissingAuthorization cover the other worker paths and fail-closed boundary. Broker tests TestStartGateRetriesRecoveryAndWithdrawsAdvertisement, TestConfigureRevokesStartGrantAndAuthorizesRollbackWithoutSettingsLocks and TestEngineStartGateRelay cover failed/interrupted/unreadable journals, stale grants, engine isolation and lock-safe reverse relay authorization.

Validation: fresh go test -race -count=1 ./... in both affected modules; go vet ./... for darwin, linux and windows in both; gofmt; git diff --check; SPDX (1344 checked, zero missing); desktop npm ci --no-audit --no-fund and npm run -s service-contracts:check all pass. Existing opt-in live tests remain skipped; no live engines or cross-process suite were run. Document the internal gate in ENGINE_SETTINGS.md and the worker README. Keep versions unchanged as requested by the maintainer.
Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
Managed Ollama reconciliation held engineConfigMu while SetPort asked the broker to prepare-start, so the reverse recovery relay deadlocked until both the move and rollback hit their five-second deadlines. Snapshot the journal, ownership plan and worker handles, release the lock for worker I/O, then revalidate before consuming any result. Changed state is reconciled from current authoritative configuration rather than an old candidate.

The broker lock audit changed every engineConfigMu-held call site in reconcileProxyPortOnReady: the managed backend move and rollback, facade bind, fallback rebind, runningEnginePorts/get-installed lookup and final corrective proxy rebind. Current-state recovery also performs engine:status unlocked. settingsApplyMu is never held on these paths. Audited settings snapshot/preview and advertisement reads, ConfigureLaunch recovery, legacy lifecycle relays, scheduler delivery, discovery and manual-node callbacks: no additional worker request waited on a callback needing its caller's held lock. ConfigureLaunch callbacks need only relay state; ordinary launch preparation precedes the worker operation lock.

Broker-only internal:set-port operations carry a unique ID tied to the captured configuration and worker. On timeout, internal:cancel-set-port cancels and joins that operation on the captured worker before fallback or rollback. Worker managedPortsMu protects registration only and is released before execution or cancellation joins. SetPort rechecks context cancellation and start authorization after acquiring opMu and before persistence, including stopped engines. Unconfirmed cancellation retains the ownership gate even across duplicate ready events. Public engine:set-port remains unchanged; internal protocol documentation and generated contracts are updated.

Regression evidence: copying the final tests onto b674ab1 reproduces a 10.003-second reverse-relay timeout/fallback and canceled/revoked queued moves persisting their port. The fixed real-worker relay completes in 447 ms, excluding compilation. Tests cover timeout/late-reply fencing, a concurrent explicit settings apply, cancellation joins, stale queued grants and unconfirmed-cancel readiness. Isolate the cross-process fixture's broker lifetime from the process-global teardown clock.

Validation: go test -race -count=1 ./... in broker and engine-manager; GOOS=darwin/linux/windows go vet ./... in both; gofmt and SPDX (zero missing); desktop npm ci --no-audit --no-fund, typecheck, lint, 892 unit tests, dead-code:check and service-contracts:check. No service versions changed, as requested.
Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
A delayed unversioned reconciliation bind could arrive after a newer Apply,
and the stale-state retry returned without restoring the accepted proxy port.
Give each engine one port-operation mutex in Broker.portOperations, acquired
before settingsApplyMu and engineConfigMu and retained across worker I/O,
compensation and the final desired-port convergence. Never acquire port-op
from a reverse callback or while holding either settings lock.

Write-site audit: enginesettings.go runSettingsOperationLocked sends
engine:configure-launch under Apply or restoration port-op ownership;
rebindSettingsProxy sends the selected proxy bind as a delegated callback.
engine_settings_read.go rebind/rollback callbacks borrow that registered
parent operation, including the captured previous startup port. The parent
seals callback admission and joins in-flight writes before releasing port-op.

proxyport.go reconcileProxyPortOnReady owns port-op across the facade bind,
fallback vacate, engine move, engine rollback and engine-wins corrective
proxy bind. Its moveManagedOllamaPort writes in managedport.go retain the
existing captured-worker cancellation join and authorization fencing.
reconcileCurrentOllamaPorts takes the same mutex and invokes the in-operation
reconciler without reacquiring it.

lmstudioport.go prepareManagedLMStudioFacadeWithPortCheck owns port-op for
its engine:set-port; readiness owns it across rebindLMStudioProxy and every
fallback/collision bind. broker.go relayToEngineNow owns it across legacy
engine:set-port, engine:start with a port and engine:install with start+port,
including the post-call settings refresh. Port-only UI/TUI RPCs continue
through Apply, which takes the same mutex.

The Ollama/LM Studio/oMLX startup planners own port-op across explicit
settings restoration and all managed/fallback startup-port choices.
spawnProxy, spawnLMStudioProxy and spawnOMLXProxy own it across startup
arguments, worker spawn and handle publication. recoverProxyBindFailure
owns it for reader-dispatched fallback choices; finishOllamaProxyTerminal
and the missing-LM-Studio startup path own it for terminal fallback choices.
recoverEngineSettingsForEngine and the nonblocking start-recovery executor
own it for journal restoration and its configure/rollback writes.

portoperation.go finishPortOperation performs the final corrective proxy
write under the operation's mutex, including early and stale-result exits.
All unversioned proxy writes use proxyport_write.go writeProxyPort and join
the response or worker exit, rather than allowing a local timeout to release
ownership before a late bind. A hung proxy requires manual worker restart.
Convergence failure withdraws availability and revokes start grants. Explicit
accepted proxy settings never get replaced by a stray ready observation.

prepare-start uses only short engineConfigMu sections. Recovery runs in a
separate executor with nonblocking port-op admission; an active operation
fails preparation closed instead of waiting for its own callback. Rebind,
rollback and authorize-start never acquire port-op. ENGINE_SETTINGS.md records
the lock order, delegation, joining and final convergence rules.

TestPortOperationSerializesDelayedReconcileWithApply fails on e45aebf with
the old bind persisted on 11434 after Apply accepted another port. With the
fix, Apply queues behind the old write (reloading a legitimately advanced
baseline revision if needed). TestPortOperationCorrectsStaleReadyToAcceptedPort
checks final proxy and persisted choices for all three engines;
TestPrepareStartCompletesWhilePortOperationHeld checks healthy and fail-closed
reverse callbacks. TestPortOperationConvergenceFailureBlocksStart checks the
failure gate and revocation, including initial recovery-map allocation.
All six e45aebf tests remain, together with b674ab1's recovery-gate tests.

Validation: go test -race -count=1 ./... in broker and engine-manager;
-race -count=20 on broker concurrency/recovery/gate tests and engine-manager
fencing/gate tests; GOOS=darwin/linux/windows go vet ./... in both modules;
gofmt and git diff --check; SPDX zero missing; own desktop npm ci
--no-audit --no-fund and service-contracts:check. An independent read-only
review found no blocking issues. No wire contracts or versions changed.

Signed-off-by: Jared Ping <13906915+JaredP94@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant