# 멀티에이전트 추상화 계층 감사 및 정비 계획서 — Rev.2

- **job_id**: `3aee63cf` (Rev.1 = `a4589a4b`)
- **역할**: Planner
- **반영한 이의제기**: `94b33591` (agy, `herdr:agy-creator-01`) — `[VERDICT: PASS WITH CHALLENGE]`
- **대상**: `lib.sh`, `resolve_session_id.sh`, `resume_session.sh`, `create_session.sh`, `stop_session.sh`, delegate_job 레지스트리
- **실측 하네스**:
  - `.mam/jobs/a4589a4b/claude-reports/proposed/probe_agent_abstraction.sh` (Rev.1, 유효)
  - `.mam/jobs/3aee63cf/claude-reports/proposed/probe_adoption_guard.sh` (Rev.2 신규, 실행·검증 완료)
- **저장소 변경**: 없음 (Planner 는 코드를 수정하지 않음 — `MULTI_AGENT_RULES.md` §1)

---

## 0. Rev.1 대비 변경 요약

agy 의 이의제기는 **Rev.1 의 W8 이 `reconcile.sh` 입양 루프를 다른 7개 호출부와 동일하게 취급한 것**을 정확히 짚었다. 입양 루프는 "이 세션은 무슨 에이전트인가"를 묻는 자리가 아니라 **"이 세션을 MAM 이 관리해야 하는가"를 묻는 자리**이며, 두 질문은 다른 함수로 답해야 한다. 이 지적을 수용해 계획을 수정했다.

| 이의제기 항목 | 판정 | Rev.2 반영 |
|---|---|---|
| §1 입양 루프 오입양 (Primary) | **[ADJUDICATION: SUSTAINED]** (메커니즘 서술 일부 정정) | W8 을 W8a/W8b/W8c 로 분할, 전용 소유권 가드 도입 |
| §1.3 입양 entry 의 `role` 키 누락 | **[ADJUDICATION: SUSTAINED]** — 실측 확인 | W8c 신설 |
| §2.1 `derive_session_name` 의 `-creator-` 하드코딩 | **[ADJUDICATION: SUSTAINED]** — agy 가 말한 것보다 심각 | W9b 신설 + role 권위 재정의 |
| §2.2 shim 중복이 단일 소스를 훼손 | **[ADJUDICATION: SUSTAINED]** | W6 을 "생성 시 코드 주입"으로 변경 |
| §3.1 권고: **접미사 일치 세션만 입양** | **[ADJUDICATION: OVERRULED]** — 실측 반증 | 채택하지 않음, 대안 제시 (§1.2) |

**핵심 정정 1건**: agy 가 권고한 remedy(§3.1 "Name Suffix 가 일치하는 세션만 입양")를 그대로 적용하면, **지금 이 순간 살아 있는 `agy-creator-01` — 이의제기를 작성한 agy 자신의 세션 — 이 영구히 입양 대상에서 제외된다.** 실측으로 확인했다(§1.2).

---

## 1. 이의제기 판정

### 1.1 Primary Challenge — 입양 오염: **SUSTAINED** (메커니즘 2곳 정정)

agy 의 우려는 **성립한다.** 근거를 실측했다.

**성립 근거 ①: 후보 풀에 사용자 개인 서버가 포함된다.**
`reconcile.sh:369` 는 `unique_servers = {'default'}` 로 시작한다. MAM 세션은 `HERDR_SESSION_NAME=<workspace-slug>` 서버를 쓰지만, **`default` 서버는 무조건 함께 스캔된다.** 즉 사용자가 개인적으로 띄운 herdr 세션이 후보 풀에 들어온다. 현재 `default` 서버의 실제 상태:

```
agy-creator-01
canary-projects-multi-agent-mux-creator-claude
canary-projects-multi-agent-mux-creator-cline
```

**성립 근거 ②: 오늘 이것을 막는 유일한 장치가 바로 W8 이 교체하려던 이름 화이트리스트다.**
`reconcile.sh:494-503` 의 `else: continue` 가 제거되고 `mam_resolve_agent()` 의 우선순위 ④(`pane.cmd` 일치)가 그 자리에 들어가면, 가드가 사라진 채 판정만 남는다. agy 의 지적이 정확한 지점이다.

