Files
multi-agent-mux/.agents/reports/planner-reviewer-codex-01/report-fbd52b0b.md
T
GodopuandClaude Opus 5.5 44999ff2fb release(v4.8.3): bump framework and eight skills to 4.8.3 in lockstep; archive PR #46 reviews (two rounds)
Issue #42 stages 1-2: the SKILL.md code-block inventory as test data and
the removal of blocks the entry scripts already do. Merge-workspace review:
round 1 (@8d26106) claude agy grok PASS, codex NOT PASS (R1-R3); fixed in
17eba09 (docs, inventory data and the guard test only); round 2 4/4 PASS.
Full suite at 8d26106 1408 passed / 0 failed. Reports and the round-2
identity sidecars go to .agents/reports/<session>/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-10-04 08:36:15 +09:00

15 KiB
Raw Blame History

PR #46 단계 1·2 리뷰 — fbd52b0b

NOT PASS. 제품 코드 변경은 없고 선택 테스트는 모두 통과했다. 다만 새 문서대로 실행할 때 격리 서버의 attach 대상이 사라지는 문제, dry-run의 부작용을 부정하는 설명, 삭제 블록 복귀 가드의 누락이 남아 있다. 문서와 테스트의 국소 수정으로 해결할 수 있으므로 재계획 승격은 필요하지 않다.

검증 대상과 결과

  • loop: loop-20261003T222707Z-a0de27
  • base: 45d1621c93504e6cfa347b31995f70da868f4172
  • head: 8d26106195a0a24621f140e76389d206332f84c0
  • tree: b579380959e08f721492386da904972092bf8d7f
  • 검증 cwd: /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261003T222707Z-a0de27/1
  • HEAD/tree 일치, 시작·종료 시 git status clean. git diff --check 45d1621 HEAD 통과.
  • 변경 파일은 SKILL.md 4개, JSON 목록, 새 테스트 파일뿐이다. 제품 스크립트/Python 및 버전 변경 없음.

검증 cwd에서 다음을 실행했다. 152 passed, 1 skipped in 314.72s, 종료 코드 0. 로그: /tmp/fbd52b0b-selected.log.

AGENT_PYTHON_BIN=/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux-wt/pr-46/.venv/bin/python PYTHONDONTWRITEBYTECODE=1 /Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux-wt/pr-46/.venv/bin/python -m pytest -p no:cacheprovider -q --basetemp=/tmp/fbd52b0b-selected tests/test_issue42_skill_blocks.py tests/test_tier2_component.py tests/test_orc_onboard.py tests/test_loop_cli.py tests/test_o1_rebuttal.py tests/test_version_consistency.py tests/test_issue10_13_purge.py

이 중 purge 파일은 수집 결과 23건이며 전부 통과했다. 나머지 지정 6개 모듈은 129 passed, 1 skipped다. skip은 브리프에 명시된 untracked .claude/skills 링크 부재다. 전체 스위트는 실행하지 않았다.

수정 필요 사항

R1 — P2: 분리한 attach 단계에 격리 서버 선택을 보존해야 한다

  • 변경 근거: .agents/skills/multi-agent-mux-resume/SKILL.md:30, :60, :63.
  • 동작 근거: resume_session.sh:133–:144는 자식 bash 안에서 서버/워크스페이스를 export하고, :147–:158은 attach 없이 반환한다. .agents/skills/lib.sh:255–:269의 shim은 서버 환경값을 실제 CLI의 --session으로 바꾼다. native herdr가 환경변수 자체로 라우팅하지 않는다는 사실은 create SKILL.md:147에도 명시돼 있다.
  • 재현: lib.sh를 source하지 않은 새 셸에서 격리 서버의 세션에 대해 새 Workflow의 bash .../resume_session.sh ... --herdr-session probe-isolated를 실행한 뒤, 새 안내대로 herdr agent attach probe-codex를 실행한다. resume의 export는 부모 셸에 전달되지 않는다. 이미 caller가 다른 서버의 shim을 쓰고 있어도 자식의 설정은 caller의 서버를 바꾸지 않는다.
  • 기대: 방금 resume한 probe-isolated 서버의 agent에 attach한다.
  • 실제: 안내된 native 호출 argv에는 --session probe-isolated가 없으므로 기본/호출자 서버로 간다. 대상이 없으면 실패하고 동명 대상이 있으면 다른 대상으로 연결될 수 있다. 실제 서버에 접속하지 않고 임시 herdr argv 기록 대역으로 확인했다: agent attach probe-codex. 수정 호출은 --session probe-isolated agent attach probe-codex였다.
  • 수정 방향: 별도 attach는 유지하되 herdr --session <row.herdr_session 또는 명시한 서버> agent attach <name>처럼 서버를 명시한다. default와 격리 서버의 방법을 구분하거나, 현재 셸에서 lib.sh와 정확한 서버 값을 설정하는 방법을 안내한다. child script가 parent 환경을 설정한다고 가정하지 않는다. 새 Workflow에서 source/export를 지웠으므로 verification의 bare herdr agent get/read도 같은 관점에서 확인한다.

