Skip to content

Unified Zeron and agent updates across devices - #610

Draft
gaelcado wants to merge 1 commit into
zeronsh:mainfrom
gaelcado:feat/unified-updates
Draft

gaelcado wants to merge 1 commit into
zeronsh:mainfrom
gaelcado:feat/unified-updates

Conversation

@gaelcado

@gaelcado gaelcado commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Why

  • Remote Zeron engines couldn't be updated from the app. A remote device on an older version (for example Demeter on 0.2.90) had no update action and no explanation. Nothing guaranteed that a restart wouldn't interrupt an agent run or terminal on that machine.
  • Agent updates could act on the wrong copy. A standalone OpenCode with a Homebrew keg installed beside it ran a bare opencode upgrade. OpenCode's own probing then picked Homebrew, and the update was a successful no-op for the binary that actually runs.
  • Updates were scattered. Agent updates had a Home card, the desktop app had a sidebar strip, and remote devices had nothing.

What changes

Zeron engines (engine-updates-v1)

Four relay-forwardable methods, so any device can be targeted. UpdateStatus is unchanged. The legacy ApplyUpdate now refuses and points at StartEngineUpdate, because it restarted without waiting for idle; nothing in the desktop app called it.

Method What it does
WatchEngineUpdate Streams the engine's update state.
CheckEngineUpdate Refreshes release metadata only.
StartEngineUpdate Returns an operation ID immediately. Retrying with the same requestId joins the running operation.
CancelEngineUpdate Cancels an operation that hasn't started installing.
  • The engine owns the operation. Closing a window or losing the relay doesn't cancel it. The record is written to disk before the restart.
  • Restart admission. New runs, terminals and agent CLI updates take an admission ticket. A restart only closes admission when no ticket is out and nothing is running.
  • Restarting. Only launchd/systemd-supervised engines restart, and only through their service manager. A hand-started engine installs the update and reports "restart required". Zeron never kills processes.
  • Result. "Updated" is reported only when the restarted engine actually runs the target version.
  • Automatic behaviour is the same operation. ZERON_AUTO_UPDATE installs go through it. So does the restart of a supervised engine whose binary someone else replaced (the desktop app swapping its bundle, or zeron update); that restart is tried once per installed version. The release checker now only reports, and its separate stage/apply/restart path is removed.
  • After a restart. An operation interrupted before it installed anything is forgotten, not reported as failed. A settled outcome is not judged again, and it is dropped once the engine runs a different version.
  • Cached downloads. A cached release that fails verification is replaced instead of blocking that release, unless it is the installation in use.

Agent CLIs: update the copy that runs

Installer ownership (Homebrew, npm, vendor updaters, Antigravity archive) is unchanged from main. This adds:

  • OpenCode upgrades with --method curl|npm|pnpm|bun, matching the layout of the binary that runs.
  • A check records which installation it described: launcher, resolved file, size and modification time, and the Homebrew or npm package. Update refuses a different copy and checks again. This covers a changed PATH, a reinstall or a retargeted symlink.
  • Verification fails if Zeron would now launch a different file, or if the update left an older version than before. For Antigravity's managed archive, the expected file is the release directory just installed. Version and discovery caches are cleared after every install attempt.
  • Updates and installs of different agents share one installer lease, so two agents on the same Homebrew or npm installation are never mutated at the same time. An agent waits for its own runs first, so a busy agent doesn't hold up the others.
  • A launcher that only lives under /opt/homebrew/bin but links elsewhere is no longer labelled Homebrew. A Codex launcher that points straight at one releases/<version> is treated as pinned and stays manual.
  • Failure messages keep the tail of the installer's output. Homebrew 7's warning listing every untrusted tap on the machine is dropped from them, so the actual error shows. Only that block is removed; any line it doesn't recognise is kept.
  • Agent executables follow PATH and override order, as launches do. The launcher no longer switches to whichever duplicate reports a newer version.

Desktop

  • Home card. It now lists Zeron engines as well as agent CLIs, and every row names its device.
    • Devices are asked to re-check when their state is missing or older than 30 minutes.
    • A new × dismisses the current offers. They come back for a newer version, and progress and failures are never hidden.
  • Settings → Updates. A new page lists every installation, grouped by device: the app, a separate local engine, remote engines and agent CLIs. It includes installations that are current, offline or not updatable remotely. It also shows each agent's launcher, resolved file and package.
  • Refused actions. When a device refuses an update action, the reason shows on that row, on Home and in Settings, until the row is acted on again or the device reports new state.
  • Older engines are asked for the legacy status stream. Their ApplyUpdate restarts without waiting for idle, so they get one-time zeron update instructions instead of a button.
  • A device that drops mid-restart shows "Reconnecting…". After 3 minutes it says the device hasn't come back.

docs/updates/support-matrix.md describes what each kind of installation gets.

Testing