---

**정정 ①: agy 의 메커니즘 서술은 `cwd` 봉쇄 가드를 누락했다.**

이름 화이트리스트 **다음**(`reconcile.sh:508-510`)에 이미 이런 가드가 있다:

```python
pane_cwd_abs = os.path.realpath(pm['cwd'])
ws_root_abs  = os.path.realpath(workspace_root)
if not pane_cwd_abs or not (pane_cwd_abs == ws_root_abs
                            or pane_cwd_abs.startswith(ws_root_abs + os.sep)):
    continue
```

따라서 agy 가 예로 든 `my-dev-session`, `test-pane`, `build-worker` 가 **워크스페이스 밖에서** 돌고 있다면 이름 가드를 없애도 여전히 입양되지 않는다. 실제 노출 범위는 "무관한 외부 herdr 세션 일반"이 아니라 **"MAM 워크스페이스 디렉터리 안에서 도는 사용자 임의 세션"** 으로 한정된다.

다만 이 정정이 우려를 약화시키지는 않는다 — 오히려 **가장 흔한 경우가 정확히 그것이다.** 개발자가 자기 프로젝트 디렉터리(=MAM 워크스페이스)에서 개인용 `claude` 를 herdr 로 하나 띄우는 것은 지극히 자연스럽다. 그래서 이 우려는 유효하다.

**정정 ②: `status: terminated` 오염 서술.**
agy 는 "입양된 외부 세션이 종료 시 `status: terminated` 로 오염 데이터로 남는다"고 했다. 이는 drift A(`reconcile.sh:463-480`)의 정상 동작이며 입양된 모든 세션에 동일하게 적용된다. 오염의 본질은 `terminated` 상태 자체가 아니라 **애초에 입양되지 말았어야 할 행이 YAML 에 생긴다는 것**이다. 결론은 같지만 원인 귀속을 바로잡아 둔다.

### 1.2 agy 의 권고(§3.1) — 접미사 전용 입양: **OVERRULED**

agy 는 "Priority ④ 를 오버라이드하여 Name Suffix 가 일치하는 세션만 입양"할 것을 권고했다. **이 remedy 는 채택할 수 없다.** 실측 반증:

```
LIVE SESSION                                     SUFFIX   PANE_CMD   PANE_CWD
agy-creator-01                                   REJECT   agy        …/canary_projects/multi-agent-mux
canary-projects-multi-agent-mux-creator-claude   MATCH    claude     …/canary_projects/multi-agent-mux
canary-projects-multi-agent-mux-creator-cline    MATCH    cline      …/canary_projects/multi-agent-mux
```

`agy-creator-01` 은 `-{creator,planner,reviewer}-{claude,agy,hermes,cline}` 접미사 규칙에 **일치하지 않는다** — 접미사가 `-creator-01` 이고 `01` 은 에이전트가 아니다. 그런데 이 세션은:

- `agy` 를 워크스페이스 루트에서 실행 중인
- `.mam/agent-sessions.yaml` 에 `role: creator, status: running` 으로 정식 등록된
- **이 이의제기를 작성한 바로 그 MAM 세션**이다

agy 의 remedy 를 적용하면, 이 행이 어떤 이유로든 YAML 에서 빠졌을 때(수동 편집, 손상 복구, `.bak` 롤백) **reconcile 이 영원히 되찾지 못한다.** 오탐(false positive)을 막으려다 오탈락(false negative)을 만드는 교환이며, 후자가 더 위험하다 — 오탐은 YAML 에 행 하나가 더 생기는 것이고, 오탈락은 살아 있는 에이전트가 오케스트레이션에서 사라지는 것이다.

**근본 문제는 "어느 이름 규칙을 쓰느냐"가 아니라 "이름을 소유권 신호로 쓰는 것" 자체다.** 이름은 사용자가 `--session` 으로 임의 지정할 수 있고(그래서 `agy-creator-01` 이 존재한다), 반대로 사용자의 개인 세션이 우연히 MAM 규칙과 같은 이름을 가질 수도 있다. 이름은 소유권의 증거가 아니다.