R2 — P2: dry-run을 무조건 무쓰기라고 설명하면 안 된다

  • 변경 근거: .agents/skills/multi-agent-mux-resume/SKILL.md:73의 “write nothing”, .agents/skills/multi-agent-mux-create/SKILL.md:164의 “spawn and write nothing”.
  • 동작 근거: resume resume_session.sh:168–:169는 macOS에서 agent binary의 quarantine 속성을 제거한다. dry-run 반환은 그보다 뒤인 :225–:227이다. create도 create_session.sh:195–:196에서 같은 동작을 하고 :278–:281에서 dry-run으로 반환한다.
  • 재현: 임시 HOME/WORKSPACE_ROOT/AGENT_SESSIONS_YAML, 빈 합성 레지스트리, mock codex/herdr 및 호출을 기록하는 mock xattr를 준비한다. 실제 resume 진입점에 --workspace <임시 root> --agent codex --session probe-codex --herdr-session probe-isolated --dry-run을 전달한다.
  • 기대: 새 옵션 표를 그대로 해석하면 파일/메타데이터 변경 작업도 호출하지 않는다.
  • 실제: rc 0 및 [dry-run] would spawn: ...을 반환하면서 xattr -d가 호출됐다. 실제 사용자 binary의 속성은 수정하지 않았다. create의 동일 경로는 정적 대조로 확인했다.
  • 수정 방향: 제품 코드를 바꾸지 말고 보장을 “agent spawn 및 session YAML/DB 갱신 생략”처럼 실제 범위로 한정하고, preflight가 binary 메타데이터 등 부수효과를 가질 수 있음을 적는다. 절대적인 무쓰기 보장을 원하면 별도 제품 변경이 필요하며 이 PR의 범위는 아니다.

R3 — P2: 삭제된 (a) 블록의 복귀를 검사하는 가드가 없다

  • 변경 근거: tests/test_issue42_skill_blocks.py:121–:127. test_no_duplicate_block_left()는 JSON의 class 문자열만 확인한다. :108–:113은 현행 문서와 현행 목록의 일치만 검사한다. 저장한 removed[].base_sha를 현재 블록과 비교하는 코드가 없다.
  • 재현: 테스트 모듈을 가져오고, base resume SKILL.md:59–118의 삭제된 Workflow 본문(sha 63abe1bb3542)을 현행 문서 끝에 다시 추가한 문자열을 만든다. 목록에도 해당 블록의 start/end/lang/sha를 정상 갱신하고 class=c, kind=example으로 기재한다. 파일을 수정하지 않고 모듈의 skill_text와 DATA에 메모리상으로 적용한 뒤 inventory, no-duplicate, replacement, exit-table, evidence 가드를 실행한다.
  • 기대: 알려진 삭제 (a) 본문이 그대로 돌아왔으므로 inventory 갱신 여부와 무관하게 거부한다.
  • 실제: 관련 가드가 전부 통과했다. 새 파일 설명의 “when an (a) block comes back” 보장과 다르다. inventory를 갱신하지 않은 일반 문서 변경은 잘 거부하지만, 그것은 별도의 보장이다.
  • 수정 방향: 알려진 삭제 본문의 hash가 현재 파싱된 본문에 없는지 명시적으로 검사한다. 필요하면 공백 정규화 fingerprint도 추가하되 의미가 다른 예시까지 막지 않는다. 위 재삽입 + inventory 갱신을 회귀 사례로 둔다. 제품 코드 수정은 필요 없다.

추가 개선 및 가드의 한계

C1 — baseline 37개에 대한 추적 하나가 빠졌다

base의 8개 SKILL.md를 실제로 파싱하면 37개, 현재는 33개다(현재 b=5, c=28). removed의 다섯 항목은 base 위치·종료 줄·hash가 모두 맞았다. 그러나 base create SKILL.md:263–266의 create_session.sh --workspace "$WORKSPACE" ... --session "$SESSION_NAME" 호출 블록도 삭제됐는데 historical 항목이 없다. 현재 JSON:87의 새 Workflow 항목은 base 168–220만 대체한다고 적고 있고, prose_mismatches의 C4는 없는 스크립트 이름을 고쳤다는 설명뿐이다.

