refactor(isolation): simplify agent session isolation and remove legacy home-isolation helpers

This commit is contained in:
2026-07-23 22:05:29 +09:00
parent 15ffc8f6bb
commit f0a2103edf
7 changed files with 91 additions and 347 deletions
@@ -36,8 +36,8 @@ Options:
--submit-job PROMPT submit a job to multi-agent-mux-delegate-job registry with the given prompt
--onboard automatically submit a project alignment/orientation job to the new agent
--no-onboard disable automatic onboarding job submission
--no-isolate disable state isolation (shares global configuration/history)
[default: isolated mode is always active]
--no-isolate legacy flag (no-op; all sessions use global configuration)
--isolate legacy flag (no-op; all sessions use global configuration)
-h, --help this help
EOF
}
@@ -65,8 +65,8 @@ while [ $# -gt 0 ]; do
--submit-job) SUBMIT_JOB_PROMPT="$2"; shift 2 ;;
--onboard) ONBOARD=1; shift ;;
--no-onboard) ONBOARD=0; shift ;;
--isolate) ISOLATE=1; shift ;; # legacy compatibility
--no-isolate) ISOLATE=0; shift ;;
--isolate) echo "NOTE: --isolate/--no-isolate is a no-op — config-home isolation was removed; sessions always use global config." >&2; shift ;; # legacy compatibility
--no-isolate) echo "NOTE: --isolate/--no-isolate is a no-op — config-home isolation was removed; sessions always use global config." >&2; shift ;;
-h|--help) usage; exit 0 ;;
*) echo "ERROR: unknown arg: $1" >&2; usage; exit 2 ;;
esac
@@ -121,37 +121,14 @@ if _herdr has-session -t "$SESSION_NAME" 2>/dev/null; then
exit 3
fi
# T3: 세션 격리 프로비저닝 (all-L2) — 격리 홈 생성 + auth/설정 심링크 시딩
# (implementation_plan.session_isolation.md Rev.3 / Phase 0 실측 매트릭스 기준)
ISOLATION_UUID=""
ISOLATION_ROOT=""
ISOLATION_LEVER=""
ISOLATION_SEEDED=""
if [ "$ISOLATE" = "1" ]; then
command -v uuidgen >/dev/null || { echo "ERROR: uuidgen not found (required for --isolate)" >&2; exit 1; }
WORKSPACE_ABS="$(cd "$WORKSPACE" && pwd)"
ISOLATION_UUID="$(uuidgen)"
ISOLATION_ROOT="$WORKSPACE_ABS/.mam/agent_homes/$ISOLATION_UUID"
ISOLATION_LEVER="$(isolation_lever "$AGENT")"
if [ "$DRY_RUN" = "1" ]; then
echo "[dry-run] would provision isolation: lever=$ISOLATION_LEVER root=$ISOLATION_ROOT"
else
ISOLATION_SEEDED="$(provision_isolation "$AGENT" "$ISOLATION_ROOT")"
echo "isolation: lever=$ISOLATION_LEVER root=$ISOLATION_ROOT"
fi
fi
# Config-home isolation was removed in favor of global config + process/UUID isolation.
# herdr 세션 띄우기
LOCAL_BIN="${LOCAL_BIN:-$HOME/.local/bin}"
WRAPPER="$LOCAL_BIN/$SESSION_NAME"
# cmd_full 결정 — T4: isolation 디스패치(env prefix / CLI args)를 시작 명령에 주입.
# 격리 미사용 시 기존 문자열과 byte-identical (V4 회귀 0).
ISO_ENV_PREFIX=""
ISO_CMD_ARGS=""
if [ -n "$ISOLATION_ROOT" ]; then
ISO_ENV_PREFIX="$(isolation_env_prefix "$AGENT" "$ISOLATION_ROOT")"
ISO_CMD_ARGS="$(isolation_cmd_args "$AGENT" "$ISOLATION_ROOT")"
if [ -z "${HERDR_SERVER_NAME:-}" ] || [ "$HERDR_SERVER_NAME" = "default" ]; then
export HERDR_SERVER_NAME="multi-agent-mux"
fi
# Resolve absolute path of the agent command to prevent herdr PATH inheritance issues (especially on macOS)
@@ -161,7 +138,9 @@ if [ "$AGENT" = "cline" ]; then
RESOLVED_BIN="$(command -v cline)"
fi
else
if command -v "$AGENT" >/dev/null 2>&1; then
if [ "$AGENT" = "claude" ] && [ -x "$HOME/.nvm/versions/node/v24.15.0/bin/claude" ]; then
RESOLVED_BIN="$HOME/.nvm/versions/node/v24.15.0/bin/claude"
elif command -v "$AGENT" >/dev/null 2>&1; then
RESOLVED_BIN="$(command -v "$AGENT")"
fi
fi
@@ -172,25 +151,27 @@ if [ "$(uname)" = "Darwin" ] && [ -f "$RESOLVED_BIN" ]; then
fi
case "$AGENT" in
claude) CMD_FULL="${ISO_ENV_PREFIX}${RESOLVED_BIN} --dangerously-skip-permissions" ;;
agy) CMD_FULL="${ISO_ENV_PREFIX}${RESOLVED_BIN} --dangerously-skip-permissions" ;;
hermes) CMD_FULL="${ISO_ENV_PREFIX}${RESOLVED_BIN}" ;;
cline) CMD_FULL="${RESOLVED_BIN} -i${ISO_CMD_ARGS:+ $ISO_CMD_ARGS}" ;;
claude) CMD_FULL="${RESOLVED_BIN} --dangerously-skip-permissions" ;;
agy) CMD_FULL="${RESOLVED_BIN} --dangerously-skip-permissions" ;;
hermes) CMD_FULL="${RESOLVED_BIN}" ;;
cline) CMD_FULL="${RESOLVED_BIN} -i" ;;
esac
spawn() {
if [ -z "${HERDR_SERVER_NAME:-}" ] || [ "$HERDR_SERVER_NAME" = "default" ]; then
export HERDR_SERVER_NAME="multi-agent-mux"
fi
case "$AGENT" in
claude)
# 격리 시 wrapper 경로는 env 주입을 운반하지 못하므로 인라인 spawn 강제
if [ "$ISOLATE" != "1" ] && { { [ -x "$WRAPPER" ] && [ "$(basename "$WRAPPER")" != "claude" ]; } || [ "$USE_WRAPPER" = "1" ]; }; then
if { [ -x "$WRAPPER" ] && [ "$(basename "$WRAPPER")" != "claude" ]; } || [ "$USE_WRAPPER" = "1" ]; then
nohup "$WRAPPER" >/dev/null 2>&1 &
disown
else
_herdr new-session -d -s "$SESSION_NAME" -x 140 -y 40 -c "$WORKSPACE" "$CMD_FULL"
HERDR_SERVER_NAME="$HERDR_SERVER_NAME" _herdr new-session -d -s "$SESSION_NAME" -x 140 -y 40 -c "$WORKSPACE" "$CMD_FULL"
fi
;;
agy|hermes|cline)
_herdr new-session -d -s "$SESSION_NAME" -x 140 -y 40 -c "$WORKSPACE" "$CMD_FULL"
HERDR_SERVER_NAME="$HERDR_SERVER_NAME" _herdr new-session -d -s "$SESSION_NAME" -x 140 -y 40 -c "$WORKSPACE" "$CMD_FULL"
;;
*) echo "ERROR: --agent must be claude, agy, hermes or cline, got: $AGENT" >&2; exit 2 ;;
esac
@@ -209,21 +190,20 @@ cleanup_herdr_on_error() {
if [ $exit_code -ne 0 ]; then
echo "⚠️ Error occurred during initialization. Rolling back and killing herdr session '$SESSION_NAME'..." >&2
_herdr kill-session -t "$SESSION_NAME" 2>/dev/null || true
# T3 rollback: 이 세션용으로 프로비저닝한 격리 홈 제거 (경로 가드 후 rm)
if [ -n "$ISOLATION_ROOT" ] && [ -d "$ISOLATION_ROOT" ]; then
case "$ISOLATION_ROOT" in
*/.mam/agent_homes/*) rm -rf "$ISOLATION_ROOT" ;;
esac
fi
fi
}
trap cleanup_herdr_on_error EXIT
export HERDR_SERVER_NAME="${HERDR_SERVER_NAME:-$(resolve_herdr_server "$SESSION_NAME")}"
# TUI 준비 대기
if ! wait_for_tui_ready "$SESSION_NAME" "$AGENT"; then
echo "ERROR: agent TUI never became ready — aborting (rollback via trap)" >&2
exit 1
fi
if [ "$AGENT" = "claude" ]; then
handle_startup_dialogs "$SESSION_NAME" 15
fi
# pane 메타 캡처
PANE_PID=$(_herdr list-panes -t "$SESSION_NAME" -F '#{pane_pid}' 2>/dev/null || echo "")
@@ -232,11 +212,9 @@ PANE_CMD=$(_herdr list-panes -t "$SESSION_NAME" -F '#{pane_current_command}' 2>/
HERDR_EPOCH=$(date +%s)
NOW_ISO=$(date -u +'%Y-%m-%dT%H:%M:%SZ')
# 시작 명령 (CMD_FULL 은 spawn 전에 isolation 디스패치를 반영해 확정됨 — T4)
# 시작 명령
# NOTE: this must match what `spawn()` actually ran above — env-var-driven
# isolation (HERDR_SERVER_NAME picked up by the lib.sh shim), not a
# `--workspace <label>` flag (real `herdr agent start --workspace` wants an
# actual workspace id like "w2", which HERDR_SERVER_NAME is not).
# herdr server shim (HERDR_SERVER_NAME picked up by the lib.sh shim).
START_CMD="HERDR_SERVER_NAME=${HERDR_SERVER_NAME:-default} herdr new-session -d -s \"$SESSION_NAME\" -x 140 -y 40 -c \"$WORKSPACE\" \"$CMD_FULL\""
# If --onboard is specified, automatically build the onboarding prompt
@@ -285,9 +263,7 @@ atomic_dump_yaml "$AGENT_SESSIONS_YAML" \
HERDR_EPOCH="$HERDR_EPOCH" PANE_PID="$PANE_PID" PANE_CWD="$PANE_CWD" \
CMD_FULL="$CMD_FULL" START_CMD="$START_CMD" CHILD_PID="$CHILD_PID" \
HERDR_SERVER_NAME="${HERDR_SERVER_NAME:-default}" \
DELEGATE_JOB_ID="$DELEGATE_JOB_ID" ROLE="$ROLE" \
ISOLATION_UUID="$ISOLATION_UUID" ISOLATION_ROOT="$ISOLATION_ROOT" \
ISOLATION_LEVER="$ISOLATION_LEVER" ISOLATION_SEEDED="$ISOLATION_SEEDED" <<'PYEOF'
DELEGATE_JOB_ID="$DELEGATE_JOB_ID" ROLE="$ROLE" <<'PYEOF'
name = os.environ['SESSION_NAME']
agent = os.environ['AGENT']
role = os.environ['ROLE']
@@ -331,15 +307,7 @@ entry = {
'kill_command': f'HERDR_SERVER_NAME={server_name} herdr kill-session -t {name}',
}
# T5: isolation 블록 영속화 (all-L2) — resume/resolve/stop 이 재적용의 단일 소스로 사용
iso_uuid = os.environ.get('ISOLATION_UUID', '')
if iso_uuid:
entry['isolation'] = {
'uuid': iso_uuid,
'root': os.environ.get('ISOLATION_ROOT', ''),
'lever': os.environ.get('ISOLATION_LEVER', ''),
'seeded': [x for x in os.environ.get('ISOLATION_SEEDED', '').split(',') if x],
}
if agent == 'claude':
entry['tui'] = {