Review loop (--review-only, --all-reviewer, focused on this Mac: macOS, /usr/bin/python3 3.9.6, bash 3.2): round 1 (@6f4f40a) agy and grok PASS, claude and codex NOT PASS (a non-uuid id `memory` purged the auto-memory dir, a linked dir below a root was followed, a NaN startedAt counted as dead), sent as a PR comment and fixed by the author in eleven commits; round 2 (@2a8d738) confirmed all three fixed but 4/4 NOT PASS on one test holder (`sh -c "sleep 120" <uuid>` loses the uuid on macOS /bin/sh), fixed in de6f3fc; round 3 (@de6f3fc) 4/4 PASS. Full suite on macOS / Python 3.9.6 @de6f3fc: 1934 passed, 56 skipped, 1 error (the known #55 false positive: agy conversations of another workspace). The real ~/.claude, /private/tmp/claude-<uid> and herdr panes unchanged. Also names lib_py.delegation.logroot in eager.CORE_MODULES (the v4.9.4 review's non-blocking note). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
140 lines
11 KiB
Markdown
140 lines
11 KiB
Markdown
---
|
|
name: multi-agent-mux-resume
|
|
description: "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."
|
|
version: 4.9.5
|
|
author: godopu
|
|
license: MIT
|
|
platforms: [linux, macos]
|
|
environments: [terminal, herdr]
|
|
metadata:
|
|
hermes:
|
|
tags: [agent, herdr, claude, antigravity, agy, multi-agent, context, resume, session-id]
|
|
related_skills: [multi-agent-mux-create, multi-agent-mux-stop, multi-agent-mux-monitor, claude-code]
|
|
prereq_skills: [multi-agent-mux-create]
|
|
---
|
|
|
|
# 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_NAME` env var를 create에서 설정한 경우, 동일 서버에서 동작합니다. 자세한 격리 패턴은 [multi-agent-mux-create/SKILL.md](../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:
|
|
|
|
1. **herdr is dead, conversation lives** — `agent-sessions.yaml` has the UUID. The JSONL/db is on disk. Re-spawn the herdr session + run `claude -r <id>` / `agy --conversation <id>`.
|
|
2. **herdr is alive but empty** — You started a session with `multi-agent-mux-create` but 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 `*.jsonl` in `$CLAUDE_PROJECT_DIR/<workspace-key>/` (defaults to `~/.claude/projects/`) for claude.
|
|
3. **herdr is alive AND the agent inside is already running** — `resume_session.sh` only updates the row and exits 0. It does not attach and does not check the agent's permission flags (#45); attach with `herdr --session <server> agent attach <name>`, where `<server>` is the row's `herdr_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.sh` resolves it via `find_workspace_uuid` tier-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.sh` transitions `stopped → running` and **clears
|
|
the stop metadata** (`stopped_at`, `stopped_at_epoch`, `stop_reason`, `resumable`)
|
|
along with the usual `terminated_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:
|
|
|
|
1. **`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 by `multi-agent-mux-stop` right before teardown (tier-1, race-free).
|
|
2. **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
|
|
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.
|
|
|
|
- `--workspace` differing from `pane.cwd` → **exit 2** with the path to use (claude/grok used to fail with exit 1 "No saved session").
|
|
- `pane.cwd` no longer exists (worktree removed) → **exit 2**; recreate the worktree at that path to resume.
|
|
- Rows without `pane.cwd` are 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 -r` requires 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 `--conversation` flag name varies by version** — older versions used `--resume` or `-r`. Check `agy --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.sh` re-passes them through the adapter `resume-spec`.
|
|
- **Don't resume if the session is brand new and empty** — `multi-agent-mux-create` already set up an empty container; sending a probe message ("init") is the right way to materialize a session id, NOT `claude -r` with a placeholder.
|
|
- **`agy --conversation <id>` will fail if the conversation was deleted from disk** — check `~/.gemini/antigravity-cli/conversations/<uuid>.db` exists before attempting resume. If missing, the conversation is gone; you need a fresh session via `multi-agent-mux-create`.
|
|
|
|
## Verification
|
|
|
|
```bash
|
|
# 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-pane` are tmux-compat pseudo-commands that only
|
|
> work after `source .agents/skills/lib.sh` — the real `herdr` binary doesn't have those
|
|
> subcommands. This block uses only the real `herdr agent get`/`herdr agent read` commands,
|
|
> 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's `herdr_session`)
|