**대안 (W8a): 적극적 소유권 마커.**
herdr 은 이미 페인 프로세스에 env 를 주입하고 있고, 그것을 되읽을 수 있다. 실측:

```
$ ps eww -p 7643 | tr ' ' '\n' | grep -E '^(HERDR|MAM)'
HERDR_ENV=1
HERDR_PANE_ID=wP:p2
HERDR_SESSION=multi-agent-mux
HERDR_SESSION_NAME=multi-agent-mux
HERDR_SOCKET_PATH=/Users/godopu16/.config/herdr/sessions/multi-agent-mux/herdr.sock
HERDR_TAB_ID=wP:t1
HERDR_WORKSPACE_ID=wP
```

그리고 `herdr agent start` 는 `--env KEY=VALUE` 를 지원한다(job `f3b10c00` 에서 CLI 계약 실측). 따라서 **생성 시점에 MAM 이 자기 소유를 명시적으로 각인**하고, 입양 시 그것을 되읽으면 된다:

```
생성: herdr agent start … --env MAM_MANAGED=<workspace_root_realpath> …
입양: ps eww -p <pane_pid>  (Linux: /proc/<pid>/environ) 에서 MAM_MANAGED 확인
```

이 신호는 이름과 무관하므로 `agy-creator-01` 도 정상 입양되고, 사용자의 개인 세션은 마커가 없으므로 입양되지 않는다. **오탐과 오탈락을 동시에 없앤다.**

단, 마커는 **도입 이후 생성된 세션에만** 존재한다. 따라서 기존 세션을 위한 3단 판정으로 설계한다:

| 단계 | 조건 | 판정 |
|---|---|---|
| 1 | `MAM_MANAGED` == 이 워크스페이스 | **입양** (권위) |
| 2 | 마커 없음 + 이름 접미사 일치 + cwd 봉쇄 통과 | **입양** (레거시 호환) |
| 3 | 그 외 | **입양 안 함** (Fail-Closed) |

2단계가 오늘의 동작과 정확히 같으므로 **회귀 위험이 없고**, 1단계가 `agy-creator-01` 같은 비규격 이름을 구제한다. `pane.cmd` 일치(우선순위 ④)는 **입양 경로에서 완전히 배제한다** — 여기서는 agy 의 판단이 옳다.

### 1.3 `role` 키 누락: **SUSTAINED**

실측 확인. 입양 `entry` 의 키 목록:

```
name, status, herdr_session_created_at, herdr_session_epoch, herdr_session,
pane{index,pid,cmd,cmd_full,cwd}, start_command, attach_command, kill_command,
last_visible_status, last_visible_note
→ role 키 존재: NO
```

agy 의 지적대로다. 다만 **해결 방법은 agy 가 제안한 "세션 이름에서 role 을 추출"이 아니다** — 그 이유는 §1.4 에서.

### 1.4 `derive_session_name` 의 `-creator-` 하드코딩: **SUSTAINED, 그리고 agy 가 말한 것보다 심각**

`lib.sh:992` 가 `printf '%s-creator-%s'` 로 하드코딩한다는 지적은 사실이다. 그런데 이 문제는 "앞으로 planner 세션 이름이 부정확해진다"에 그치지 않는다. **이미 깨져 있다.** 이 워크스페이스의 현재 레지스트리:

```
canary-projects-multi-agent-mux-creator-claude   name_role=creator   row_role=planner    <== 충돌
canary-projects-multi-agent-mux-creator-cline    name_role=creator   row_role=reviewer   <== 충돌
agy-creator-01                                   name_role=(없음)     row_role=creator    <== 이름에 role 없음

이름/row 충돌 : 2
이름에 role 없음: 1
```

**3개 세션 전부가 이름으로 role 을 알아낼 수 없는 상태다.** 이름이 `-creator-` 라고 말하는 두 세션의 실제 role 은 `planner` 와 `reviewer` 다. 즉 지금 이 감사를 수행 중인 세션(planner)과 리뷰를 수행 중인 세션(reviewer) 둘 다 이름이 거짓말을 하고 있다.

그래서 결론은 agy 의 방향과 **부분적으로 다르다**:

