Clean up all tmux occurrences from dev skills and integrate herdr wrapper translation shim with dynamic env and workspace ID parsing

This commit is contained in:
2026-07-19 16:57:00 +09:00
parent 42b54d7643
commit 30e606b0fa
20 changed files with 480 additions and 396 deletions
+25 -25
View File
@@ -1,14 +1,14 @@
---
name: multi-agent-mux-resume
description: "Resume an existing agent (claude, antigravity/agy) conversation by UUID into a tmux session. Reads .mam/agent-sessions.yaml for the saved session/conversation id, spawns (or reuses) a tmux 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 tmux died but the agent's conversation is still on disk."
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: 1.0.0
author: godopu
license: MIT
platforms: [linux, macos]
environments: [terminal, tmux]
environments: [terminal, herdr]
metadata:
hermes:
tags: [agent, tmux, claude, antigravity, agy, multi-agent, context, resume, session-id]
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]
---
@@ -16,18 +16,18 @@ metadata:
# 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).
> **Tmux Isolation**: `TMUX_SERVER_NAME` env var를 create에서 설정한 경우, 동일 서버에서 동작합니다. 자세한 격리 패턴은 [multi-agent-mux-create/SKILL.md](../multi-agent-mux-create/SKILL.md) 참조.
> **Herdr Isolation**: `HERDR_SERVER_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 tmux session (the container), then run the agent inside with a specific session id (the data) so the previous conversation's context is restored.
**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. **tmux is dead, conversation lives**`agent-sessions.yaml` has the UUID. The JSONL/db is on disk. Re-spawn the tmux session + run `claude -r <id>` / `agy --conversation <id>`.
2. **tmux 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. **tmux is alive AND the agent inside is already running** — Just attach. No re-spawn needed.
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** — Just attach. No re-spawn needed.
### Resuming a `stopped` session (`stopped → running`)
@@ -64,7 +64,7 @@ WORKSPACE=/path/to/project
AGENT=claude # or agy or hermes
SESSION_NAME=<workspace>-creator-<agent> # same convention as multi-agent-mux-create
# Resolve the isolated tmux server name & load isolation utils
# Resolve the isolated herdr server name & load isolation utils
source .agents/skills/lib.sh
# 1. Resolve the session id (T5: pass session name for target-row isolation check)
@@ -76,12 +76,12 @@ if [ -z "$UUID" ]; then
exit 1
fi
export TMUX_SERVER_NAME="$(resolve_tmux_server "$SESSION_NAME")"
export HERDR_SERVER_NAME="$(resolve_herdr_server "$SESSION_NAME")"
# 2. If tmux is alive, attach. Done.
if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
echo "tmux '$SESSION_NAME' already running. Attaching..."
exec tmux attach -t "$SESSION_NAME"
# 2. If herdr is alive, attach. Done.
if herdr has-session -t "$SESSION_NAME" 2>/dev/null; then
echo "herdr '$SESSION_NAME' already running. Attaching..."
exec herdr attach -t "$SESSION_NAME"
fi
# 3. Resolve isolation settings for this session (T4/T5 re-apply)
@@ -108,7 +108,7 @@ try:
d = yaml.safe_load(f) or {}
except Exception:
pass
for s in d.get('tmux_sessions', []):
for s in d.get('herdr_sessions', []):
if s.get('name') == name:
print(json.dumps(s.get('isolation') or {}))
raise SystemExit(0)
@@ -138,20 +138,20 @@ if [ -n "$ISO_ARGS" ]; then
CMD_FULL="$CMD_FULL $ISO_ARGS"
fi
# 4. Spawn new tmux session + run agent with the saved id (and re-applied isolation)
# 4. Spawn new herdr session + run agent with the saved id (and re-applied isolation)
case "$AGENT" in
claude)
if [ -z "$ISO_ROOT" ] && [ -x "$HOME/.local/bin/canary-projects-multi-agent-mux-creator-claude" ]; then
START_CMD="tmux new-session -d -s \"$SESSION_NAME\" -x 140 -y 40 -c \"$WORKSPACE\" \"$HOME/.local/bin/canary-projects-multi-agent-mux-creator-claude\""
START_CMD="herdr new-session -d -s \"$SESSION_NAME\" -x 140 -y 40 -c \"$WORKSPACE\" \"$HOME/.local/bin/canary-projects-multi-agent-mux-creator-claude\""
else
START_CMD="tmux new-session -d -s \"$SESSION_NAME\" -x 140 -y 40 -c \"$WORKSPACE\" \"$CMD_FULL\""
START_CMD="herdr new-session -d -s \"$SESSION_NAME\" -x 140 -y 40 -c \"$WORKSPACE\" \"$CMD_FULL\""
fi
eval "$START_CMD"
# auto-handle trust / bypass dialogs
handle_startup_dialogs "$SESSION_NAME" 20
;;
agy|hermes|cline)
eval "tmux new-session -d -s \"$SESSION_NAME\" -x 140 -y 40 -c \"$WORKSPACE\" \"$CMD_FULL\""
eval "herdr new-session -d -s \"$SESSION_NAME\" -x 140 -y 40 -c \"$WORKSPACE\" \"$CMD_FULL\""
;;
esac
@@ -161,7 +161,7 @@ bash .agents/skills/multi-agent-mux-resume/scripts/update_yaml_resumed.sh \
--session "$SESSION_NAME" --uuid "$UUID"
# 5. Attach
tmux attach -t "$SESSION_NAME"
herdr attach -t "$SESSION_NAME"
```
## Pitfalls
@@ -175,21 +175,21 @@ tmux attach -t "$SESSION_NAME"
## Verification
```bash
# 1. tmux alive with the right cmd
tmux list-panes -t "$SESSION_NAME" -F 'cmd=#{pane_current_command} cwd=#{pane_current_path}'
# 1. herdr alive with the right cmd
herdr list-panes -t "$SESSION_NAME" -F 'cmd=#{pane_current_command} cwd=#{pane_current_path}'
# 2. agent-sessions.yaml updated
python3 -c "
import yaml
d = yaml.safe_load(open('.mam/agent-sessions.yaml'))
s = [s for s in d['tmux_sessions'] if s['name'] == '$SESSION_NAME'][0]
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 (capture-pane to verify)
sleep 5
tmux capture-pane -t "$SESSION_NAME" -p -S -30
herdr capture-pane -t "$SESSION_NAME" -p -S -30
# look for the previous message at top of the buffer (claude) or last_visible_status set (agy)
```
@@ -197,4 +197,4 @@ tmux capture-pane -t "$SESSION_NAME" -p -S -30
- **No saved session yet** → `multi-agent-mux-create`
- **Killing an existing session** → `multi-agent-mux-stop`
- **Just attaching** → `tmux attach -t <name>` (no skill needed)
- **Just attaching** → `herdr attach -t <name>` (no skill needed)
@@ -37,11 +37,11 @@ if [ -z "$UUID" ]; then
exit 1
fi
export TMUX_SERVER_NAME="$(resolve_tmux_server "$SESSION_NAME")"
export HERDR_SERVER_NAME="$(resolve_herdr_workspace "$SESSION_NAME")"
# 2. If tmux is alive, print warning or attach.
if tmux has-session -t "$SESSION_NAME" 2>/dev/null; then
echo "tmux '$SESSION_NAME' already running."
# 2. If herdr is alive, print warning or attach.
if herdr has-session -t "$SESSION_NAME" 2>/dev/null; then
echo "herdr '$SESSION_NAME' already running."
# Just update YAML to make sure it's set to running
bash "$(dirname "${BASH_SOURCE[0]}")/update_yaml_resumed.sh" \
--session "$SESSION_NAME" --uuid "$UUID" --agent "$AGENT"
@@ -72,7 +72,7 @@ try:
d = yaml.safe_load(f) or {}
except Exception:
pass
for s in d.get('tmux_sessions', []):
for s in d.get('herdr_sessions', []):
if s.get('name') == name:
print(json.dumps(s.get('isolation') or {}))
raise SystemExit(0)
@@ -87,7 +87,7 @@ if [ -n "$ISO_ROOT" ]; then
echo "Re-applying isolation: root=$ISO_ROOT env=$ISO_ENV args=$ISO_ARGS"
fi
# Resolve absolute path of the agent command to prevent tmux PATH inheritance issues (especially on macOS)
# Resolve absolute path of the agent command to prevent herdr PATH inheritance issues (especially on macOS)
RESOLVED_BIN="$AGENT"
if [ "$AGENT" = "cline" ]; then
if command -v cline >/dev/null 2>&1; then
@@ -120,9 +120,8 @@ if [ -n "$ISO_ARGS" ]; then
CMD_FULL="$CMD_FULL $ISO_ARGS"
fi
# 4. Spawn new agent session via herdr
herdr workspace create --label "${TMUX_SERVER_NAME:-default}" --cwd "$WORKSPACE" >/dev/null 2>&1 || true
herdr agent start "$SESSION_NAME" --workspace "${TMUX_SERVER_NAME:-default}" --cwd "$WORKSPACE" -- $CMD_FULL
# 4. Spawn new agent session (delegates to herdr translation shim)
_herdr new-session -d -s "$SESSION_NAME" -c "$WORKSPACE" "$CMD_FULL"
if [ "$AGENT" = "claude" ]; then
# auto-handle trust / bypass dialogs
handle_startup_dialogs "$SESSION_NAME" 20
@@ -33,7 +33,7 @@ done
[ -n "$UUID" ] || { echo "ERROR: --uuid required" >&2; exit 2; }
[ -f "$AGENT_SESSIONS_YAML" ] || { echo "ERROR: $AGENT_SESSIONS_YAML not found" >&2; exit 1; }
export TMUX_SERVER_NAME="$(resolve_tmux_server "$SESSION_NAME")"
export HERDR_SERVER_NAME="$(resolve_herdr_workspace "$SESSION_NAME")"
# --agent 미지정 시 이름 suffix 로 fallback (P1-F: 가능하면 --agent 명시)
if [ -z "$AGENT" ]; then
@@ -48,8 +48,8 @@ fi
NOW_ISO=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
# 새 tmux pane pid / 자식 pid 를 bash 에서 캡처 (env 로 전달, P1-B)
PANE_PID=$(tmux list-panes -t "$SESSION_NAME" -F '#{pane_pid}' 2>/dev/null | head -1 || true)
# 새 herdr pane pid / 자식 pid 를 bash 에서 캡처 (env 로 전달, P1-B)
PANE_PID=$(herdr list-panes -t "$SESSION_NAME" -F '#{pane_pid}' 2>/dev/null | head -1 || true)
PANE_PID="${PANE_PID:-}"
CHILD_PID=0
if { [ "$AGENT" = "agy" ] || [ "$AGENT" = "hermes" ] || [ "$AGENT" = "cline" ]; } && [ -n "$PANE_PID" ]; then
@@ -61,7 +61,7 @@ DELEGATE_JOB_ID=$(MAM_STATE_JSON="$(load_state_json)" SESSION_NAME="$SESSION_NAM
import sys, os, json
name = os.environ['SESSION_NAME']
d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
for s in d.get('tmux_sessions', []):
for s in d.get('herdr_sessions', []):
if s.get('name') == name:
print(s.get('delegate_job_id', '') or '')
sys.exit(0)
@@ -77,7 +77,7 @@ now = os.environ['NOW_ISO']
pane_pid = os.environ.get('PANE_PID', '')
target = None
for s in d.get('tmux_sessions', []):
for s in d.get('herdr_sessions', []):
if s.get('name') == name:
target = s
break