Merge-workspace review of PR #46 (codex R1-R3 and C1, claude O1 and O4). - R1: resume never attaches, and the server it exports does not reach the calling shell; the native herdr binary does not route on environment variables. The attach advice and the Verification block now name the server: `herdr --session <server> agent attach <name>`, with <server> the row's herdr_session. - R2: --dry-run in resume and create skips the spawn and the registry update, but the checks before it still run and on macOS remove the quarantine attribute from the agent binary. The option rows say so. - R3: test_no_deleted_body_came_back compares every current block with the deleted bodies by exact sha and by a whitespace-insensitive fingerprint (stored as base_fingerprint), independently of the inventory, so a deleted block cannot return by being listed again under class c. Replaying the review's probe (the deleted resume Workflow re-added and inventoried as c) fails the new test and passes the old one. - C1 / O1: the base create call block at 263-266 (class c) folded into the Workflow call is recorded under "superseded"; the 37 base blocks are all accounted for. - O4: create exit 42 also covers MAM_KILL_ON_READY_TIMEOUT=1; stop's no-op exit 0 does not apply with --purge-conversation. - The inventory's positions and shas are regenerated for the edited blocks; every classification is unchanged. No product code changed. 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-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.8.2 | godopu | MIT |
|
|
|
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_attachmentsfor audit - The agent's on-disk conversation (claude
*.jsonl, agyconversations/*.db) — so the user canmulti-agent-mux-resumelater - The
start_commandso a futuremulti-agent-mux-create --session <name>reproduces the same herdr spec
The stop command is always graceful by default:
- Sends exit keys to the agent TUI (
/exitfor Claude,/exitfor Agy) and waits 3 seconds. - If still alive, issues
herdr kill-session(SIGTERM) and waits 5 seconds. - If still alive, kills the pane PID via SIGKILL (
kill -9) as a last resort. - 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
stop_session.sh checks this itself: the YAML exists and the session is registered (otherwise exit 1), and a session already stopped is a no-op (exit 0).
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 --yes
--purge-conversation cannot be undone. Pass --yes only after the user has confirmed it.
| Exit | Meaning |
|---|---|
| 0 | Stopped; or already stopped (no-op, except with --purge-conversation, which still purges) |
| 1 | From the checks before the stop, nothing was changed: YAML not found, or the session is not registered. The registry update after the stop also exits 1 (the row disappeared from the YAML during the run) |
| 2 | Bad arguments (including the retired --mode / --capture-id / --graceful), or --agent omitted and not resolvable |
| 3 | --purge-conversation without --yes |
| 4 | Purge aborted: the herdr session survived the kill chain |
| other | The registry update after the stop (YAML / DB write) failed and its code is passed through |
Exits 2 and 3, and exit 1 from the pre-stop checks, come before anything is stopped, so nothing was changed. On exit 1 from the registry update, or an other exit, the session may already be down while its row still says running: check with multi-agent-mux-status, then run the stop again (it is idempotent).
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:
- Verifies the session is in agent-sessions.yaml
- If
delegate_job_idis set, automatically publishes aprogress --detail "terminating"event to the multi-agent-mux-delegate-job registry - Captures the
last_visible_statusfromherdr capture-pane(so we have a final TUI snapshot for audit) - Attempts graceful exit keys → SIGTERM kill-session → SIGKILL fallback
- For
purge-conversation: deletes~/.claude/projects/.../jsonl(claude) or, for agy,~/.gemini/antigravity-cli/conversations/<uuid>.dbwith its-wal/-shm/-journalsidecars,brain/<uuid>/,annotations/<uuid>.pbtxtandpresence/<uuid>.lock(the lock only once no live process references the conversation — Issue #10) - Updates the YAML entry and SQLite database atomically
- If
delegate_job_idis set, publishes acompletedevent 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:
- a linked worktree of the control root, not locked (
git worktree unlockfirst - git refuses even--forceon a locked one); - running sessions under it only with
--stop(stopped normally, conversations kept); - no open job of those sessions - never cancelled automatically, they may belong to a running loop (the delegate job of a session
--stopstops is cancelled by stop itself); - a clean tree, submodules included (
status --porcelain --untracked-files=all --ignore-submodules=none- astatus.showUntrackedFiles=nosetting cannot hide a file from a check that decides a forced removal); - inside submodules: no modification, no commit reachable from
HEADor any local ref that is on no remote (a submodule sits on a detached HEAD; a commit kept only by a tag or a note is protected too) and no stash (refs/stashis reachable from no branch and leaves the status clean) - checked in every repository under the worktree'smodules/, including deinitialised submodules (their repository stays there) and nested ones; a repository that cannot be checked refuses.
Then it stops the sessions, re-checks the jobs, runs checks 4 and 5 again (a session that is being stopped can still write, commit or stash - any change refuses with exit 3, sessions already stopped) 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/*.dbis the data thatmulti-agent-mux-resumeneeds.--purge-conversationis 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
runningbut herdr is actually dead (crash, host reboot), the YAML is stale. Runningmulti-agent-mux-stopwill detect "herdr already dead, just update YAML" and proceed. - Don't delete the
claude_session_id_own: nullplaceholder — when the user creates a fresh session withmulti-agent-mux-createand never sent a message, the entry hasclaude_session_id_own: null. Stopping must preserve that field. - Monitor skill may still be tracking — if
multi-agent-mux-monitoris running a heartbeat loop, stopping a session while it watches will trigger itsherdr ls != yamlreconciliation. That's expected — let the monitor run, it will mark the entry asterminatedon 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 detachCLI 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-Cor/exit(claude) /Ctrl-D(agy) viaherdr agent send <target> <text>(real native command;herdr send-keysis a lib.sh tmux-compat pseudo-command that needssource .agents/skills/lib.shfirst). The herdr session stays but the agent process is gone. - Replacing an existing session with a new one →
multi-agent-mux-stopfirst, thenmulti-agent-mux-create.