Files
multi-agent-mux/.agents/reports/planner-reviewer-claude-01/report-adc0ff3c.md
T
GodopuandClaude Fable 5.1 9fb037c084 release(v4.7.2): bump framework and eight skills to 4.7.2 in lockstep; archive PR #35 merge-side reviews
Issues #23 and #24: cross-server herdr scope, agent-agnostic start-up
dialogs, onboarding delivery judged by the agent's own started event,
verified rollback. VERSIONS.md carries the entry; the merge-side reports
(round 1: claude/agy/grok; round 2 on 05b0ea4: all four, with identity
sidecars, base f4ba205 -> head 05b0ea4, evidence review-tree) are archived
under .agents/reports/<session>/. The round-1 codex report was lost to the
host sleeping mid-review; its two probes are recorded in the changelog.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-24 08:36:35 +09:00

20 KiB
Raw Blame History

🔍 Review Report — PR #35 병합 전 독립 리뷰 (job adc0ff3c)

  • Reviewer: planner-reviewer-claude-01 (role: planner,reviewer) · 2026-09-23
  • 대상: PR #35 fix/issue-23-24 @ f4ba205, base main 1e78dae (v4.7.1). 13커밋, 22파일 +2197/−118, git diff --check 클린.
  • 검증 경로: /var/folders/.../T/mam-review/loop-20260923T142448Z-609954/1. identity 확인: HEAD = f4ba205c…fd2f, HEAD^{tree} = 88cb4f34…f181 — 브리프의 head·tree 와 일치.
  • 안전 규칙 준수: 검증 경로·소스 워크트리(multi-agent-mux-wt/pr-35)·본 체크아웃(main 1e78dae) 모두 수정·커밋·checkout 없음. 실험은 scratchpad 임시 디렉터리(uuid4 이름)에서만, pytest 는 AGENT_PYTHON_BIN=<제어 루트 .venv> + --basetemp=<검증 경로 밖> 으로 실행. 실제 herdr 서버·에이전트 세션·.mam·데몬 조작 없음(이 pane 의 herdr 환경변수를 읽기만 했다), push·merge·PR 상태 변경 없음, purge·scrub 실행 없음.

결론 요약

병합을 막을 결함은 없다. 세 핵심 규칙 — 교차 서버 판정, "이번 dispatch 의 started 만 증거", kill 결과 확인 롤백 — 을 각각 독립 실험으로 재현·확인했고, 새 테스트는 실제 registry.py/publish_event.py 를 쓰며 홈·브로커·실제 herdr 에 닿지 않는다. 비차단 지적 6건(F1–F6)을 남긴다. 그중 F1 은 브리프의 codex 수락 키 서술과 코드가 다르다는 점이라 오케스트레이터가 확인할 필요가 있다.

1. #24 시작 다이얼로그 (항목 1)

1-1. 어댑터 시그니처·수락 키

python -m lib_py.agents facts <agent> 가 실제로 내놓는 값(검증 경로에서 실행):

에이전트 MAM_STARTUP_DIALOGS 강한 준비 토큰
claude 기존 사다리 5개(trust Enter / Yes, try it Escape / Yes, proceed Down+Enter / Resuming… Enter / Press Enter… Enter)가 그대로 옮겨짐 Anthropic|Assistant|…|Haiku
agy Do you trust the contents of this project|Yes, I trust this folder → Enter, trust Antigravity
grok Do you trust the contents of this directory → y, trust Grok|xAI|Assistant|❯|>>>
codex Do you trust…|1\. Yes, continue → Enter, trust · 1\. Update now|Skip until next version → 2, notice Codex|Ask Codex to do anything
opencode·hermes [] (다이얼로그 없음) 있음 → 핸들러가 배너에서 즉시 반환

