This repository has been archived on 2026-10-08. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
maestro/docs/RESTART.md
T
Miguel PalhasandClaude Sonnet 5 64741a027e docs(maestro): fix five more factual errors in structured-session docs
Corrects recovery-as-two-operations, tmux-only survival on restart,
replay's durable-vs-live-state gap, snapshot-vs-replay purpose, and the
three-file persistence count.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 03:45:26 +01:00

6.4 KiB

Restart and upgrade contract

Maestro is upgraded in place: the operator reinstalls the binary and systemd restarts the daemon under every browser tab and TUI that is attached to it. tmux keeps terminal sessions alive across that (see docs/INSTALL.md), but clients lose their sockets either way. This document is the contract for what a client does about it — what the daemon publishes, how a client tells a restart from a blip, and what the user sees while it happens.

A structured session (docs/STRUCTURED-VIEW.md) has no tmux pane behind it to survive on: its agent is a child of the daemon process itself, so a restart takes it down with the daemon. See Structured sessions below for exactly what that costs and what it doesn't.

What the daemon publishes

GET /api/info carries two fields beyond identity and presets:

field meaning
build { commit, commit_date, dirty } — the commit the binary was compiled from
instance uuid regenerated on every daemon start

build is stamped at compile time by crates/maestro-daemon/build.rs, which runs git in the checkout it is compiled in. The Nix build has no .git in the crane-filtered source tree, so flake.nix passes the flake's own revision as MAESTRO_BUILD_COMMIT / MAESTRO_BUILD_DATE / MAESTRO_BUILD_DIRTY. A build with neither source reports empty strings — clients render that as "unknown release" and fall back to instance alone.

instance is the one that never goes missing. It says this process, so two replies that differ on it are two runs of the daemon, even when the commit did not change (a plain systemctl restart, or a crash).

How a client reads them

Three verdicts, from comparing the reply before an outage with the one after (compareIdentity in web/src/daemonLink.ts, mirrored by crates/maestro-tui/src/daemon_link.rs):

  • upgraded — both commits are known and differ. New server code.
  • restarted — commits match (or are unknown) and instance differs.
  • same — neither changed; the socket blipped, the daemon never moved.

Only upgraded is special. A browser tab is holding a bundle that the upgraded daemon no longer serves, so it reloads the page rather than driving new server code from an old bundle. The TUI is its own binary — a reload would not replace it — so it stays up, reconnects, and says which release it is now talking to; the user restarts maestro tui when they want the new client.

What the user sees

The status indicator lives in the sidebar footer, left of the spawn button, in both clients. Its colour is the link state, and it names the running release — commit hash and commit date — on hover (web) or in the status bar (TUI).

state colour meaning
connecting amber first probe hasn't landed
online green daemon answered; same process, or a resolved new one
reconnecting amber socket dropped, still inside the grace window
down red unreachable past the grace window — the UI blocks
updating red answered on a new commit; the page is reloading

updating is web-only, because reloading is web-only. The TUI goes straight back to online after an upgrade and puts updated: <hash> (restart tui) on its status bar instead, until the user restarts it.

The restart overlay is up for exactly down and updating. It is not dismissable and it swallows the app's keyboard shortcuts: everything behind it would act on a daemon that is not there. In the TUI the quit binding still resolves, so a blocked client is never a trapped one. It clears itself the moment a probe succeeds — there is no "retry" button, because the client is already retrying.

Structured sessions

Everything above describes a client's view of a restart, and it holds for a structured session exactly as written — the same three verdicts, the same status indicator, the same overlay. What differs is what survives on the daemon side, because a structured session's agent is a daemon child, not a tmux pane:

  • A restart ends the active turn. There is no adapter process left to finish it, and no tmux pane keeping it alive underneath.
  • The transcript survives, in the per-session event log (docs/STRUCTURED-VIEW.md#persistence-recovery-and-dead-sessions) — a graceful shutdown marks the session for one resume attempt. Recovery is two separate operations, not one: the next daemon calls session/load so the adapter restores its own ACP session, but whatever history the adapter emits during that call is discarded, not replayed into anything — the daemon already has that history on disk. Separately, the daemon rebuilds its own projection by folding the persisted event log back through it. An ungraceful death (a crash, kill -9) leaves no resume marker, and the session stays dead permanently.
  • Pending approvals and their nonces do not survive. Both live only in the old daemon's memory; any ACP permission request still mid-flight when the daemon exits is answered Cancelled on the way down, and a resumed session starts with no pending approval at all.

docs/STRUCTURED-VIEW.md is the full contract for all of this — the event log's shape, the recovery marker, and exactly which kind of session death is resumable.

Timing

  • Grace window — 1200 ms between the socket dropping and the overlay appearing. A wifi hiccup or a laptop waking up should not throw a modal.
  • Poll cadence — GET /api/info every 700 ms while unreachable, flat, not backed off: a restart takes a second or two and the point is to unlock as soon as it is over. The events socket keeps its own exponential backoff for the session stream; the two are independent, and the probe is what decides state.

The events socket dropping is the trigger, not a timer: the daemon cannot warn anyone it is about to die, so its socket closing is the earliest signal there is. A reconnect of that socket does not by itself clear the state — only a probe does, because the socket carries no identity and reconnecting to an upgraded daemon must reload rather than resume.