Files
multi-agent-mux/.agents/reports/canary-projects-multi-agent-mux-creator-claude/plan-a4589a4b.md
T

359 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 멀티에이전트 추상화 계층 감사 및 정비 계획서 — 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
```
**결함 목록** (F1F9, 상세는 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(W1W5) 단독 실행 → `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 경로에서 지키지 못하고 있는 상태다.)*