- **수용**: `derive_session_name` 에 role 인자를 추가한다 (W9b). 앞으로 만들어질 이름은 정확해진다.
- **반대**: 그것을 role 판정의 **근거**로 삼아서는 안 된다. 위 3행이 보여주듯 기존 이름은 role 을 담고 있지 않으며, 이름을 고쳐도 **이미 존재하는 세션의 이름은 바뀌지 않는다**(herdr 세션명·`~/.local/bin/<session>` 래퍼·디스크 아티팩트가 모두 이름에 묶여 있어 개명이 불가능하다).

**따라서 role 의 권위는 레지스트리 row (`s['role']`) 로 고정한다.** 이름 접미사는 row 가 없을 때의 최후 폴백일 뿐이다. 입양 시 `role` 을 채우는 방법도 이 원칙을 따른다 — 이름에서 뽑는 것이 아니라, 마커/폴백 판정 결과에 따라 명시적으로 부여하고 알 수 없으면 `'unknown'` 을 기록한다(§W8c).

### 1.5 shim 중복: **SUSTAINED** — 단, 해법은 "중복 제거"가 아니라 "중복 생성"

agy 의 지적은 타당하다. 그러나 Rev.1 의 C2 제약(shim 은 `PYTHONPATH` 없이 동작해야 하므로 `lib_py` 를 import 할 수 없다)은 job `7132d954` 에서 실측한 사실이라 철회할 수 없다.

해법은 shim 이 **손으로 유지되는 사본이 아니라 생성물**이 되게 하는 것이다. 실측한 사실:

- `_init_herdr_isolation()` 은 lib.sh 를 source 할 때마다 **무조건** shim 을 재생성한다 (존재 여부 가드 `if [ -f ]` 가 0개, `mv -f "$tmp_file" "$wrapper_dir/herdr"` 로 원자 교체)
- shim 본문은 `cat <<'EOF'` — **인용된** heredoc (`lib.sh:118` ~ `795`)

따라서 에이전트 표를 생성 시점에 주입하면 단일 소스가 유지된다. **다만 heredoc 의 인용을 풀어서는 안 된다** — 678줄 안의 모든 `$` 가 전개되어 shim 이 파괴된다. 주입은 (a) 인용 heredoc 뒤에 두 번째 비인용 heredoc 을 append 하거나, (b) 플레이스홀더 한 줄을 생성 후 치환하는 방식이어야 한다. (a) 를 권장한다 — 치환은 실패해도 조용하다.

그리고 **표가 어긋나면 실패하는 테스트**를 붙인다(W6b): lib.sh 를 source 해 shim 을 재생성한 뒤, shim 이 아는 에이전트 집합과 단일 소스의 집합을 비교해 불일치 시 실패. 이것이 agy 가 우려한 "신규 에이전트 추가 시 shim 동시 갱신 누락"을 기계적으로 잡는다.

---

## 2. 감사 결과 (Rev.1 에서 변경 없음 — 요약)

Rev.1 의 §1–§4 실측 결과는 이의제기의 영향을 받지 않았으므로 그대로 유효하다. 전문은 `.mam/jobs/a4589a4b/claude-reports/report-final.md` 를 참조하고, 여기서는 계획 수립에 필요한 결론만 옮긴다.

**8개 유도 구현의 불일치** (`probe_agent_abstraction.sh` 로 재현 가능):

```
SESSION NAME                     TRUTH   | A:shim  B:stop  C:upd   D:loop  E:stat  G:adopt H:rowag
proj-planner-claude              claude  | claude  ERR     claude  claude  ?       SKIP    claude   <== MISMATCH
proj-reviewer-cline              cline   | cline   ERR     cline   cline   ?       SKIP    cline    <== MISMATCH
hermes-sdk-creator-cline         cline   | hermes  cline   cline   cline   ?       cline   cline    <== MISMATCH
my-cline-fork-creator-hermes     hermes  | hermes  hermes  hermes  cline   ?       hermes  hermes   <== MISMATCH
```

**결함 목록** (F1–F9, 상세는 Rev.1 §4):

