feat(create,resume,stop): verify and standardize --herdr-session option with full peer review

- Standardize --herdr-session as primary flag with --herdr-server alias across create_session.sh, resume_session.sh, update_yaml_resumed.sh, and stop_session.sh
- Guard HERDR_SESSION_NAME in create_session.sh from being overwritten by workspace slug defaults when explicitly provided
- Forward explicit --herdr-session from resume_session.sh to update_yaml_resumed.sh and force-update row metadata
- Add 5 new Tier 2 component tests covering CLI dry-run parsing, usage matching, default preservation, YAML serialization, and resume propagation
- Update multi-agent-mux-create/SKILL.md documentation
- Verified by autonomous multi-agent loop with unanimous PASS verdicts from Claude and Cline
This commit is contained in:
2026-08-24 11:21:21 +09:00
parent f7e1513585
commit d7ab69ef68
8 changed files with 633 additions and 56 deletions
@@ -9,17 +9,19 @@ source "$LIB_SH"
usage() {
cat <<EOF
Usage: $0 --workspace <path> --agent <claude|agy|hermes|cline> --session <name> [--dry-run]
Usage: $0 --workspace <path> --agent <claude|agy|hermes|cline> --session <name> [options]
Options:
--dry-run Simulates resume flow (resolves binary, environment) without writing
any updates to YAML or DB. Safe to execute inside active write transactions.
--herdr-session NAME specify isolated herdr session name (alias: --herdr-server)
--dry-run Simulates resume flow (resolves binary, environment) without writing
any updates to YAML or DB. Safe to execute inside active write transactions.
EOF
}
WORKSPACE=""
AGENT=""
SESSION_NAME=""
HERDR_SERVER_OPT=""
DRY_RUN=0
@@ -28,6 +30,7 @@ while [ $# -gt 0 ]; do
--workspace) WORKSPACE="$2"; shift 2 ;;
--agent) AGENT="$2"; shift 2 ;;
--session) SESSION_NAME="$2"; shift 2 ;;
--herdr-session|--herdr-server) HERDR_SERVER_OPT="$2"; shift 2 ;;
--dry-run) DRY_RUN=1; shift ;;
-h|--help) usage; exit 0 ;;
*) echo "ERROR: unknown arg: $1" >&2; exit 2 ;;
@@ -51,8 +54,12 @@ if [ -z "$UUID" ]; then
exit 1
fi
HERDR_SESSION_NAME="$(resolve_herdr_session "$SESSION_NAME" "$WORKSPACE")"
export HERDR_SESSION_NAME
if [ -n "$HERDR_SERVER_OPT" ]; then
export HERDR_SESSION_NAME="$HERDR_SERVER_OPT"
else
HERDR_SESSION_NAME="$(resolve_herdr_session "$SESSION_NAME" "$WORKSPACE")"
export HERDR_SESSION_NAME
fi
# 2. If herdr is alive, print warning or attach.
if herdr has-session -t "$SESSION_NAME" 2>/dev/null; then
@@ -63,7 +70,8 @@ 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" --workspace "$WORKSPACE"
--session "$SESSION_NAME" --uuid "$UUID" --agent "$AGENT" --workspace "$WORKSPACE" \
--herdr-session "$HERDR_SESSION_NAME"
exit 0
fi
@@ -126,6 +134,7 @@ sleep 2
# 5. Update agent-sessions.yaml: status running, last_visible_status
bash "$(dirname "${BASH_SOURCE[0]}")/update_yaml_resumed.sh" \
--session "$SESSION_NAME" --uuid "$UUID" --agent "$AGENT" --workspace "$WORKSPACE"
--session "$SESSION_NAME" --uuid "$UUID" --agent "$AGENT" --workspace "$WORKSPACE" \
--herdr-session "$HERDR_SESSION_NAME"
echo "Successfully resumed $SESSION_NAME ($AGENT)"
@@ -11,7 +11,7 @@ source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/lib.sh"
usage() {
cat <<EOF
Usage: $0 --session <name> --uuid <id> [--agent claude|agy|hermes|cline]
Usage: $0 --session <name> --uuid <id> [--agent claude|agy|hermes|cline] [--herdr-session <name>]
EOF
}
@@ -20,6 +20,7 @@ UUID=""
AGENT=""
WORKSPACE=""
ROLE=""
HERDR_SERVER_OPT=""
while [ $# -gt 0 ]; do
case "$1" in
@@ -28,6 +29,7 @@ while [ $# -gt 0 ]; do
--agent) AGENT="$2"; shift 2 ;;
--workspace) WORKSPACE="$2"; shift 2 ;;
--role) ROLE="$2"; shift 2 ;;
--herdr-session|--herdr-server) HERDR_SERVER_OPT="$2"; shift 2 ;;
-h|--help) usage; exit 0 ;;
*) echo "ERROR: unknown arg: $1" >&2; exit 2 ;;
esac
@@ -37,8 +39,14 @@ done
[ -n "$UUID" ] || { echo "ERROR: --uuid required" >&2; exit 2; }
[ -f "$AGENT_SESSIONS_YAML" ] || { echo "ERROR: $AGENT_SESSIONS_YAML not found" >&2; exit 1; }
HERDR_SESSION_NAME="$(resolve_herdr_session "$SESSION_NAME" "${WORKSPACE:-}")"
export HERDR_SESSION_NAME
if [ -n "$HERDR_SERVER_OPT" ]; then
export HERDR_SESSION_NAME="$HERDR_SERVER_OPT"
export HERDR_SERVER_OPT_EXPLICIT="1"
else
HERDR_SESSION_NAME="$(resolve_herdr_session "$SESSION_NAME" "${WORKSPACE:-}")"
export HERDR_SESSION_NAME
export HERDR_SERVER_OPT_EXPLICIT="0"
fi
# --agent 미지정 시 레지스트리 기록으로 해석 (B-21).
# ① row['agent'] → ② 세션명 접미사 → ③ pane.cmd 순. 셋 다 실패하면
@@ -84,7 +92,7 @@ for s in d.get('herdr_sessions', []):
atomic_dump_yaml "$AGENT_SESSIONS_YAML" \
SESSION_NAME="$SESSION_NAME" UUID="$UUID" AGENT="$AGENT" NOW_ISO="$NOW_ISO" \
NOW_EPOCH="$NOW_EPOCH" TARGET_WORKSPACE="${WORKSPACE:-$WORKSPACE_ROOT}" ROLE="$ROLE" \
PANE_PID="$PANE_PID" CHILD_PID="$CHILD_PID" <<'PYEOF'
PANE_PID="$PANE_PID" CHILD_PID="$CHILD_PID" HERDR_SERVER_OPT_EXPLICIT="${HERDR_SERVER_OPT_EXPLICIT:-0}" <<'PYEOF'
name = os.environ['SESSION_NAME']
uuid = os.environ['UUID']
agent = os.environ['AGENT']
@@ -111,6 +119,7 @@ if target is None:
'herdr_session_created_at': now,
'herdr_session_epoch': epoch,
'herdr_session': server_name,
'herdr_server': server_name,
'delegate_job_id': None,
'pane': {'index': 0, 'pid': int(pane_pid) if pane_pid.isdigit() else 0, 'cmd': agent, 'cwd': ws_root},
'start_command': f'HERDR_SESSION_NAME={server_name} herdr agent attach {name}',
@@ -118,6 +127,16 @@ if target is None:
'kill_command': f'HERDR_SESSION_NAME={server_name} herdr kill-session -t {name}',
}
d.setdefault('herdr_sessions', []).append(target)
else:
sn = os.environ.get('HERDR_SESSION_NAME')
is_explicit = os.environ.get('HERDR_SERVER_OPT_EXPLICIT') == '1'
if sn:
if is_explicit or not target.get('herdr_session'):
target['herdr_session'] = sn
target['herdr_server'] = sn
target['start_command'] = f'HERDR_SESSION_NAME={sn} herdr agent attach {name}'
target['attach_command'] = f'HERDR_SESSION_NAME={sn} herdr agent attach {name}'
target['kill_command'] = f'HERDR_SESSION_NAME={sn} herdr kill-session -t {name}'
target['status'] = 'running'
target.pop('terminated_at', None)