Review loop (--review-only, --all-reviewer, focused on this Mac: macOS, /usr/bin/python3 3.9.6, bash 3.2): round 1 (@63d8164) agy and grok PASS, claude and codex NOT PASS (F1 paho via the /usr/bin/python3 fallback, F2 logs default path, R1 disk-fallback redispatch interleaving, R2 symlinked registry), sent as a PR comment and fixed by the author in four commits; round 2 (@a7cb90e) 4/4 PASS. Full suite on macOS / Python 3.9.6 @a7cb90e: 1867 passed, 56 skipped, 1 error (the known #55 false positive: WAL/SHM of an agy conversation running in another workspace). Real herdr panes and ~/.claude/projects unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
11 KiB
name, description, version, author, license, platforms, environments, metadata
| name | description | version | author | license | platforms | environments | metadata | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| multi-agent-mux-resume | Resume an existing agent (claude, antigravity/agy) conversation by UUID into a herdr session. Reads .mam/agent-sessions.yaml for the saved session/conversation id, spawns (or reuses) a herdr session of the matching name, and runs `claude -r <id>` or `agy --conversation <id>` inside. Use when you want to reattach to a previous session's context, or revive a session whose herdr died but the agent's conversation is still on disk. | 4.9.4 | godopu | MIT |
|
|
|
Multi-Agent Resume — Reattach to a Saved Conversation
Companion skills:
multi-agent-mux-create(start a fresh agent),multi-agent-mux-stop(terminate),multi-agent-mux-monitor(live status). Herdr Isolation:HERDR_SESSION_NAMEenv var를 create에서 설정한 경우, 동일 서버에서 동작합니다. 자세한 격리 패턴은 multi-agent-mux-create/SKILL.md 참조. Single source of truth:./.mam/agent-sessions.yaml.
What this skill does
Container + data reconstruction: spawn a herdr session (the container), then run the agent inside with a specific session id (the data) so the previous conversation's context is restored.
Three cases this skill handles:
- herdr is dead, conversation lives —
agent-sessions.yamlhas the UUID. The JSONL/db is on disk. Re-spawn the herdr session + runclaude -r <id>/agy --conversation <id>. - herdr is alive but empty — You started a session with
multi-agent-mux-createbut haven't sent a message yet (so no session id was assigned). The user can either send their first message (and the id is auto-assigned), or you can read the workspace's most recent conversation from$HOME_DIR/.gemini/antigravity-cli/cache/last_conversations.json(defaults to~/.gemini/...) for agy, or the latest*.jsonlin$CLAUDE_PROJECT_DIR/<workspace-key>/(defaults to~/.claude/projects/) for claude. - herdr is alive AND the agent inside is already running —
resume_session.shonly updates the row and exits 0. It does not attach and does not check the agent's permission flags (#45); attach withherdr --session <server> agent attach <name>, where<server>is the row'sherdr_session(the server it was resumed on).
Resuming a stopped session (stopped → running)
When a session was ended via multi-agent-mux-stop (which captures the ID and gracefully stops by default),
its row is status: stopped with resumable: true and the conversation id
already recorded in claude_session_id_own / agy_conversation_id_own. This is the
ideal resume path:
- tier-1, race-free: because the stop command wrote the id into the row at stop
time,
resolve_session_id.shresolves it viafind_workspace_uuidtier-1 (the per-row own id) — no reliance on the mtime-based disk scan, so a concurrent session in another workspace can never shadow it. - On resume,
update_yaml_resumed.shtransitionsstopped → runningand clears the stop metadata (stopped_at,stopped_at_epoch,stop_reason,resumable) along with the usualterminated_at*/termination_mode/archived_at, so the row reflects a clean running state with no stale end-of-session fields.
UUID resolution order
agent-sessions.yaml and on-disk discovery are used to resolve the UUID in this order:
herdr_sessions[]row's per-row own id (claude_session_id_own/agy_conversation_id_own/hermes_conversation_id_own/grok_session_id_own/opencode_session_id_own/codex_session_id_own) — explicitly saved bymulti-agent-mux-stopright before teardown (tier-1, race-free).- Workspace-scoped on-disk scan (adapter
discover())
If both are empty → for Class A agents (agy, hermes, opencode, codex), resume_session.sh automatically falls back to FRESH_SPAWN=1 (spawn-spec). For Class B agents (claude, grok), fall back to multi-agent-mux-create.
Workflow
bash .agents/skills/multi-agent-mux-resume/scripts/resume_session.sh --workspace <path> --agent <agent> --session <name>
resume_session.sh does the whole flow: original-cwd check, control-plane binding, session-id resolution (resolve_session_id.sh), the herdr-alive check, the agent command (adapter resume-spec, or spawn-spec for a Class A fresh spawn), spawn (just before it, the session's own empty shell left by a server restart is closed: same session id, no agent, nothing running; lib_py/herdr_restore.py), start-up dialogs, and the YAML update (update_yaml_resumed.sh, which also publishes a progress "resumed" event when the row has a delegate_job_id). It never attaches; attaching is a separate step that names the server, because the server the script exports does not reach the calling shell and the native herdr binary does not route on environment variables: herdr --session <server> agent attach <name> (<server> = the --herdr-session you passed, or the row's herdr_session).
| Option | Meaning |
|---|---|
--workspace |
Required. The directory the session ran in (its row's pane.cwd; a different path is exit 2) |
--agent |
Required. claude, agy, hermes, grok, opencode, codex |
--session |
Required. Session name |
--herdr-session (--herdr-server) |
Isolated herdr server name |
--herdr-workspace |
herdr workspace label |
--auto-trust / --no-auto-trust |
Answer a start-up directory-trust dialog or leave it to a human (MAM_AUTO_TRUST; default: answer) |
--dry-run |
Resolve the command and print it; no spawn and no registry (YAML/DB) update. The checks before it still run, and on macOS they remove the quarantine attribute from the agent binary |
| Exit | Meaning |
|---|---|
| 0 | Resumed; or already running, so only the row was updated (no attach); or --dry-run |
| 1 | From the checks before spawn, nothing was changed: no saved session for claude/grok (use multi-agent-mux-create first), agent binary missing or not executable. The YAML update after spawn (update_yaml_resumed.sh) also exits 1 (YAML not found) |
| 2 | From the checks before spawn, nothing was changed: bad arguments, --workspace differs from the original cwd or it no longer exists, control-plane binding refused, whitespace in the control-plane path. The YAML update after spawn also exits 2 (bad arguments) |
| other | A later step failed (herdr spawn, start-up dialogs, YAML update) and its code is passed through. resume does not roll back |
A non-zero exit after the spawn can leave the herdr session running while the row is still stopped. Before retrying or creating a new session, check with multi-agent-mux-status. The ERROR: line on stderr tells the two apart: the pre-spawn checks' messages are in resume_session.sh (lines 50-223).
Original cwd only (Issue #21 phase 2)
A session is resumed only in the directory it ran in (its row's pane.cwd): the conversation is keyed by that directory, and a class A agent (agy/hermes/opencode/codex) would otherwise start a new conversation in the wrong tree.
--workspacediffering frompane.cwd→ exit 2 with the path to use (claude/grok used to fail with exit 1 "No saved session").pane.cwdno longer exists (worktree removed) → exit 2; recreate the worktree at that path to resume.- Rows without
pane.cwdare not checked. The check runs before session-id resolution and also under--dry-run. - The control-plane binding is computed before the herdr-alive check, so an already-running session's row gets it too; a binding that cannot be computed is refused (exit 2) for a worktree and warned about for the control root.
Pitfalls
claude -rrequires the SAME project directory — if the workspace path differs from when the session was created, claude will create a new project dir key (-home-...-different-name) and put the resume in a different location. Always-c(cd to workspace) before running.- agy's
--conversationflag name varies by version — older versions used--resumeor-r. Checkagy --help | grep -E "conversation|resume"and use the right flag. v1.0.x:--conversation. - The first message after resume might re-trigger TUI dialogs — if the original session was created with
--dangerously-skip-permissions, those flags are NOT persisted; you must re-apply them on resume.resume_session.shre-passes them through the adapterresume-spec. - Don't resume if the session is brand new and empty —
multi-agent-mux-createalready set up an empty container; sending a probe message ("init") is the right way to materialize a session id, NOTclaude -rwith a placeholder. agy --conversation <id>will fail if the conversation was deleted from disk — check~/.gemini/antigravity-cli/conversations/<uuid>.dbexists before attempting resume. If missing, the conversation is gone; you need a fresh session viamulti-agent-mux-create.
Verification
# The server the session runs on (its row's herdr_session); the native herdr
# binary needs it as --session, an exported variable is not enough.
SERVER=$(python3 -c "import yaml; d = yaml.safe_load(open('.mam/agent-sessions.yaml')); print([s for s in d['herdr_sessions'] if s['name'] == '$SESSION_NAME'][0].get('herdr_session') or 'default')")
# 1. herdr alive with the right cmd (real native command, no lib.sh needed)
herdr --session "$SERVER" agent get "$SESSION_NAME" | python3 -c "
import sys, json
a = json.load(sys.stdin)['result']['agent']
print(f\"cmd={a['agent']} cwd={a['cwd']}\")
"
# 2. agent-sessions.yaml updated
python3 -c "
import yaml
d = yaml.safe_load(open('.mam/agent-sessions.yaml'))
s = [s for s in d['herdr_sessions'] if s['name'] == '$SESSION_NAME'][0]
print(f' status: {s[\"status\"]}')
print(f' pane.cmd_full: {s[\"pane\"][\"cmd_full\"]}')
"
# 3. TUI shows resumed conversation (real native command, no lib.sh needed)
sleep 5
herdr --session "$SERVER" agent read "$SESSION_NAME" --source visible --lines 30
# look for the previous message at top of the buffer (claude) or last_visible_status set (agy)
herdr list-panes/herdr capture-paneare tmux-compat pseudo-commands that only work aftersource .agents/skills/lib.sh— the realherdrbinary doesn't have those subcommands. This block uses only the realherdr agent get/herdr agent readcommands, so it works standalone.
When NOT to use this skill
- No saved session yet →
multi-agent-mux-create - Killing an existing session →
multi-agent-mux-stop - Just attaching →
herdr --session <server> agent attach <name>(no skill needed;<server>= the row'sherdr_session)