| ID | 결함 | 등급 |
|---|---|---|
| F1 | cline/hermes `verify_session_uuid` 에 워크스페이스 스코핑 없음 → P0-C 위반 | **P0** |
| F2 | `kind` 오탐이 duplicate-path strip 무력화 → `b0c2c08` 회귀 | **P0** |
| F3 | 역할 커버리지 불일치 (stop=거절 / reconcile=무시 / tier-1=우회) | P1 |
| F4 | `status.sh` 가 hermes/cline resume 판정 불가 | P1 |
| F5 | wrapper 경로에서 실행되지 않은 커맨드를 레지스트리에 기록 | P1 |
| F6 | `orc_onboard` 의 hermes argv 파싱 누락 | P1 |
| F7 | `send_keys_safe` 제출 경로가 세션 이름 부분문자열로 결정됨 | P2 |
| F8 | TUI ready 토큰 변별력 부족 | P2 |
| F9 | `create_session.sh` 의 `--agent` 검증 시점이 늦음 | P2 |

**이의제기가 추가한 결함:**

| ID | 결함 | 등급 |
|---|---|---|
| **F10** | `reconcile.sh` 입양 판정이 이름에만 의존 — 소유권의 적극적 증거가 없음 | **P1** |
| **F11** | 입양 `entry` 에 `role` 키 누락 → 스키마 불완전 행 유입 | P1 |
| **F12** | `derive_session_name` 이 role 을 반영하지 않아 **현재 3개 세션 전부** 이름/role 불일치 | P1 |

---

## 3. 구현 계획 (Rev.2)

설계 원칙 (Rev.1 에서 1개 추가):

1. 에이전트별 지식을 단일 레지스트리로 모으고, 8개 유도 구현을 하나의 함수로 대체한다.
2. 기존 소비자 계약을 깨지 않도록 파사드를 유지한다.
3. **(신규) "이 세션은 무슨 에이전트인가"와 "이 세션을 MAM 이 관리하는가"는 서로 다른 질문이므로 서로 다른 함수로 답한다.** — 이의제기 §1 수용

### Phase 0 — 선행 조건 (해소됨)

**W0.** Rev.1 이 블로킹으로 지목했던 미커밋 워킹트리는 커밋 `657a749` 로 확정되었다. `git status --porcelain` 및 `git diff HEAD --stat` 모두 비어 있음을 확인했고, 이 계획의 모든 라인 참조는 `git show HEAD:` 로 대조 검증했다. **블로킹 선행 조건 없음.**

### Phase 1 — P0 수정 (독립 실행 가능, 지금 착수 가능)

Phase 1 은 `lib_py` 에도 이의제기 반영분에도 의존하지 않는다.

**W1. cline `verify_session_uuid` 에 cwd 검사 추가** (`lib.sh:1517-1531`)
이미 읽고 있는 `sdata` 에서 `cwd`(없으면 `workspace_root`)를 꺼내 `workspace_key()` 로 비교, 불일치 시 `False`. claude 분기(`lib.sh:1468`)와 동일 형태.

**W2. hermes `verify_session_uuid` 에 cwd 검사 추가** (`lib.sh:1502-1516`)
`SELECT 1 FROM sessions WHERE id=?` → `SELECT cwd FROM sessions WHERE id=?` 후 `workspace_key` 비교. **선행 확인 필요**(§5.3).

**W3. W1/W2 회귀 테스트 — 먼저 작성해 실패를 확인할 것**
`test_workspace_scope.py` 에 4개 에이전트 전부에 대해 "다른 워크스페이스의 id 는 반환되지 않는다"를 추가한다. 현재 이 파일의 비-claude 참조는 0 이며, 그것이 F1 이 발견되지 않은 이유다.

**W4. `kind` 판정을 접미사 우선으로 교정** (`lib.sh:267-280`)
`-{creator,planner,reviewer}-<agent>` 접미사를 먼저 확인, 실패 시에만 현행 부분문자열 캐스케이드. 기본값 `cline` 제거 — 판정 실패는 `""` 로 두고 strip 을 건너뛴다(오탐 strip 보다 안전).

**W5. W4 회귀 테스트** — `test_herdr_shim_contract.py` 에 `hermes-sdk-creator-cline` 계열 이름 추가.