이 호출은 (c)에 해당하며 삭제 자체가 기능 유실은 아니다. 다만 “37개를 모두 분류했다”는 단계 1의 감사 근거를 정확히 하려면 해당 base 블록을 (c)로 기록하고 새 호출과 통합됐다는 매핑을 추가해야 한다. 전체 baseline 목록과 현행 목록/삭제 이력을 구분하면 removed를 무조건 class=a로 강제하는 현재 스키마와도 충돌하지 않는다. 현재 가드는 DATA의 base/base_sha 자체를 검증하지 않는다.

C2 — 종료 코드 스캐너는 현재 세 스크립트의 표에는 맞지만 일반 규칙으로는 부정확하다

tests/test_issue42_skill_blocks.py:68–:78은 heredoc 본문에서 주석을 거르기 전에 SystemExit를 수집하고, heredoc 명령의 || true 여부를 보지 않는다. 반대로 unguarded Python의 sys.exit(N)은 수집하지 않는다. 임시 입력에 대한 _scan_exits() 탐침 결과:

입력 실제 shell 종료 의미 스캐너 결과
heredoc 안 # raise SystemExit(9) 주석: 9 아님 passthrough 9 포함
unguarded heredoc 안 sys.exit(9) + set -e 9 전달 passthrough 비어 있음
heredoc 안 raise SystemExit(9) + 명령 뒤 ` true`

현재 파일의 SystemExit(4)/SystemExit(1)은 실제 unguarded atomic write라 표가 맞고, stop의 sys.exit(7)은 || { exit 1; }로 감싸져 있으므로 7을 제외한 것도 맞다. 따라서 이 부분에서 현재 종료 코드 표가 틀렸다고 판정하지는 않는다. 다만 정상적인 주석 추가가 가드를 깨거나 동등한 sys.exit 변경을 놓칠 수 있다. Python 본문은 주석/문자열을 제외하고 두 exit 형태를 처리하며, passthrough 여부는 해당 호출의 처리 방식으로 제한하는 작은 fixture 테스트를 권장한다. 범용 shell 파서를 만들 필요는 없다.

숫자 행 가드는 설명 열의 의미까지 검증하지 않으므로 아래 종료 경로 검토는 별도로 수행했다. 줄 번호/hash를 고정하는 inventory는 prose 편집으로 블록 위치만 바뀌어도 함께 갱신해야 한다. 이는 설계상 비용이며 판정 사유로 삼지 않았다.

요청 항목별 대조

1. 삭제 분류와 resume의 단계별 대응

짧은 제품 경로는 .agents/skills/ 아래다.

삭제된 resume 단계 실제 근거 / 판단
session ID 해석 resume_session.sh:111–112 → resolve_session_id.sh
ID 없는 Class A fresh-spawn :122–130, :172–184; Class B는 :127–128에서 실패
서버/label 선택 :133–144
herdr alive면 attach :147–158은 row 갱신 + 종료이며 attach 없음. 불일치 R1/R2가 JSON에 기록됐고 별도 attach로 정정한 방향은 맞지만 서버 선택 보완 필요(위 R1)
agent command / 기동 :172–197의 adapter/fallback, :200–211 binary 검사, :237 spawn
handle_startup_dialogs :241, 이어서 :244 sleep
update_yaml_resumed.sh :247–250; agent/workspace/server/cmd-full 등 옵션 전달. 이미 살아 있는 경로는 :154–157
마지막 attach 스크립트에 없음. 사람이 별도 실행해야 하는 동작을 prose로 남겼다. 순수한 “모든 줄이 그대로 중복”은 아니며 JSON mismatch가 그 차이를 명시한다

나머지 삭제 항목: create preflight는 create_session.sh:103–158, Workflow는 :160–168 / :207 / :249–274 / :361–412, YAML append는 :491–610에 구현돼 있다. stop preflight는 stop_session.sh:99 / :125–143 / :184–188에 있다. 기본 stop의 no-op은 purge가 아닐 때만 적용된다. 기존의 잘못된 양 agent 동시 체크, 고정 sleep, 없는 yaml-edit 안내 등을 제거한 것은 타당하다.

남겨진 (b) 다섯 개도 타당하다: create/resume/stop의 사후 검증 블록은 운영자가 결과를 확인하는 절차이고, monitor runbook에는 새 drift만 사람에게 보고하기·중지 판단·hard rules가 있다. status preflight에는 status.sh가 하지 않는 has_real_herdr 체크가 섞여 있다. 이런 사람/agent 측 절차를 이번 단계에서 지우지 않은 판단은 맞다. 단, 새 진입점 호출은 caller 셸의 변수나 서버 선택을 설정하지 않는다는 R1을 반영해야 한다.