Tested at 08ce1021 on macOS 27.0 arm64, a single commit on main (42926c80):

  • cargo test -p zeron-engine --lib: 413 passed, 2 ignored
  • cargo test -p zeron-harness -p zeron-proto -p zeron-update -p zeron-rpc --lib -- --test-threads=1: harness 314, proto 52, update 21, rpc 16 passed
  • cargo test -p zeron-ui --lib -- --test-threads=1: 1,402 passed
  • cargo check -p zeron: passes
  • New regression tests:
    • restart admission refuses an agent install;
    • a launcher rewritten between check and click is re-checked, not updated;
    • OpenCode --method for a standalone copy beside a Homebrew keg;
    • trimmed diagnostics without unrelated tap warnings;
    • a pinned Codex release alias;
    • engine update lifecycle and wire contracts, including recovery after a restart, the once-per-version automatic restart and a restart-only operation on an install the engine can't update itself;
    • a busy agent not holding the shared installer lease;
    • an unusable cached download being replaced;
    • a refused action showing on its own row;
    • Updates row presentation.
  • cargo fmt --all -- --check reports the same pre-existing differences as main, and none in the files changed here beyond those.

Not verified yet

  • No screenshots of this build: the Home card with Zeron rows and the dismiss button, and Settings → Updates.
  • No real launchd/systemd restart of a remote engine, and no relayed reconnect timing.
  • No Linux or Windows runs.
  • Full engine integration/e2e suites were not rerun on this base.
  • No live Homebrew, OpenCode or Antigravity update was run against a disposable install on this commit. The Antigravity verification fix has no automated test.

Contract names (engine-updates-v1, the method names, EngineUpdateState fields) are a proposal. iOS is unchanged and can adopt them later.

Closes #448. Related: #389, #595.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

@gaelcado

Copy link
Copy Markdown
Contributor Author

Closes #448

@gaelcado gaelcado changed the title Reliable agent updates and one Updates surface for every device Make Zeron and agent updates follow the selected installation Sep 28, 2026
@gaelcado
gaelcado force-pushed the feat/unified-updates branch from 58be16c to 12a896c Compare September 30, 2026 08:33
@gaelcado gaelcado changed the title Make Zeron and agent updates follow the selected installation Unified Zeron and agent updates across devices (builds on #661) Sep 30, 2026
@gaelcado gaelcado changed the title Unified Zeron and agent updates across devices (builds on #661) Unified Zeron and agent updates across devices Sep 30, 2026
@gaelcado
gaelcado force-pushed the feat/unified-updates branch from 12a896c to e002050 Compare October 1, 2026 09:33
Zeron engines (`engine-updates-v1`): Watch/Check/Start/CancelEngineUpdate,
relay-forwardable so any device can be targeted. The engine owns each
operation: closing a window or losing the relay never cancels it, and a
retried requestId joins the running operation. New runs, terminals and
agent CLI updates take a restart admission ticket; a restart only
closes admission when none is outstanding and nothing is running. Only
supervised engines restart, through launchd/systemd; hand-started ones
report "restart required". Updated is reported only when the restarted
engine runs the target version.

Automatic behaviour is the same operation: ZERON_AUTO_UPDATE installs,
and a supervised engine restarts into a binary someone else installed
(the desktop app swapping its bundle, `zeron update`), once per
installed version. The release checker only reports; its own
stage/apply/restart path is gone. An operation interrupted before it
installed anything is forgotten on restart, a settled outcome is not
judged again, and it is dropped once the engine runs another version.
An unusable cached download is replaced instead of blocking the
release. UpdateStatus is unchanged; legacy ApplyUpdate now refuses and
points at StartEngineUpdate, since it restarted without waiting for
idle.

Agent CLIs: every update stays bound to the installation Zeron launches.

- OpenCode upgrades with `--method curl|npm|pnpm|bun` for the layout of
  the running binary. A bare `opencode upgrade` beside a Homebrew keg
  picked Homebrew and no-oped for the standalone copy.
- A check records the launcher, resolved file, size, mtime, Homebrew
  package and npm package it described. Apply and the install step
  refuse a different copy and re-check instead.
- Verification fails when Zeron would now launch another file (for a
  managed archive: anything but the release it just installed) or the
  update left an older version. Version and discovery caches are
  invalidated after every install attempt, including harness installs.
- Agent installs and updates share one installer lease, since several
  agents can live in one Homebrew or npm installation. It is taken
  after the agent's own runs have drained, so a busy agent does not
  hold up the others.
- A launcher under /opt/homebrew/bin that links elsewhere is not
  labelled Homebrew; a Codex launcher pinned to one release stays manual.
- Failure messages keep the installer's tail, without Homebrew 7's
  list of unrelated untrusted taps.
- Executable selection follows PATH and override order like launches,
  instead of switching to whichever duplicate reports a newer version.

Desktop: the Home card covers Zeron engines and agent CLIs on every
device, re-asks devices whose state is missing or stale, and gets a
dismiss button that never hides progress or failures. Settings ->
Updates lists every installation by device, including current, offline
and remotely non-updatable ones, with instructions and installation
details. A refused action shows its reason on its own row, on Home
and in Settings. Older engines get one-time `zeron update` instructions
instead of an unsafe apply; a device dropping mid-restart shows
Reconnecting, then says it has not come back after 3 minutes.
@gaelcado
gaelcado force-pushed the feat/unified-updates branch from e002050 to 08ce102 Compare October 1, 2026 12:50
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.

Add in app update

1 participant