### Phase 2 — 단일 소스 도입 (이의제기 §2.2 반영)

**W6a. 에이전트 능력 레지스트리 정의** — 본체는 `lib_py/agents.py` (job `7132d954` 의 패키지 위에).

**W6b. shim 표를 생성물로 만든다** ← *이의제기 §2.2 반영, Rev.1 에서 변경*
Rev.1 은 "shim 이 쓰는 최소 부분만 `lib.sh` 에 중복"이라 했으나, 손으로 유지되는 중복은 agy 의 지적대로 갱신 누락에 취약하다. `_init_herdr_isolation()` 이 shim 을 **매번 무조건 재생성**한다는 실측에 근거해, 표를 생성 시점에 주입한다:

- 인용 heredoc(`lib.sh:118-795`)은 **그대로 둔다** — 인용을 풀면 678줄의 모든 `$` 가 전개되어 shim 이 파괴된다
- 표는 인용 heredoc **뒤에 두 번째 비인용 heredoc 으로 append**
- 표 불일치 시 실패하는 테스트를 함께 추가 (shim 재생성 → shim 이 아는 에이전트 집합 == 단일 소스 집합)

**W7. `mam_resolve_agent()` — "무슨 에이전트인가"에만 답한다**
우선순위: ① 명시 `--agent` → ② 레지스트리 row 의 `agent` → ③ 세션 이름 접미사 `-{role}-<agent>` → ④ `pane.cmd` 정확 일치 → ⑤ 실패는 실패로 반환(암묵 기본값 없음).
**이 함수는 소유권을 판정하지 않는다.** 우선순위 ④ 는 "이미 MAM 이 관리한다고 확정된 세션"에 대해서만 유효하다. 입양 경로는 W8a 를 쓴다.

**W8. 호출부 교체 — 입양 경로를 분리** ← *이의제기 §1 반영, Rev.1 W8 에서 분할*

- **W8a. `reconcile.sh` 입양 루프 전용 판정** (`reconcile.sh:494-510`)
  `mam_resolve_agent()` 를 쓰지 않는다. 대신 §1.2 의 3단 판정:
  1. `MAM_MANAGED == realpath(workspace_root)` (pane pid 의 env 에서 읽음) → 입양
  2. 마커 없음 + 이름 접미사 일치 + 기존 cwd 봉쇄 통과 → 입양 (레거시 호환, 오늘의 동작과 동일)
  3. 그 외 → 입양 안 함 (Fail-Closed)

  `pane.cmd` 일치는 이 경로에서 **완전히 배제한다.**
  env 읽기는 macOS `ps eww -p <pid>` / Linux `/proc/<pid>/environ` 두 경로를 모두 구현하고, 어느 쪽도 불가하면 2단계로 강등한다(마커 없음과 동일 취급).

- **W8b. 생성 시 소유권 마커 각인**
  `create_session.sh` 의 herdr 기동 경로에 `--env MAM_MANAGED=<workspace_root_realpath>` 를 추가한다. 실측으로 `herdr agent start --env KEY=VALUE` 지원과 `ps eww` 회수 가능성을 모두 확인했다.

- **W8c. 입양 시 `role` 부여** ← *이의제기 §1.3 반영*
  `entry['role']` 을 추가한다. 값의 출처는 §1.4 의 원칙을 따른다: 이름 접미사에서 뽑지 않고, 1단계 마커 입양이면 마커에 함께 실은 role 을, 2단계 레거시 입양이면 이름 접미사의 role 을, 알 수 없으면 `'unknown'` 을 기록한다. **`role` 을 절대 추측해 `'creator'` 로 채우지 않는다** — 현재 레지스트리에서 이름이 `creator` 라고 말하는 두 세션의 실제 role 이 `planner`/`reviewer` 이기 때문이다.

- **W8d. 나머지 6개 호출부를 `mam_resolve_agent()` 로 교체**
  A(`lib.sh:267`), B(`stop_session.sh:94`), C(`update_yaml_resumed.sh:46`), D(`run_loop.sh:254`), E(`status.sh:55,151`), H(`reconcile.sh:568`). F(`send_keys_safe`)는 W12.

### Phase 3 — P1 수정

