Expand description
synaps daemon reload (PLAN-phase3 §2.8, C3): version gate → drain →
Checkpoint{Reload} every session → reload-state → announce
Reloading/Bye{Reloading} → execv self (same pid, adopted flock,
CLOEXEC listener) → new image rehydrates sessions from reload-state
BEFORE accepting.
What reload cannot preserve (§2.8, stated once): in-flight turns
(checkpointed = cancelled; partial history + restart marker kept), pending prompts (answered
None), PTY/background shells (closed, announced), turn_replay,
input ownership (the owner reclaims it on
reconnect via was_owner). What it preserves — each session’s
Checkpoint{Reload} reply carries a SessionReloadRecord: its journal
and id (the rehydrated session continues the same journal), its
SessionConfig as created (cwd, system, prompt manifest, compaction
policy, auto-compact, …), its keep-warm pin, its lifecycle (a Parked
session comes back Parked), the non-persisted runtime knobs
(settings_replay: context window, compaction model, retries,
timeouts, tool-output cap, worker grants, /system), the CURRENT
model/thinking (/model mid-session survives), and the session’s exact
tool activations (pins only, re-validated by the new runtime at its first
turn start — SessionToolSet::restore_carried).
Windows has no execv, and a byte-range lock cannot be inherited, so
step 5 is a hand-off between two processes (see daemon::win): the old
image starts the new one with the reload-state and a hand-off event; the
new image queues its daemon.lock request behind the old one and sets
the event; the old image exits — discarding what an exec would — and its
release grants the queued request, so the liveness oracle never reads
“free”. The new image does this before booting anything, then proceeds
exactly as after an exec. Same guarantees, one difference: the pid
changes (clients learn it from the next Welcome).
Structs§
- Prepared
- Steps 1–4 (§2.8): gate, drain, checkpoint, reload-state, announce.
Nothing irreversible has happened when this returns
Ok; the caller (the requesting connection) sends its ownBye{Reloading}, flushes, then callsexec. - Print
Version <exe> daemon --print-versionoutput.- Reload
Request - Reload
Session - One recorded session.
- Reload
State registry_dir()/daemon[-P].reload.jsonbody.
Enums§
Constants§
- DRAIN_
SECS_ ENV - Env: default drain budget in seconds.
- HANDOFF_
EVENT_ ENV - Windows: env naming the event the new image sets once its
daemon.lockrequest is queued behind the old image (which then exits). - HANDOFF_
PID_ ENV - Windows: env carrying the old image’s pid; the new image waits for it to be gone (and its pipe instances with it) before it binds.
- LOCK_
FD_ ENV - Env: inherited
daemon.lockfd (mandatory whenRELOAD_STATE_ENVis set). - RELOAD_
STATE_ ENV - Env: path of the reload-state file handed to the new image.
- RETRY_
AFTER_ MS - What clients are told to wait before reconnecting.
- SUBAGENT_
ID_ FLOOR_ ENV - Env: the subagent handle counter read at the
execvitself, later thanReloadState::next_subagent_id(written after the checkpoints): a worker that ignored its stop could still dispatch in between.
Functions§
- adopt_
from_ env - New-image side (
Daemon::start): whenRELOAD_STATE_ENVis set, the lock is ADOPTED fromLOCK_FD_ENV(mandatory — refuse to start without it, risk §6.8) and the recorded generation is returned. - exec
- Step 5: rewrite
daemon.json(generation+1, same pid), stop sidecars, hand the flock to the next image andexecv. Returns only on failure — thendaemon.json/reload-state are restored, extension discovery is re-spawned (the sidecars were stopped for the exec — M4: no zombie daemon without hooks), and the old image keeps serving (sessions checkpointed but alive; clients that gotByereconnect to THIS image — areconnect_of.generationmismatch is tolerated, the daemon answers with its own generation). - prepare
- probe_
version - Run
<exe> daemon --print-version(5 s) and parse it. - rehydrate
- reload_
state_ path - resolve_
exe - The executable to re-exec: argv[0] canonicalised (NOT
/proc/self/exe, which reads “(deleted)” after an in-place rebuild);current_exeonly as a fallback when argv[0] is not resolvable (e.g. bare name on PATH). - validate_
exe - M8:
--exemust be something we would trust toexecvin our own pid: an existing regular file (canonicalised), executable, owned by our uid (or root — package-installed binaries), not group/world-writable. Returns the canonical path. Same-uid 0600 socket → not a privilege boundary; this is a footgun guard, not a sandbox. - version_
gate - Directional gate (§2.1): new protocol ≥ ours (clients must still be able to reconnect) and new binary ≥ ours (newer OR EQUAL; same-version rebuilds are the common case).