2. 옵션, 종료 코드, 다섯 정정

create parser :74–93 및 resume parser :35–47와 새 옵션을 대조했다. stop의 세 호출 예제는 --agent를 유지하고 purge에 --yes가 추가됐다. 임시 HOME/WORKSPACE_ROOT/AGENT_SESSIONS_YAML로 create/resume/stop 실제 --help를 실행해 모두 rc 0을 확인했다. stop help의 짧은 agent 목록 등 기존 제품 help 불일치는 이 PR에서 새로 만든 문제가 아니다.

  • resume Case 3: no-attach/permission flag 미검사 정정은 스크립트와 일치. 별도 attach의 라우팅만 R1.
  • purge: stop_session.sh:191–198에서 --yes 없으면 rc 3. 문서 :57은 사용자 확인 후에만 --yes를 허용하므로 판단 단계도 보존됐다.
  • status JSON: status.sh:209–239의 sessions_detail 필드 및 reconcile.sh:1083–1091의 상위 필드와 일치. resume_state 값도 일치한다.
  • create cmd_full: spawn-spec 경로와 일치. 없는 yaml-edit 스크립트 참조 제거는 올바르다.
  • create exit 4: 단순 header 숫자가 아니라 :515의 SystemExit(4)가 전달된다. trap :320–352가 rollback한다. exit 43은 kill 실패 후 살아 있는 경우다. exit 42는 ready timeout의 abort 경로이며 MAM_KILL_ON_READY_TIMEOUT=1로 살아 있는 agent도 rollback 대상으로 삼을 수 있다는 추가 설명이 있으면 더 정확하다.
  • resume의 post-spawn update rc 1/2, stop의 post-stop SystemExit(1) 구분을 표에 남긴 점은 맞다. other는 set -e를 통해 전달되는 실패를 포함한다.

3–5. 문서 사용성·가드·범위

사용자 purge 확인은 유지됐고 호출 예제는 실행 진입점으로 통일됐다. 기존 test_comp_docs_stop_examples_pass_agent도 선택 스위트에서 통과했다(최소 3개, 모두 --agent). 일반 inventory drift를 잡는 효과는 확인했다: 메모리상으로 create/resume/stop/status 각각을 base 문서로 되돌리면 현행 inventory 가드는 네 경우 모두 실패한다. 다만 알려진 삭제 본문 복귀는 R3처럼 누락된다.

제품 코드 변경·버전 bump·push/merge/이슈 상태 변경 없음. 실제 세션·herdr 서버·실제 .mam.env 값은 접근하지 않았고, 탐침은 합성 env/임시 HOME/임시 registry/mock binary만 사용했다. 검증 트리와 source/main 코드는 수정하지 않았다. 실제 .mam 쓰기는 이 잡의 지시된 lifecycle 및 최종 리포트에만 한정했다.

6. “purge 12개 기준 실패” 주장

이번 macOS 실행에서 해당 파일은 23/23 통과했다. base..head에서 그 테스트와 purge 구현은 변경되지 않았다. 따라서 이 PR이 purge 회귀를 만들었다는 증거는 없지만, 이 호스트에서도 12개 실패가 당연하다는 주장은 뒷받침되지 않는다. PR의 전체 실행 환경·실패 로그·동일 환경 base 비교가 있어야 Linux의 알려진 실패와 동일하다고 결론 낼 수 있다. 이번 리뷰에서는 외부 PR을 수정하거나 전체 스위트를 재실행하지 않았다.

독립 탐침 재현 자료

  • /tmp/fbd52b0b-probes.py: safe --help/--dry-run, attach argv, 삭제 Workflow 복귀 가드 탐침. 결과 /tmp/fbd52b0b-probes.log.
  • /tmp/fbd52b0b-guard-probes.py: base 문서 거부 확인, 종료 코드 스캐너의 합성 입력. 결과 /tmp/fbd52b0b-guard-probes.log.
  • 실행: 위 검증 cwd에서 PYTHONDONTWRITEBYTECODE=1 <위 Python> /tmp/fbd52b0b-probes.py "$PWD", 이어서 같은 인터프리터로 guard-probes.py 실행. 모두 종료 코드 0이며, 이는 탐침 실행 성공을 뜻한다. 발견한 문서/가드 문제의 해결을 뜻하지 않는다.

[VERDICT: NOT PASS]