F1 (확인 요망, 비차단): 브리프는 codex 수락 키를 "1+Enter, 업데이트 안내 3+Enter" 로 적었으나 코드는 Enter 단독(1번 항목이 미리 선택돼 있다는 주석)과 2("2 = Skip" 이라는 관찰 기록, 2026-09-22) 다. 어느 쪽이 실제 화면인지는 이 리뷰에서 재현할 수 없다(실제 codex 를 띄우는 것은 금지). PR 본문은 codex 를 포함한 4개 에이전트의 실제 herdr 시험이 사람 개입 없이 started(토큰 포함)→completed 까지 갔다고 기록하므로 코드 쪽이 실측으로 보이지만, 브리프 작성자의 관찰과 다르므로 병합 전에 한 번 맞춰 볼 것을 권한다. _MAM_READY_TOKENS_CLAUDE 상수는 제거되고 어댑터의 strong_ready_tokens 로 대체됐다(값 동일).

1-2. 준비 토큰으로 수락을 판정하는 이유

handle_startup_dialogs(lib.sh:2979-)는 시그니처가 화면에 남아 있어도 같은 인덱스에 대해 MAM_DIALOG_RESEND_S(기본 5 s) 안에는 재전송하지 않고(_mam_dialog_pick 의 cooldown), 반환 조건은 오직 준비 토큰이다. --source visible 스냅샷이 수락된 다이얼로그를 한동안 계속 보여 준다는 #24 의 관찰과 맞물려 타당하다 — 옛 코드는 매 폴링마다 Enter 를 다시 보내 이미 넘어간 TUI 에 입력을 흘렸다. test_c1_tui_readiness.py::test_c1b 의 단언이 ==30 에서 1 ≤ n ≤ 30 으로 바뀐 것도 같은 이유이며, "한 프레임에 두 번 보내지 않는다"는 원래 가드는 그대로다.

1-3. --no-auto-trust / MAM_AUTO_TRUST=0

_mam_dialog_pick 은 kind == "trust" 이고 auto_trust 가 꺼져 있으면 skip|idx| 를 내고, 핸들러는 경고만 남긴다(cooldown 마다 1회). notice 는 항상 답한다. create(create_session.sh:86-94)와 resume(resume_session.sh:43-57)이 같은 스위치·같은 env 변수(MAM_AUTO_TRUST)·같은 호출(handle_startup_dialogs "$SESSION" N "$AGENT") 을 쓴다. S6/R3 가 고정.

1-4. 자동 수락의 적용 범위(보안)

pane 은 new-session -c "$WORKSPACE" 로 뜨고 신뢰 창은 그 cwd 에 대한 것이므로 실질적으로 --workspace 에만 적용된다. 다만 시그니처 정규식은 디렉터리 이름을 검사하지 않는다 — 화면에 어떤 경로가 적혀 있든 매치되면 수락한다(F5). 시작 직후의 신뢰 창이 다른 디렉터리를 가리킬 경로는 없어 실무 위험은 낮다.

2. #23 교차 서버 스코프 (항목 2)

_herdr_caller_server / _herdr_call_is_cross_server(lib.sh:1403-1447)를 lib.sh 에서 함수만 추출해 env -i 로 9가지 조합을 돌렸다(전부 기대와 일치):

