Files
multi-agent-mux/.agents/skills/multi-agent-mux-stop/SKILL.md
T
GodopuandClaude Opus 5 7988db9adf docs(issue-21): item 4 helpers, the submodule removal rule and the resume cwd rule in the design doc, compatibility matrix and skill docs
- design doc §3: the hand-assembled worktree/.venv/create recipe is replaced by
  worktree_prepare.sh --creator-agent (its BASE= line is the base to record);
  cleanup is worktree_remove.sh --stop; resume needs the original path.
- §4: what the helpers do - no .mam.env copy, the shared info/exclude, opt-in
  submodules (any init leaves modules/, which blocks a plain remove), removal
  of submodule worktrees with one --force after the checks, and never by hand.
- §5: item 4 is loop A on this branch; phase-1 items are done in v4.6.0.
- §7: create (control-root reconcile/watchdog, loud binding failure), resume
  (original cwd only, binding persisted when already running), stop (the
  removal helper's checks), monitor (register via create/prepare only).
- create / stop / resume SKILL.md: usage and exit codes of worktree_prepare.sh
  and worktree_remove.sh, the original-cwd rule and its exit code (1 -> 2).
  Frontmatter versions are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017R3Eg7MEpSWnm1oB9ZPP6G
2026-09-14 15:26:37 +00:00

10 KiB

name, description, version, author, license, platforms, environments, metadata
name description version author license platforms environments metadata
multi-agent-mux-stop Stop an agent herdr session (claude, antigravity/agy) and update .mam/agent-sessions.yaml. Default stops gracefully and marks status=stopped with conversation preserved for resume. Does NOT delete on-disk conversation artifacts (jsonl/db) — those are preserved unless --purge-conversation is passed. Use when ending a work session, switching to a different one, or cleaning up before a fresh start. 4.6.0 godopu MIT
linux
macos
terminal
herdr
hermes
tags related_skills prereq_skills
agent
herdr
claude
antigravity
agy
multi-agent
stop
terminate
cleanup
multi-agent-mux-create
multi-agent-mux-resume
multi-agent-mux-monitor
multi-agent-mux-create
multi-agent-mux-resume

Multi-Agent Stop — Stop an Agent herdr Session

Companion skills: multi-agent-mux-create (start), multi-agent-mux-resume (re-attach), multi-agent-mux-monitor (live status). Herdr Isolation: stop 명령은 YAML의 herdr_session 필드를 자동으로 파싱하여 해당 격리 서버의 세션을 안전하게 종료(kill)하므로, HERDR_SESSION_NAME 환경변수를 수동으로 지정할 필요가 없습니다. (--herdr-workspace는 CLI 대칭성을 위해 파서에서 허용되지만 소켓 라우팅에는 영향을 주지 않습니다.) Single source of truth: ./.mam/agent-sessions.yaml.

What this skill does

Stop an agent's herdr session gracefully, resolve and store the conversation ID, and mark the YAML entry (status=stopped). Preserves:

  • The herdr session's recorded pane.pid / cmd / cwd / mcp_attachments for audit
  • The agent's on-disk conversation (claude *.jsonl, agy conversations/*.db) — so the user can multi-agent-mux-resume later
  • The start_command so a future multi-agent-mux-create --session <name> reproduces the same herdr spec

The stop command is always graceful by default:

  1. Sends exit keys to the agent TUI (/exit for Claude, /exit for Agy) and waits 3 seconds.
  2. If still alive, issues herdr kill-session (SIGTERM) and waits 5 seconds.
  3. If still alive, kills the pane PID via SIGKILL (kill -9) as a last resort.
  4. Auto-captures the conversation ID into the row (claude_session_id_own/agy_conversation_id_own) before killing, ensuring the next resume uses a race-free tier-1 lookup.

Pre-flight

SESSION_NAME=<workspace>-creator-<agent>  # convention
AGENT=claude                              # claude | agy | hermes | grok | opencode | codex — always pass it
AGENT_SESSIONS_YAML=.mam/agent-sessions.yaml

# 1) Session is registered?
python3 -c "
import yaml
d = yaml.safe_load(open('$AGENT_SESSIONS_YAML'))
names = [s['name'] for s in d.get('herdr_sessions', [])]
if '$SESSION_NAME' not in names:
    print('NOT in YAML — refusing to stop (no audit trail). Use multi-agent-mux-create first, or pass --force-no-yaml.')
    raise SystemExit(1)
"

# 2) Already stopped?
ALREADY=$(python3 -c "
import yaml
d = yaml.safe_load(open('$AGENT_SESSIONS_YAML'))
s = [x for x in d['herdr_sessions'] if x['name']=='$SESSION_NAME'][0]
print(s.get('status', 'unknown'))
")
if [ "$ALREADY" = "stopped" ]; then
  echo "Already stopped."
fi

Workflow

# 1. Stop gracefully (default — captures ID, shuts down safely, status=stopped)
bash .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \
  --session "$SESSION_NAME" --agent "$AGENT"

# 2. Stop gracefully + record a custom stop reason
bash .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \
  --session "$SESSION_NAME" --agent "$AGENT" --reason api_error

# 3. Stop gracefully + clean up on-disk conversation (DANGEROUS)
#    — this prevents any future resume (status=terminated, resumable=false).
bash .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \
  --session "$SESSION_NAME" --agent "$AGENT" --purge-conversation

Idempotency: if the row is already status: stopped, the script prints already stopped (...) and exits 0 — re-running is a safe no-op.

--agent is the standard. Pass it on every invocation. If omitted, the script resolves the agent from the registry record — the row's agent field, then the session-name suffix, then pane.cmd — and exits 2 if none of the three resolve. The fallback exists for recovery, not as the normal calling convention: a session whose name carries no agent suffix (e.g. agy-creator-01) is only resolvable while its registry row survives.

State machine

running ──(stop default / --reason)────────► stopped      (resumable:true, conv preserved)
running ──(stop --purge-conversation --yes)► terminated   (resumable:false, conv deleted)
stopped ──(stop default … again)───────────► stopped      (idempotent no-op)

Fields written in STOP mode: status: stopped, stopped_at, stopped_at_epoch, stop_reason, termination_mode: graceful, claude_session_id_own/agy_conversation_id_own and resumable: true.

If --purge-conversation is used: status: terminated, terminated_at, terminated_at_epoch, termination_mode: purge and resumable: false.

The script:

  1. Verifies the session is in agent-sessions.yaml
  2. If delegate_job_id is set, automatically publishes a progress --detail "terminating" event to the multi-agent-mux-delegate-job registry
  3. Captures the last_visible_status from herdr capture-pane (so we have a final TUI snapshot for audit)
  4. Attempts graceful exit keys → SIGTERM kill-session → SIGKILL fallback
  5. For purge-conversation: deletes ~/.claude/projects/.../jsonl (claude) or, for agy, ~/.gemini/antigravity-cli/conversations/<uuid>.db with its -wal/-shm/-journal sidecars, brain/<uuid>/, annotations/<uuid>.pbtxt and presence/<uuid>.lock (the lock only once no live process references the conversation — Issue #10)
  6. Updates the YAML entry and SQLite database atomically
  7. If delegate_job_id is set, publishes a completed event to the multi-agent-mux-delegate-job registry

Removing an issue worktree: worktree_remove.sh (Issue #21 item 4)

bash .agents/skills/multi-agent-mux-stop/scripts/worktree_remove.sh --path <WT> [--stop] [--dry-run]

Every check runs first and changes nothing; a refusal exits 3:

  1. a linked worktree of the control root, not locked (git worktree unlock first - git refuses even --force on a locked one);
  2. running sessions under it only with --stop (stopped normally, conversations kept);
  3. no open job of those sessions - never cancelled automatically, they may belong to a running loop (the delegate job of a session --stop stops is cancelled by stop itself);
  4. a clean tree, submodules included (status --porcelain --ignore-submodules=none);
  5. inside submodules: no modification and no commit that is on no remote (HEAD included - a submodule sits on a detached HEAD).

Then it stops the sessions, re-checks the jobs and removes the worktree (the branch is kept): plain, or - when the worktree holds submodule state, which git refuses to remove otherwise - a single --force justified by checks 4 and 5. Do not run git worktree remove --force by hand on such a worktree: it deletes the worktree's own submodule repositories, unpushed commits included.

Exit codes: 0 removed, 1 stop/git failure, 2 not a worktree of the control root, 3 refused (nothing changed). Sessions created in a removed worktree cannot be resumed until a worktree exists at that path again.

Pitfalls

  • Don't delete on-disk artifacts by default — the agent's *.jsonl / conversations/*.db is the data that multi-agent-mux-resume needs. --purge-conversation is for when the user is genuinely done with the conversation and wants zero recovery chance.
  • YAML is append-only until you write a stop — if a previous run left the entry as running but herdr is actually dead (crash, host reboot), the YAML is stale. Running multi-agent-mux-stop will detect "herdr already dead, just update YAML" and proceed.
  • Don't delete the claude_session_id_own: null placeholder — when the user creates a fresh session with multi-agent-mux-create and never sent a message, the entry has claude_session_id_own: null. Stopping must preserve that field.
  • Monitor skill may still be tracking — if multi-agent-mux-monitor is running a heartbeat loop, stopping a session while it watches will trigger its herdr ls != yaml reconciliation. That's expected — let the monitor run, it will mark the entry as terminated on its own.

Verification

# 1. herdr gone (real native command — `herdr has-session` is a lib.sh
#    tmux-compat pseudo-command and needs `source .agents/skills/lib.sh` first)
herdr agent get "$SESSION_NAME" >/dev/null 2>&1 && echo "STILL ALIVE" || echo "OK: herdr gone"

# 2. YAML has stopped entry
python3 -c "
import yaml
d = yaml.safe_load(open('$AGENT_SESSIONS_YAML'))
s = [x for x in d['herdr_sessions'] if x['name']=='$SESSION_NAME'][0]
assert s['status'] == 'stopped', f'expected stopped, got {s[\"status\"]}'
assert s.get('stopped_at'), 'missing stopped_at'
print(f'OK: stopped at {s[\"stopped_at\"]}')
print(f'  preserved: pane.pid={s[\"pane\"][\"pid\"]}, cmd={s[\"pane\"][\"cmd\"]}, cwd={s[\"pane\"][\"cwd\"]}')
"

# 3. (if --purge-conversation) disk artifacts gone
[ -f "${CLAUDE_PROJECT_DIR:-$HOME/.claude/projects}/<projkey>/<uuid>.jsonl" ] && echo "WARN: jsonl still exists" || echo "OK: jsonl purged"

When NOT to use this skill

  • Just detaching → there's no herdr detach CLI command; press the herdr detach keybinding inside the pane, or just close the terminal. The herdr session keeps running.
  • Stopping the agent inside but keeping herdr → send Ctrl-C or /exit (claude) / Ctrl-D (agy) via herdr agent send <target> <text> (real native command; herdr send-keys is a lib.sh tmux-compat pseudo-command that needs source .agents/skills/lib.sh first). The herdr session stays but the agent process is gone.
  • Replacing an existing session with a new one → multi-agent-mux-stop first, then multi-agent-mux-create.