**W9a. 역할 커버리지 통일** — F3. `-{creator,planner,reviewer}-` 3개 역할을 전 경로에서 인정. `find_workspace_uuid` tier-1 의 `endswith('-creator-…')` 게이트(`lib.sh:1707-1721`)도 함께 푼다.

**W9b. `derive_session_name` 에 role 인자 추가** — F12, *이의제기 §2.1 반영, 신규*
`derive_session_name <workspace> <agent> [role=creator]` → `printf '%s-%s-%s' "$slug" "$role" "$agent"`. `create_session.sh` 가 `--role` 을 넘기도록 연결한다.
**W9a 와 반드시 같이 착수한다** — 순서가 어긋나면 새 이름(`<slug>-planner-claude`)을 이해하지 못하는 경로가 남는다.
**기존 세션은 개명하지 않는다.** 이름/role 불일치 3건은 그대로 두고, role 권위를 row 로 고정(W9c)해 해소한다.

**W9c. role 판정의 권위를 레지스트리 row 로 고정** — F12, 신규
`s['role']` 이 있으면 그것이 답이다. 이름 접미사는 row 가 없을 때의 폴백일 뿐임을 코드와 주석에 명시한다. `run_loop.sh:resolve_planner_session` 이 이미 `s.get('role')` 를 쓰고 있으므로 이것이 기준 구현이다.

**W10.** `status.sh:resume_on_disk` 에 hermes/cline 분기 추가 — F4.
**W11.** `create_session.sh` wrapper 경로에서 `CMD_FULL` 재계산 — F5.
**W12.** `send_keys_safe` 를 `agent` 기반으로 전환 — F7. hermes 만 strict-verify 를 타는 것이 의도인지 확인 후 정책을 명시 기록.
**W13.** `orc_onboard` 에 hermes argv 분기 추가 — F6. `--resume[[:space:]=]+<id>`.

### Phase 4 — P2 및 정리

**W14.** `create_session.sh` 의 `--agent` 검증을 파싱 직후로 이동 — F9.
**W15.** TUI ready 토큰 4개를 전부 오버라이드 가능한 변수로 — F8.
**W16.** 신규 에이전트 추가 절차를 `AGENTS.md` 에 기록 — W6a 레지스트리 + W6b 생성 표에 항목 하나만 추가하면 되도록 만든 뒤.

---

## 4. 검증 전략

**두 하네스를 회귀 게이트로 쓴다.**

`probe_agent_abstraction.sh` (Rev.1) — 판정 기준이 스크립트에 내장되어 있다:
> A 의 불일치가 0, C 가 4개 에이전트 모두 `scoped=YES`, D 의 비-claude 참조가 0 이 아님.

`probe_adoption_guard.sh` (Rev.2 신규) — 이의제기 대응분의 게이트:
> C3 에 오탈락(SUFFIX=REJECT + 지원 에이전트 + 워크스페이스 내부) 행이 없을 것,
> C4 의 `role` 키 존재 = YES, C5 의 "이름에 role 없음"이 새로 늘지 않을 것.

**W8a 전용 추가 검증 (이의제기가 요구하는 것):**
격리된 herdr 세션에 (i) 마커 있는 세션, (ii) 마커 없는 규격 이름 세션, (iii) 마커 없는 임의 이름 + 지원 에이전트 + 워크스페이스 내부 세션 셋을 띄우고 reconcile 을 `--once` 로 돌려, **(i)(ii) 만 입양되고 (iii) 은 입양되지 않는지** 확인한다. (iii) 이 agy 가 지적한 오염 케이스이고, (ii) 가 내가 지적한 오탈락 방지 케이스다.

**W8d 차등 테스트:** 교체 전후 동일 입력 집합에 대해 출력을 바이트 비교. 의도한 차이(오분류 4행)만 남고 나머지는 전부 동일해야 한다.

기존 스위트는 이 역할을 못 한다 — `tier3`/`tier4` 는 HEAD 에서 10분을 넘겨 job `7132d954` 에서도 측정에 실패했다.

---

## 5. 리스크 및 미측정 항목

**5.1 선행 블로커 없음.** 커밋 `657a749` 로 해소(§Phase 0).

