miadi-orchestration-kit

Session continuity

Earned on 2026-09-28, when gaia crashed on Sunday at 19:31:47 and booted nine times on Monday. Tmux came back with 67 empty shells from a save 30 hours old. Nothing said which agent conversation had run in which terminal, and 29 agents had been running.

William’s outcome for this practice: “Picture that I’m rebooting my computer and when it comes back up, all of the sessions are the same way that they were. I should not even know that the computer rebooted.”

The four parts

part what it keeps where
binding line each Claude session’s id, tmux session:window.pane, pane id, command line, name history, team, chronicle episode this plugin’s hooks/claude_hooks/terminal_binding.sh, written to <root>/data/terminal_bindings.jsonl
tmux save and restore layout, folders, visible screens, saved every 15 minutes by tmux-save.timer; the server started at boot by tmux-server.service jgwill/gaia linux_migration/14-tmux-resurrect.sh and its two hooks; on a new machine miadi-terminal enable restore (apt)
tide the agent in each pane, every 60 s, and the relaunch after a restore ironsilk 0.9.40 and later (claude, hermes, pi), tide agents list, tide agents restore; apt miadi-tide
one tmux 3.7c everywhere: a client of another version cannot attach apt miadi-tmux
recovery list what each pane probably held, when the three above had nothing built by hand as in “After a crash” below

<root> is CLAUDE_SESSIONDATA_ROOT, else MIADI_SESSION_DIR, MIADI_SESSIONDATA_ROOT, SESSION_DATA_ROOT, else /src/_sessiondata.

Read what a pane holds

tide agents list                       # running, exited or lost, per pane of the running tmux
jq -c 'select(.tmux.session == "<tmux session>") | {at, event, session_id, name: .name.name, team}' \
  /src/_sessiondata/data/terminal_bindings.jsonl | tail

state: running comes from the pane’s process tree and ~/.claude/sessions/<pid>.json. exited and lost come from the pane’s last binding line since the tmux server started. The session name changes with /rename, and every change is a session.rename line.

What happens at a restore

  1. tmux-server.service starts the tmux server at boot, once the desktop login has imported its environment, after pointing a dangling last at the newest save. tmux-continuum restores the last save: panes in their folders, with their visible screens.
  2. The post-restore-all hook (tmux-restore-agents.sh) starts tide agents restore.
  3. tide reads its last snapshot with panes from before this tmux server started. It relaunches the agents that were running, with their launch alias and --resume <id>, 10 seconds apart. The most recently active ones that fit above 16 GiB of available memory (512 MiB counted each, --agent-memory-mb) start, oldest of them first, so the last one resumed is the one that was active last and the session list on William’s phone keeps its order (ironsilk 0.9.40, 2026-10-07). The others get their command typed without Enter. An agent that had exited starts nothing and gets nothing typed: its pane keeps a note (the pane option @miadi-resume), tide agents list shows it exited, and tide agents resume [pane] brings it back when the human chooses (William, 2026-10-07). A session closed after the last save comes back from it as a shell. When tide’s snapshot is newer than the save and no longer names the session, tide closes it again, unless a pane runs something other than a shell or the session shares a folder with a snapshot session that did not come back (a rename). ironsilk 0.9.42 (William, 2026-10-08: no dead bodies).
  4. It runs once per tmux server start. A second hook call answers “already restored”.

Try it without touching anything: tide agents restore --dry-run --force.

Set it up on a machine

After a crash

  1. Find the first crash, not the last boot: journalctl --list-boots. A crash loop makes the last boot misleading, and tmux may already have restored once between two boots.
  2. Find tide’s last snapshot before that crash:
    sqlite3 -readonly ~/.miadi/navigator/context/snapshots.sqlite3 \
      "select timestamp from context_snapshots where timestamp < '<crash, UTC>' order by timestamp_epoch desc limit 1"
    

    Copy it out before anything restarts the daemon: latest.json is overwritten every minute, and the store keeps 7 days.

  3. If that snapshot names agents (tide 0.9.35 and later), tide agents restore --force brings them back. If it does not, build the recovery list: for each tmux session in the snapshot, the Claude transcripts whose folder matches, with their /rename name, the inventory record that names the tmux session, and any --resume <id> in the tmux save. Call them candidates. A shared folder cannot tell which pane held which session.
  4. Compare the snapshot with what tmux restored. Sessions created after the last save are missing, and recreating them is the human’s decision. Sessions closed before the crash came back from the save, and tide 0.9.42 closes them again (its restore log says closed); the ones it skipped say why. Panes the restore itself added, idle shells that no snapshot names, are yours to remove.

When tmux did not come back by itself (2026-10-03, jgwill/gaia#90):

  1. readlink ~/.local/share/tmux/resurrect/last. A crash during a save can leave it naming a file that is not on disk. Point it at the newest save whose sessions match tide’s last snapshot.
  2. Start the server through the unit, never from an agent’s shell (it would hand that shell’s environment, CLAUDECODE included, to every pane): systemctl --user start tmux-server.service.
  3. Wait for ~/.miadi/navigator/restore/agents-*.jsonl to list every step, then tide agents list. Every relaunched pane should show running with its own session id.
  4. Compare the pane addresses with the snapshot’s. The agents that were not running are noted, not started. Give the human the session list to confirm.

Checks that proved each part

Never test against the live tmux server. Starting a private server loads the same plugins, and its run-shell jobs get TMUX for that server, so they stay on it.

Rules earned

Earned 2026-10-03 (jgwill/gaia#90):

Teams

Every session belongs to one team, and the binding line says which one and how it was found: {"id": "T1", "source": "session"}. The order is a declared team (MIADI_TEAM, or tmux set-option -t <session> @miadi-team T1), then the session names in teams/teams.json, then its folders (the longest prefix wins), then its name patterns, else unassigned. The list is MIADI_TEAMS_FILE, else $MIADI_ORCHESTRATION_KIT_ROOT/teams/teams.json. Keep it in step with teams/README.md. Naming a new team is William’s.

Episodes

The binding line also names the chronicle episode a session works in, so an episode can list its terminals: {"id": "2026-09-27-episode-548-...", "source": "cwd"}. An episode is a directory directly under MIADI_CHRONICLE_ROOT named <yyyy-mm-dd>-episode-<n>-<slug>. The order is a directory given with --add-dir (add-dir), then the agent’s folder (cwd), then MIADI_CHRONICLE_PROD_EPISODE (declared), else null. The variable comes last because every shell exports it: alone it names the episode in production, not the one the session works in.