HERDR_SOCKET_PATH HERDR_SESSION 대상 판정
~/.config/herdr/herdr.sock – mam caller=default, CROSS (보고된 #23 시나리오)
〃 – (없음) / default same (앰비언트 sentinel)
~/.config/herdr/sessions/mam/herdr.sock – mam caller=mam, same → pane 배치·workspace 패킹 유지
〃 – other CROSS
〃 orch mam caller=orch(HERDR_SESSION 우선), CROSS
/other/layout/herdr.sock / 비어 있음 – mam caller="" → same (pane 밖: 명시 스코프 존중)
…/sessions/a b/herdr.sock – mam caller=a b, CROSS (공백 안전)
  • 제거는 mam_herdr 안에서 교차일 때만 env -u HERDR_WORKSPACE_ID -u HERDR_PANE_ID -u HERDR_TAB_ID 로 이루어지고(lib.sh:1449-1461), argv 배열 + if 로 판정하므로 set -e 호출자를 중단시키지 않는다(S12).
  • lib_py/loop/watcher.py::caller_server/scoped_env 는 같은 규칙의 파이썬 쌍둥이다. 같은 5가지 입력으로 돌려 bash 와 동일 결과를 확인했다(같은 서버·pane 밖·default·None 대상은 ids 유지, 교차만 제거).
  • 실제 레이아웃 근거: 이 pane(herdr 0.9.1)의 환경은 HERDR_SOCKET_PATH=~/.config/herdr/sessions/multi-agent-mux/herdr.sock, HERDR_SESSION=multi-agent-mux 였다 — 규칙이 전제하는 sessions/<name>/herdr.sock 형태와 일치하고, HERDR_SESSION 도 export 된다. 0.8.2 의 "기본 서버 pane 에는 HERDR_SESSION 이 없다"는 전제는 PR 의 실측 기록(코드 주석)에 의존하며 여기서 재현할 수 없다. 두 경우 모두 소켓 경로 규칙이 뒷받침하므로 어느 버전이든 판정이 성립한다.
  • 인식되지 않는 레이아웃은 ""(= pane 밖) 으로 떨어져 오늘의 동작을 유지한다. 잘못된 제거보다 안전한 쪽이다.

3. #23 온보딩 전달 판정 (항목 3)

3-1. 공유

delegate_publish_cmd·delegate_job_status·delegate_started_recorded·delegate_wait_started 가 lib.sh:2189-2313 에 하나씩 있고, create 온보딩(create_session.sh:622-680)과 래퍼 _wait_started(multi-agent-mux-delegate-job:647-668)가 같은 delegate_started_recorded 를 부른다. 래퍼는 MAM_JOBS_DIR="$REGISTRY_DIR" 와 LOGS_DIR_ABS 를 넘겨 자기 레지스트리·감사 루트를 그대로 쓴다(:191,:659). 옛 _disk_status != pending → 성공 우회와 구독자 콘솔 grep 은 삭제됐다(diff 로 확인).

3-2. 증거 규칙 실측

delegate_started_recorded 만 추출해 가짜 events.log/events.ndjson 으로 10가지를 돌렸다(전부 기대와 일치):

# 상태 토큰 요구 결과
1 파일 없음 있음 NOT
2 이전 dispatch 토큰의 started 있음 NOT (R1: 이전 시도의 증거 거부)
3 〃 없음 recorded (토큰 없으면 종전대로)
4 이번 토큰의 started 있음 recorded
5 감사 기록 dispatch_verified:true, fingerprint 없음 있음 NOT (신원 없는 과거 기록 fail closed)
6 〃 없음 recorded
7 이전 시도의 fingerprint 있음 NOT
8 이번 토큰의 fingerprint 있음 recorded
9 fingerprint 맞지만 dispatch_verified:false 있음 NOT
10 깨진 줄 + progress 만 있음 NOT (JSON 파싱, 패턴 매칭 아님)

fingerprint 는 sha256(token) 으로 구독자(job_subscriber.py:303-306)와 같은 해시임을 확인했다. 레지스트리 status 는 어느 경로에서도 증거로 쓰이지 않는다.

3-3. 종료된 잡·#18 정합

  • create 는 더 이상 에이전트 대신 started 를 발행하지 않는다(:649-657 주석과 코드; S7). 미확인 시 delegate_job_status 가 completed|error|cancelled 면 덮어쓰지 않고 경고만 한다(:674-679; S7m).
  • 대기 시간은 MAM_ONBOARD_ACK_S → MAM_INJECT_ACK_GRACE_S → 잡의 ack_timeout_sec(기본 180) 순이다 — #18 의 T=180 과 같은 값이고, heartbeat 계약은 started --heartbeat 30 명령을 delegate_publish_cmd 가 래퍼와 인자 단위로 동일하게 만든다(S7l).
  • inject_instructions(= send_keys_safe) 의 rc 4 는 세 자리 모두 "전달 미상"(lib.sh:2746 busy 초과, :2810 delivery unknown, :2883 Enter 미수락)이라 started 대기로 판정을 넘기는 것이 맞고, rc 1(quiesce 실패)·2·3(다이얼로그 차단)은 즉시 실패로 남는다(S8/S8b).

4. #23 실패 처리 (항목 4)

  • cleanup_herdr_on_error(create_session.sh:318-346): kill-session 의 rc 를 버리지 않고 받은 뒤 has-session 으로 재확인, 살아 있으면 행을 running + last_visible_status: rollback-failed + delegate_job_id: null 로 표시하고 exit 43. bash 3.2 에서 EXIT trap 안의 exit 43 이 원래 종료 코드(1)를 덮어씀을 직접 확인했다(rc=43). 상태값은 atomic_yaml.py:18 의 어휘(running/terminated/archived/stopped) 안이고 last_visible_status 는 reconcile 이 이미 쓰는 필드다.
  • 온보딩 미확인: 세션 보존 + 잡 error(이미 종료된 잡은 제외) + 경고 2줄, ROW_APPENDED 뒤라 YAML 행은 유효한 running 이다. 실패한 잡은 JOB_TERMINALIZED=1 로 표시돼 트랩이 cancelled 를 겹쳐 발행하지 않는다.
  • create SKILL.md(:101-131)가 이 규칙(started 기반 판정, rc 4 경고, 세션 보존, MAM_ONBOARD_ACK_S, exit 42/43 과 행 상태)을 정확히 서술한다.

5. 기존 테스트 조정 6파일 (항목 5)

파일 변경 판정
conftest.py mam_sandbox 에 MAM_ONBOARD_ACK_S=3 추가 타당. 샌드박스에는 started 를 발행할 실제 에이전트가 없어 이것이 없으면 create 테스트마다 180 s 를 소모한다. create 전용 변수라 래퍼의 MAM_INJECT_ACK_GRACE_S 를 단언하는 테스트와 간섭하지 않는다
test_b19_* grep -q 'Yes, try it' 가 lib.sh 에 있는지 → claude 어댑터의 ('Yes, try it', ['Escape'], 'notice') 로 위치 이동 토큰 목록 두 단언은 그대로. 검증 대상이 이사한 것뿐
test_c1_tui_readiness.py KEYS_COUNT==30 → 1 ≤ n ≤ 30, custom 에이전트 → claude §1-2 참조. cooldown 도입으로 "매 반복 1회" 가 "프레임당 최대 1회" 로 바뀌었고 원래 가드(2회 금지)는 유지·강화. custom 은 어댑터가 없어 다이얼로그 목록이 비므로 이 시험에는 claude 가 맞다
test_issue14_delivery_ack.py 레지스트리 상태만 running 으로 flip 하던 "ack" 를 실제 publish_event.py + dispatch 토큰의 started 로 교체 강화. 옛 시험은 PR 이 없앤 "상태만 보고 성공" 우회를 통과 조건으로 쓰고 있었다. 새 시험은 로컬 events.log 에 토큰이 실린 줄까지 단언한다
test_issue18_liveness_contract.py acceptance5 의 배달 단언을 recorded 'started' 하나로 좁힘 계약상 옳다(레지스트리 상태는 증거가 아님). 다만 "브로커가 retained completed 만 배달" 한 경우가 이제 실패한다 — _skip_if_broker_did_not_deliver 는 그 잡의 어떤 이벤트도 못 받았을 때만 skip 하므로. 공개 브로커에서 flake 가 늘 수 있는 자리(F4)
test_issue5_phase2a_loop.py G9 가드가 코드만 읽도록(ast 로 docstring 노드만 제거, 주석은 unparse 에서 사라짐) + g9b 자체 검증 약화 아님. 호출 형태(subprocess argv 의 lib.sh 경로·source, 식별자)는 여전히 코드 텍스트에 있어 잡히고, g9b 가 "같은 줄의 코드"(R2) 네 가지까지 고정한다. ast.unparse 는 3.9+ — venv 3.9.6 이라 통과

7. 문서 (항목 7)

  • create SKILL.md :101-131: started 기반 판정·rc 4 경고·세션 보존·MAM_ONBOARD_ACK_S·--no-auto-trust(codex 의 로컬 설정 로드 위험 명시)·MAM_DIALOG_RESEND_S·exit 42/43 과 행 상태가 코드와 일치한다. resume SKILL.md 는 에이전트 분기 case 를 없애고 같은 호출로 바꿨다.
  • F2 (비차단, 문서 누락): 감사 기록의 새 필드 dispatch_verified·dispatch_fingerprint·attempt_id 는 MESSAGING.md·delegate-job SKILL.md 어디에도 없다(전수 grep 0건). 두 문서는 events.ndjson 을 단계 수준으로만 서술해 필드 목록 자체가 없으므로 모순은 없지만, "래퍼와 create 가 무엇을 증거로 읽는가"가 이번 PR 의 핵심이라 한 문단은 있어야 한다.
  • F3 (비차단, 문구 정밀도): SKILL.md 는 MAM_ONBOARD_ACK_S 가 래퍼의 MAM_INJECT_ACK_GRACE_S 와 "별개" 라고만 적었지만 코드의 우선순위는 MAM_ONBOARD_ACK_S → MAM_INJECT_ACK_GRACE_S → 잡의 ack_timeout_sec 이다(create_session.sh:658). 즉 래퍼 변수도 create 의 대기에 2순위로 영향을 준다.
  • .claude/skills: 본 체크아웃의 것은 git 에 추적되지 않는 로컬 심링크(../../.agents/skills/*, 10개)라 자동 동기화된다. 검증 경로(워크트리)에는 존재하지 않는다 — 배포 사본 정합 문제 없음.

6. 테스트 (항목 6)

6-1. 검증 경로에서의 실행

cd <검증 경로> && AGENT_PYTHON_BIN=<제어 루트 .venv/bin/python> PYTHONDONTWRITEBYTECODE=1 <같은 인터프리터> -m pytest \
  -p no:cacheprovider -q -rfEs --basetemp=<검증 경로 밖> \
  tests/test_issue23_24_startup_path.py tests/test_issue24_resume_dialogs.py tests/test_b19_headless_reconcile_fixes.py \
  tests/test_c1_tui_readiness.py tests/test_issue14_delivery_ack.py tests/test_issue18_liveness_contract.py \
  tests/test_issue5_phase2a_loop.py tests/test_herdr_shim_contract.py tests/test_loop_cli.py
→ 273 passed in 700.58s (0:11:40)   failed 0 · errors 0 · skipped 0

새 파일 2개(73건)와 조정된 6파일, 그리고 셸 shim 계약·loop CLI 까지 모두 통과. 브로커 의존 시험(issue14·issue18)도 이번 실행에서는 flake 없이 통과했다.

6-2. 수정 없이는 실패하는가 — 확인함

검증 경로 사본에서 PR 이 바꾼 소스 12파일(lib.sh·어댑터 5·__main__.py·job_subscriber.py·watcher.py·create/resume 스크립트·래퍼)만 base 1e78dae 판으로 되돌리고 새 테스트 2파일을 돌렸다:

60 failed, 12 passed, 1 skipped in 275.50s

실패 60건은 S1(교차 서버)·S4S6(다이얼로그)·S7/W(started 증거)·S8(rc 4)·S9/S10(롤백)·R1R4(resume) 전 영역에 걸친다. 통과한 12건은 "같은 서버는 스코프 유지"(S2/S2b)·"shim 은 바뀌지 않음"(S11)·"호출자가 준 토큰은 덮어쓰지 않음"(S5e) 같은 불변 조건들이라 수정과 무관한 것이 맞다. 즉 새 테스트는 이 변경을 정확히 겨냥한다.

6-3. 격리·픽스처·시간

항목 결과
실제 .mam/jobs / .mam/delegate_job_logs 전후 차이 0 / 차이 0
실제 herdr 서버·홈 mam_sandbox 가 HOME/HOME_DIR 를 tmp_path 로 바꾸고(conftest.py:210-211) herdr 는 mock. 새 파일에서 hivemq·실제 소켓 참조 0건. 이 pane 의 herdr 환경변수는 S1d 가 읽기만 한다
실제 생산자 사용 S7i~S7n·W 계열은 REGISTRY_PY/PUBLISH_PY(실제 registry.py·publish_event.py)로 잡을 등록하고 started 를 발행한다(test_issue23_24_startup_path.py:620-660). "손으로 만든 픽스처가 결함을 가렸다"는 PR 의 교훈이 테스트에 반영돼 있다
검증 경로 tracked 파일 변화 없음(HEAD f4ba205 유지). 내 실행이 무시되는 .mam/ 을 만들었다(#32 리뷰 때와 같은 현상, 그대로 둠)
실행 시간 새 파일 2개는 대체로 빠르지만 test_issue24_resume_dialogs.py 4건이 각 39~43 s 다(resume 의 handle_startup_dialogs … 20 타임아웃과 mock 준비 대기가 겹친다). 스위트 전체 시간에 +2.7 분을 더한다(F6)

8. 병합 차단 여부 (항목 8)

버전 락스텝 bump 부재(lib.sh:32 4.7.1, 오케스트레이터가 4.7.2 로 처리 예정) 외에 병합을 막을 결함은 없다.

# 비차단 지적 근거 / 권고
F1 codex 수락 키가 브리프 서술과 다르다. 코드: 신뢰 창 Enter(1번 미리 선택), 업데이트 안내 2(Skip, 이번만). 브리프: 1+Enter, 3+Enter(Skip until next version). S4 의 codex 화면 픽스처(Update available / 1. Update now / 2. Skip / 3. Skip until next version)를 보면 3개 항목이 실제로 있고, 코드는 그중 2 를 고른다 — "아무것도 바꾸지 않는" 선택이라는 주석과 일치하나 다음 시작 때 다시 뜬다. 실제 시험은 PR 본문대로 통과했으므로 동작은 성립 병합 전 브리프 작성자와 한 번 맞출 것; 3 이 의도라면 어댑터 한 줄
F2 감사 기록의 새 필드(dispatch_verified·dispatch_fingerprint·attempt_id)가 MESSAGING.md·delegate-job SKILL.md 에 없다 한 문단 추가 권고
F3 MAM_INJECT_ACK_GRACE_S 가 create 의 대기에도 2순위로 영향을 준다는 점이 SKILL.md 에 없다(create_session.sh:658) 문구 보강
F4 acceptance5 가 "retained completed 만 배달" 된 경우를 이제 실패로 본다(계약상 옳음). 공개 브로커에서 flake 가 늘 수 있는 자리 관찰되면 skip 조건 재검토
F5 다이얼로그 시그니처는 화면의 디렉터리 경로를 검사하지 않는다 — pane 이 -c $WORKSPACE 로 뜨므로 실무 위험은 낮다 정보
F6 test_issue24_resume_dialogs.py 4건이 각 ~40 s 타임아웃을 env 로 줄일 수 있으면 개선

9. 교차검증 가이드

RP=/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20260923T142448Z-609954/1
PY=/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python
git -C "$RP" rev-parse HEAD 'HEAD^{tree}'                     # f4ba205… / 88cb4f3…
# 교차 서버 규칙 (함수만 추출, env -i)
eval "$(sed -n '/^_herdr_caller_server() {/,/^}/p; /^_herdr_call_is_cross_server() {/,/^}/p' "$RP/.agents/skills/lib.sh")"
env -i HERDR_SOCKET_PATH=/x/.config/herdr/herdr.sock HERDR_SESSION_NAME=mam bash -c "$(declare -f _herdr_caller_server _herdr_call_is_cross_server); _herdr_call_is_cross_server && echo CROSS"
env -i HERDR_SOCKET_PATH=/x/.config/herdr/sessions/mam/herdr.sock HERDR_SESSION_NAME=mam bash -c "$(declare -f _herdr_caller_server _herdr_call_is_cross_server); _herdr_call_is_cross_server || echo same"
# 증거 규칙: 이전 토큰의 started / fingerprint 없는 감사 기록은 토큰 요구 시 거부 (§3-2 의 10 케이스)
# 수정 없이는 실패하는지 (사본에서만): 소스 12파일을 1e78dae 판으로 교체 후 새 테스트 2파일 → 60 failed / 12 passed

[VERDICT: PASS]