**5.2 `7132d954` 와의 순서 의존.** W6a 는 `lib_py/` 패키지를 전제한다. **권장 순서: Phase 1(W1–W5) 단독 실행 → `7132d954` lib_py 이행 → Phase 2 이후.** Phase 1 은 어디에도 의존하지 않는다.

**5.3 hermes 는 미설치 — 실측 불가.** 이 머신에 `hermes` 바이너리가 없다(`claude`/`agy`/`cline` 은 있음). hermes 관련 판단은 **전부 소스 독해이며 실행으로 확인하지 않았다**:
- `~/.hermes/state.db` 의 `sessions` 테이블에 `cwd` 컬럼이 실재하는지 (tier-2/3 의 `WHERE cwd=?` 로부터의 추론)
- hermes 세션 id 가 UUID 형식인지 (`is_valid_id` 가 그렇게 가정하나 미검증)
- `hermes --resume <uuid>` 플래그의 실재
W2/W13 착수 전 hermes 설치 환경에서 확인할 것.

**5.4 소유권 마커(W8a/W8b)의 미측정 부분.** `ps eww` 로 herdr 주입 env 를 읽는 것은 이 머신에서 확인했으나, 다음은 미확인이다:
- `herdr agent start --env MAM_MANAGED=…` 로 넣은 값이 **페인 루트 프로세스의 env 에 실제로 나타나는지** (herdr 자체 주입 변수는 확인했으나 `--env` 경유 값은 별도 확인 필요)
- Linux `/proc/<pid>/environ` 경로 (이 머신은 darwin)
- 에이전트가 자식 프로세스를 새로 exec 하며 env 를 갈아끼우는 경우
**W8b 착수 전 이 3가지를 먼저 실측할 것.** 실패하면 대체 마커(예: `.mam/owned/<session>` 마커 파일)로 전환하되, 3단 판정 구조 자체는 유지한다.

**5.5 `default` 서버 스캔은 이번 범위에서 바꾸지 않는다.** `unique_servers` 가 항상 `'default'` 를 포함하는 것이 오염 노출의 근인이지만, 이를 제거하면 `default` 서버에 있는 **현재 살아 있는 3개 세션 전부**가 관측 대상에서 사라진다. 소유권 마커가 자리잡은 뒤 별도 job 으로 다룰 문제다.

**5.6 cline id 는 UUID 가 아니다.** 실제 형식은 `1782614591159_mrkxj`. `is_valid_id` 가 `^[0-9]{10,}_[0-9A-Za-z]+$` 로 올바르게 허용함은 확인했으나, "UUID" 라는 용어가 전반에 쓰여 오해를 부른다. 개명은 호출부 21곳(테스트 핀 고정)을 건드리므로 이번 범위 밖.

**5.7 Rev.1 §2.3 의 노출 측정(cline 26개/8 cwd)은 이 머신 한정이다.** 다만 스코핑 부재라는 사실 자체는 소스에서 확인된 것이므로 환경과 무관하다.

**5.8 `reconcile.sh` 840줄 전체를 감사하지 않았다.** 브리프가 지정한 6개 인터페이스와 이의제기가 지목한 입양 루프·`row_agent` 에 집중했다.

---

## 6. 판정

이의제기 3건을 모두 검토해 2건을 전면 수용, 1건(권고 remedy)을 실측 반증으로 대체하고, 신규 결함 3건(F10–F12)을 계획에 반영했다.

**[ADJUDICATION: SUSTAINED]** — `94b33591` §1 (입양 오염), §1.3 (`role` 누락), §2.1 (`derive_session_name`), §2.2 (shim 중복)
**[ADJUDICATION: OVERRULED]** — `94b33591` §3.1 (접미사 일치 세션만 입양) — `agy-creator-01` 오탈락 실측

**[VERDICT: PASS]**

*(이 토큰은 이 계획서 산출물의 완성도를 뜻한다. 감사 대상 코드에 대한 판정이 아니다 — 감사 결과는 **P0 2건 + P1 6건 미해결**이며, 특히 F1 은 `resolve_session_id.sh` 가 헤더에 명시한 P0-C 계약을 cline 경로에서 지키지 못하고 있는 상태다.)*
