# Update Manager > Module 10 · Plane: Control · Roadmap phase: 4 > Part of [MCP Nexus architecture](../ARCHITECTURE.md). ## Purpose The Update Manager keeps managed `MCPInstance`s current and safe. It watches upstream sources for newer versions of the packages an instance was installed from — Docker image tags/digests, GitHub releases, and [Registry](02-package-registry.md) index updates — decides whether to act based on **policy** (automatic, manual-approval-gated, or pinned), and applies updates as **re-installs** through the [Auto Installer](03-auto-installer.md). Crucially, it keeps the previous known-good spec so any update can be **rolled back** safely. It is a day-2 control-plane reconciler (Architecture §5): a failure here degrades *management currency*, never the data plane. Per tenet #2 it proposes and applies state transitions through the loop, it does not run ad-hoc upgrade scripts. ## Responsibilities - **Watch** upstream sources per instance and detect available updates: - Docker image **tags/digests** (a new digest behind a tracked tag). - **GitHub releases** for `source=github` packages. - **Registry index updates** (a package's recommended version advanced). - Evaluate each candidate against the instance's **update policy**: `automatic`, `manual` (approval-gated), or `pinned` (never auto-update). - Enforce **version pinning**: a pinned instance is skipped entirely (still reported as "update available"). - Apply an approved update by driving a **re-install** at the new digest via the [Installer](03-auto-installer.md), preserving the same `bound_resource_uuid`. - Retain the **previous known-good `MCPInstance` spec** and perform **rollback** when the new version fails to become/stay healthy. - Coordinate with [Health Monitoring](09-health-monitoring.md) to gate and validate updates, and with [Notifications](13-notifications.md) to alert on available / applied / failed / rolled-back updates. - Record all decisions and transitions to Audit (Architecture §9). ## Non-goals - Performing the actual pull/create/start — delegated to the [Installer](03-auto-installer.md) and Runtime. - Choosing/verifying package signatures or digests — owned by the [Registry](02-package-registry.md). - Restarting crashed-but-same-version instances — that is [Health Monitoring](09-health-monitoring.md) (self-healing), not an update. - Deciding which package a service uses ([Recipes](15-smart-recipes.md)). ## Interfaces ```go type UpdatePolicy string const ( PolicyAuto UpdatePolicy = "automatic" // apply without human action PolicyManual UpdatePolicy = "manual" // detect, then wait for approval PolicyPinned UpdatePolicy = "pinned" // never auto-update ) // Everything-is-a-plugin (tenet #3): each upstream is a watcher. type UpdateWatcher interface { Name() string // "docker-digest", "github-release", "registry-index" // Return a candidate if a newer version exists for this instance. Check(ctx context.Context, inst MCPInstance) (Candidate, bool, error) } type Candidate struct { InstanceID string From PackageVersion // current (digest-pinned) To PackageVersion // proposed (digest-pinned) Source string // watcher name Notes string // e.g. GitHub release notes } type UpdateManager interface { Scan(ctx context.Context) ([]Candidate, error) // periodic + event-driven Approve(ctx context.Context, candidateID string) error Apply(ctx context.Context, c Candidate) (MCPInstance, error) // re-install Rollback(ctx context.Context, instanceID string) error // to known-good SetPolicy(ctx context.Context, instanceID string, p UpdatePolicy) error Pin(ctx context.Context, instanceID string, version string) error } ``` HTTP/API surface (behind Auth + [RBAC](08-rbac.md)): - `GET /api/v1/updates` — available/pending candidates across instances. - `POST /api/v1/updates/{id}/approve` — approve a manual-gated candidate. - `POST /api/v1/updates/{id}/apply` — force-apply now (audited). - `POST /api/v1/instances/{id}/rollback` — roll back to previous known-good. - `PUT /api/v1/instances/{id}/update-policy` — set policy / pin a version. ## Data - **Reads:** `MCPInstance` (current `package`, `version`, digest) from **Inventory**; `Package`/`PackageVersion` from the **Registry** store (Architecture §8); instance `health` from Health Monitoring. - **Writes:** on apply, the Installer updates the `MCPInstance` (new `version`, `container_ref`); the Update Manager updates `state` around the transition (`Updating`, `RollingBack`). - **Module-local persistence:** - `update_policy` per instance (`automatic|manual|pinned`, optional pinned version/digest). - `previous_good_spec` — the last healthy `MCPInstance` spec (package, version, digest, rendered config-with-secret-refs) for rollback. - `update_candidate` rows (from→to, source, status: available/approved/applied/ failed/rolled-back) and per-watcher cursors/ETags to avoid re-alerting. ## Dependencies - [Auto Installer](03-auto-installer.md) — updates are re-installs of a new version. - [MCP Package Registry](02-package-registry.md) — resolves/validates target digests. - [Health Monitoring](09-health-monitoring.md) — gates the new version; signals rollback. - [Notifications](13-notifications.md) — alerts on available/applied/failed/rolled-back. - [Secrets Manager](07-secrets-manager.md) — re-injected at the re-install (unchanged). - [Plugin System](11-plugin-system.md) — `UpdateWatcher` is a plugin point. - [RBAC](08-rbac.md) — who may approve/apply/pin/rollback. ## Failure modes & handling - **New version unhealthy:** the Installer's health gate fails, or Health Monitoring reports the freshly updated instance unhealthy within a validation window → automatic **rollback** to `previous_good_spec`; fire a failed-update notification. - **Watcher/upstream unreachable:** skip silently (best-effort), keep the last cursor; repeated failures alert but never block other updates or the data plane. - **Registry can't validate target digest:** candidate is rejected (not applied); reported as `failed` with reason. - **Pinned instance with newer upstream:** never applied; surfaced as "update available (pinned)" only. - **Rollback itself fails:** instance marked degraded, high-severity notification, left for Health Monitoring quarantine + human intervention. - **Concurrent apply + heal:** updates are serialized per `instance-id` (shared lock with the Installer) so an update and a restart don't race. - **Approval race / stale candidate:** applying a candidate whose `From` no longer matches the live instance is rejected and re-scanned. ## Security notes - Only **digest-pinned, signature-verified** target versions are applied; the Registry re-verifies before the Installer pulls (Architecture §9). - Secrets are re-injected at runtime by the Installer on the re-install — never copied into `previous_good_spec` as plaintext; the retained spec holds secret *references* only. - Approve/apply/pin/rollback are audited mutating actions with principal, target, from→to versions, and outcome. - Auto-update policy is itself an RBAC-guarded setting; a compromised auto policy is a supply-chain risk, so downgrades/policy changes are audited. ## Open questions - Health **validation window** length after an update before declaring success — fixed, or per-recipe? - Should GitHub-release watching drive version *selection* directly, or only notify the Registry to advance its index (single source of truth)? - Batch/canary updates across many instances of the same package vs. one-at-a-time. - Retention depth for `previous_good_spec` — just N−1, or a short history for multi-step rollback? - Maintenance windows / update scheduling to avoid disrupting active agent calls. ## Milestone Phase 4 — *Operate (day-2)*. Update Manager ships with Docker-digest, GitHub-release, and Registry-index watchers; automatic/manual/pinned policies; and health-gated rollback. It contributes to the Phase 4 exit story alongside [Health](09-health-monitoring.md), [Metrics](18-metrics.md), [Notifications](13-notifications.md), and the [Dashboard](12-web-dashboard.md).