Files
kandev/docs/decisions/2026-08-12-validated-managed-runtime-version-selection.md
T

9.0 KiB

ADR-2026-08-12-validated-managed-runtime-version-selection: Validate and Persist Managed Runtime Version Selection

Status: accepted (amended 2026-08-21, 2026-08-24, and by 2026-09-07-activate-managed-runtime-defaults) Date: 2026-08-12 Area: backend, frontend, protocol, workflow Supersedes: ADR-2026-07-26-user-managed-agent-runtime-updates

Amendment: ADR-2026-09-07 limits a selection to one shipped default generation. A changed package or default activates the new Kandev default before runtime consumers start.

Context

Kandev originally invoked managed npm ACP runtimes by an unversioned package name. The Settings action could prepare the current npm latest target, but it could not select an older version. This made recovery impossible when an npm release was only partly published, platform artifacts were missing, or the latest runtime failed ACP initialization. Clearing the matching _npx tree did not help because the next unversioned invocation selected the same broken release.

The recovery surface must not turn into an arbitrary package installer. Package identity, registry location, and ACP arguments are trusted Kandev metadata. Users need to choose among valid versions of that package, validate the candidate, and keep a known-good version across Kandev restarts.

Persisting a selection only after a manual update still leaves every fresh installation and every unselected agent on an unversioned npx <package> command. Removing npm from the launch path would require Kandev-owned or user-installed runtime artifacts and is not a requirement for this iteration. The immediate invariant is narrower: every managed npm ACP command names an exact top-level package version, while npm remains responsible for cache and network behavior.

Decision

Kandev lets an operator select an exact stable version for a built-in managed npm runtime. The backend obtains the version catalogue from npm metadata, validates strict stable SemVer values, and accepts only a version published for the trusted package. Callers cannot provide a package name, registry URL, tag, prerelease, command argument, or shell text.

Kandev prepares the candidate under its exact package@version npm execution key and probes it through ACP before activation. Candidate preparation or probe failure leaves the existing active selection and capability catalogue unchanged. Kandev persists the trusted package identity and candidate as the install-wide active version only after a successful ACP probe, then publishes the candidate capabilities. This ordering makes the persisted active version the last known good version; automatic silent rollback is unnecessary.

Each trusted managed package has an exact default version shipped in Kandev. Command resolution uses the persisted operator selection when its package identity still matches; otherwise it uses the shipped default. Kandev does not copy the default into the database. An installation with no explicit selection therefore follows newer defaults when Kandev upgrades, while an operator selection remains stable until the operator changes or clears it, unless a changed applied default generation resets it before runtime consumers start.

The resulting effective version applies to every Kandev-built ACP command for that managed package, including capability probes, utility prompts, local sessions, containers, and SSH executors. Existing sessions keep their current process. Native binaries, passthrough commands, and separately distributed authentication helpers remain outside this package selection.

The managed package and exact-version override belong only to an agent's ACP command surfaces. They do not replace PassthroughConfig.PassthroughCmd, an interactive authentication helper, or either surface's install recipe. Agents such as Pi can therefore use an ACP adapter package for structured execution and a separately distributed native CLI for terminal passthrough.

The selection is install-wide and per built-in agent. A saved selection applies only while its stored package identity matches the agent's current trusted package metadata; a package change falls back to the replacement package's shipped default. The selection survives backend and browser restarts independently of npm's best-effort cache. If reading a saved selection fails, Kandev fails the new managed launch or probe visibly instead of bypassing the selection. If npm evicts the effective version's cache, npm may prepare that same exact version again when needed.

Settings checks the registry for each managed package when the Agents page asks for update status. The backend caches successful results for a bounded period, returns an explicit unknown state when the registry is unavailable, and never updates a runtime from this read-only check. The UI marks an available newer stable version on the existing update control. Opening the control performs the authoritative preview and retains the validated prepare, probe, and activation flow.

Normal launches retain --prefer-offline because an exact package version removes version-tag drift while preserving npm cache reuse. Explicit previews, manual updates, and the bounded stale-metadata retry use online-preferred metadata. Kandev accepts that a cold or incomplete npm cache can still place the registry on the launch path.

The stale-metadata retry runs cache repair through the agentctl instance that hosts the npm process. This boundary covers local PC, local Docker, and remote SSH runtimes. It preserves the configured registry and the exact selected version. ADR-2026-08-24 owns the cache-repair placement and security rationale.

A weekly repository workflow compares the shipped defaults with each trusted package's stable npm latest tag and opens a reviewable PR for changes. It does not merge automatically. The same centralized version catalogue drives command construction, tests, and the workflow so runtime pins cannot drift across separate constants.

Consequences

Operators can recover from a broken latest release in Settings without shell access or a Kandev restart. Runtime changes are attributable: every managed npm launch uses either a reviewed Kandev default or an explicit validated operator selection.

The backend now owns a small durable selection record and must route it through every managed-runtime command path. It also owns a bounded, process-local update status cache. Version catalogue lookup remains dependent on registry availability, and cached versions are not a Kandev-owned artifact inventory. A version that has disappeared from the registry can still fail to reinstall after npm cache eviction.

An exact top-level package pin does not lock transitive dependency ranges. npm process startup, cold-cache downloads, registry failures, and cache eviction remain accepted costs. The weekly PR requires maintenance and compatibility review, while the Settings override lets operators move ahead of a Kandev release when necessary.

The ACP probe is the activation boundary, not an assertion that every provider, model, or future prompt will succeed. Authentication-required or failed probes do not activate a candidate because Kandev cannot validate and publish its capabilities.

Alternatives Considered

  • Latest-only repair was rejected because cache replacement selects the same broken release and gives the operator no recovery path.
  • Free-form package specs or versions were rejected because they would let a Settings request execute an arbitrary npm package.
  • Automatic fallback after a failed launch was rejected because it hides the version actually used and can make separate launches behave differently.
  • Persisting the shipped default as an operator selection was rejected because it would prevent existing unmodified installations from inheriting a newer reviewed default in a later Kandev release.
  • Removing --prefer-offline was rejected because exact package specs already prevent top-level version drift, while online-preferred metadata on every launch adds registry checks and latency. Explicit update paths remain the freshness boundary.
  • Automatically installing npm latest after an update check was rejected. The checker is informational, and every version change remains an explicit operator action or a reviewed Kandev pin PR.
  • A Kandev-owned package directory and lockfile were rejected for this iteration. Exact npm package specs and version-specific execution keys provide transactional selection without reimplementing package storage.
  • User-installed bridge discovery was rejected for this iteration because it would require path ownership, compatibility discovery, and a separate update contract. npm remains the supported bridge distribution mechanism.
  • Reusing the managed ACP package for terminal passthrough was rejected because an ACP JSON-RPC stdio adapter is not an interactive PTY application, and some integrations distribute those surfaces as different packages and binaries.