AgentBridge / docs /autoupdate.md
kocahoctpa's picture
AgentBridge showcase: docs, white paper, demo media and landing page
8f16a6b verified
|
Raw
History Blame Contribute Delete
7.25 kB

AgentBridge β€” automatic updates (architecture)

How AgentBridge updates itself from the GitHub Releases page, and why it is designed this way. Implemented in AutoUpdate.cs (a self-contained static class) with two hooks in Program.cs and one menu item in Tui.cs.

What it does

At startup the app asks GitHub for the latest release. When the released version is newer than the running one β€” and auto-update is enabled β€” it:

  1. refreshes the tool plugins first (PluginUpdater): every loaded plugin is checked against its repo's GitHub release and, when a newer self-contained zip exists, installed into Tools/<Plugin>/ before the app archive is applied (the archive also carries the plugins β€” this covers plugin releases that landed between two app releases). When agents are executing the refresh is refused and the whole update is retried after 30 minutes (ScheduleRetryIn);
  2. downloads the platform archive (agentbridge-<rid>.tar.gz) to %TEMP%;
  3. extracts it to a temp folder;
  4. spawns the new executable from the temp extract as an updater process (--apply-update <target> <extract> <oldPid>) and exits;
  5. the updater waits for the old process to terminate, copies the changed files (the executable last, via a .old rename for rollback), restarts the app with the original command line and cleans up.

Any failure along the way leaves the current version running untouched β€” the update is best-effort by design.

Why the two-process swap

On Windows a running executable cannot be overwritten, and at startup the app is already the process mapping its own exe β€” so the executable can never replace itself, neither during execution nor at the next start. The swap must be done by a different process: the new exe extracted to %TEMP% is a different file, so it can replace the installed one once the old process is gone.

This was verified both against Microsoft's documentation (CreateFile sharing modes: the image is opened read-only for the lifetime of the process) and empirically on Windows: a child process does not keep the parent's exe locked after the parent exits β€” the lock lasts only as long as the owning process. Hence the pattern "spawn the updater and exit immediately" (never wait for the updater from the app).

The file storage tiers (what an update may touch)

The update mechanism respects the three tiers defined in RELEASING.md:

Tier Rule
PersistentData\ (user-editable config) never touched β€” not present in the archive, never written
OS app-data folder <AppData>\agent\ (SMTP/IMAP credentials, setup.json, autoupdate.json; LLM API keys are NOT here β€” they live per-provider in providers.json) never touched β€” outside the app folder by construction
Distribution content (everything else) replaced when changed, with two exceptions

The exceptions: appsettings.json (server config) and providers.json (LLM provider definitions) are stripped from the extracted copy before the swap, so the user's configuration is never overwritten. Everything else β€” including the other .json files in the archive (.playwright/package/*.json, agent.staticwebassets.endpoints.json) β€” is distribution content and is replaced. Protection is by whitelist, never by file extension.

Files are copied only when they changed (length + SHA-256 comparison); the executable is always replaced (never compared β€” a fresh build is the point of the update).

Version check (no GitHub API)

  • Current version: Assembly.GetExecutingAssembly().GetName().Version β€” the numeric 1.yy.MM.dd baked into the binary by the csproj (the -prerelease suffix lives only in the informational version, so a prerelease build compares by its numeric date).
  • Latest version: an HTTP HEAD/GET to https://github.com/Graphene-Lab/AgentBridge/releases/latest with AllowAutoRedirect = false; the redirect's Location header carries the tag (v1.26.8.10). No API call, so the unauthenticated rate limit (60 req/h/IP) is never an issue. A download URL is then constructed deterministically: https://github.com/Graphene-Lab/AgentBridge/releases/download/<tag>/agentbridge-<rid>.tar.gz.
  • GitHub imposes no bandwidth limit on release downloads (the Acceptable Use Policies "Excessive Bandwidth Use" clause is a relative anti-abuse rule, not a quota); the client still uses bounded timeouts so a slow/offline GitHub never blocks startup.

Platform detection

The archive name maps from the running OS + architecture (the RIDs built by release.yml):

Platform Archive
Windows x64 agentbridge-win-x64.tar.gz
Linux x64 / arm64 agentbridge-linux-x64.tar.gz / agentbridge-linux-arm64.tar.gz
macOS x64 / arm64 agentbridge-osx-x64.tar.gz / agentbridge-osx-arm64.tar.gz
anything else (e.g. Windows arm64) no archive β€” the check is skipped

The update is also skipped when the app runs from dotnet run/dotnet <dll> (dev mode): the entry assembly is a .dll, so the swap would target dotnet itself.

Enabling / disabling

  • Default: enabled (true).
  • TUI: menu File β†’ Auto-Update toggles the check on/off and persists the choice to <AppData>\agent\autoupdate.json (the OS app-data folder, same tier as setup.json β€” updates never touch it).
  • appsettings.json: "AutoUpdate": { "Enabled": true } is the shipped default.
  • Command line: --no-update disables the check for that launch (for services/CI that manage the binary themselves β€” e.g. systemd units should pass it).

Precedence: --no-update > persisted toggle > appsettings.json > built-in true.

Restart and rollback

  • The updater restarts the app with the original command line (minus --no-update), so a TUI session stays a TUI and a --headless service stays headless.
  • The old executable is kept as agent(.exe).old until the next successful start, which deletes it (and any stale %TEMP%\agentbridge-update area). A broken new executable can be recovered by renaming the .old back manually.
  • If the old process does not exit within two minutes, the updater aborts and the old version keeps running.

Security notes

  • Downloads travel over HTTPS from GitHub's CDN; archives are not checksum-verified (GitHub does not publish per-asset hashes). A sha256.txt asset could be added to release.yml later if verification is wanted.
  • The binaries are unsigned; the trust model is "GitHub + HTTPS". Fine for personal distribution; sign the binaries before wider roll-out.

Testing

  • Unit-testable pieces: version comparison, RID mapping, "file changed" comparison (length + SHA-256).
  • End-to-end (Windows): run a published build from a folder, set AutoUpdate.Enabled to force a fake/lower current version (or run an old build), and observe the swap: temp extract β†’ updater β†’ .old β†’ restart with the new exe.
  • The updater wait loop can be observed by keeping the old process busy past its exit deadline β€” the update must abort cleanly.