Files
multi-agent-mux/.agents/reports/planner-reviewer-codex-01/report-c1dec920.md
T
GodopuandClaude Fable 5.1 22586d2a01 release(v4.7.4): bump framework and eight skills to 4.7.4 in lockstep; archive PR #38 merge-side reviews
Issue #34: the loop waits for the creator job's sub-jobs, reviewers get
the sub-job table as evidence, a run with sub-jobs voids a round whose
source changed during review, and a pane caller reaches an agent of
another workspace on its own server. VERSIONS.md carries the entry; the
merge-side reports (round 1 @ed293dc and round 2 @e80186f with identity
sidecars, base ed293dc -> head e80186f, evidence review-tree) are archived
under .agents/reports/<session>/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-25 16:44:20 +09:00

11 KiB

PR #38 독립 리뷰 — c1dec920

판정: NOT PASS. 새 하위 잡 분류 코드가 실제 종결 이벤트 생산자/조회자의 계약과 맞지 않는 P2 결함 두 건이 재현됐다. 기존 테스트의 실패와 구분하여 아래에 기록한다. 설계 재계획이 필요한 사안은 아니며, 종결 사유 조회·정규화와 회귀 테스트 추가로 수정할 수 있다.

검증 대상

  • 역할: planner,reviewer / 이번 잡은 Reviewer.
  • base: 7a4a0c209195c20719a50e28398416ccd19004e2
  • head: ed293dcef4c346e0050a31cb46812bcdcf1d67cf
  • tree: 671b0e59eef915f89d1dce63e9c0796db132899c
  • 검증 경로: /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20260925T055502Z-a37cf1/1
  • 아래 파일:라인은 이 HEAD 기준이다. 검증 전후 HEAD/tree가 일치하고 git status --short는 비어 있다. 제품 코드 수정, 커밋, 실제 herdr 세션 조작은 하지 않았다. 탐침·수정 방향 검증·역검증은 임시 자료 또는 일회성 Python 프로세스에서 수행했다.

R1 — P2: 취소 이벤트의 사유를 읽지 못해 NOT-DISPATCHED 분기가 작동하지 않음

근거

  • .agents/skills/lib_py/loop/runner.py:687: registry.get_feedback()에서 종결 사유를 가져온다.
  • 같은 파일 :693: cancelled + session_busy/target_not_found/rolled back 사유일 때만 NOT-DISPATCHED로 분류한다.
  • .agents/skills/lib_py/delegation/registry.py:325, :327, :342: audit 및 로컬 events.log 경로 모두 completed/error만 읽고 cancelled는 제외한다.
  • 실제 생산자는 .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job:926에서 cancelled를 발행한다. create rollback도 .agents/skills/multi-agent-mux-create/scripts/create_session.sh:340에서 cancelled + rolled back by create_session을 발행한다.

재현 절차

  1. 임시 HOME/WORKSPACE_ROOT/registry/audit 경로를 만들고 브로커를 127.0.0.1:9로 설정한다.
  2. 실제 delegate-job 래퍼의 submit --agent human --agent-session herdr:img --dry-run --registry-dir <tmp-reg> --prompt probe로 부모를 등록한다. 같은 명령에 --parent-job <parent>를 추가해 자식을 각각 등록한다.
  3. 자식 레코드의 dispatch token과 attempt를 사용해 실제 publish_event.py --registry-dir <tmp-reg> --job <child> --expect-dispatch-token <token> --attempt-id 1 --event cancelled --detail <reason>를 실행한다.
  4. 같은 registry/audit 환경에서 LoopRunner._subjob_state(registry.load_job(child, registry_dir)) 및 _subjobs_block([parent])를 읽는다.
발행한 detail 기대 상태·사유 실제 상태·사유
session_busy NOT-DISPATCHED / session_busy cancelled / -
target_not_found NOT-DISPATCHED / target_not_found cancelled / -
rolled back by create_session NOT-DISPATCHED / rolled back by create_session cancelled / -

세 경우 모두 레코드는 cancelled이고 events.log에 정확한 detail이 남아 있다. 브로커 연결 거절로 publisher rc=2지만 로컬 종결·로그 기록은 완료됐다. get_feedback()만 빈 문자열을 반환한다. 리뷰 표에서는 세 사유가 모두 사라진다.

영향 및 수정 방향

리뷰어가 실제 실행 후 실패와 애초에 전달되지 않은 잡을 구분할 수 없다. 현재는 둘 다 실패 건수에 포함되므로 이 문제만으로 자동 PASS가 되는 것은 아니다. 그러나 이번 PR이 명시한 상태/사유 근거 제공 계약을 충족하지 못한다.

종결 사유 조회가 cancelled도 처리하도록 하거나, 기존 completed/error feedback 계약을 유지해야 한다면 세 terminal 이벤트를 다루는 별도 조회 함수를 새 소비자에서 사용해야 한다. audit와 로컬 events.log fallback을 모두 검사하는 회귀 테스트를 추가한다. 새 T12는 cancelled 이벤트의 존재만 검사하고 그 이벤트가 리뷰 표에서 어떻게 분류되는지는 검사하지 않는다.

임시 Python 프로세스에서 get_feedback의 세 이벤트 필터에 cancelled를 포함시킨 뒤 같은 저장 레코드 세 건이 모두 기대한 NOT-DISPATCHED + 원래 사유로 분류됨을 확인했다. 소스 파일은 변경하지 않았다.

재현 자료: /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/c1dec920-wrapper-probe-_555g31h/review-block.md 및 인접 reg/, .mam-logs/.

R2 — P2: 원격 러너의 실제 deadline 사유를 CLOSED-UNCONFIRMED로 인식하지 않음

근거

  • .agents/skills/lib_py/loop/runner.py:36: 허용 사유는 정확히 deadline_timeout/agent_blocked 두 문자열이다.
  • 같은 파일 :690: 문자열 완전 일치로 미확인 종결을 판단한다.
  • .agents/skills/lib_py/delegation/remote.py:352: 실행 명령 없이 외부 에이전트의 종료를 기다리는 원격 러너는 제한 시간이 지나면 deadline_timeout (remote runner)라는 detail로 error를 발행한다. 외부 에이전트를 중단시키는 경로는 아니다.

재현 절차

  1. 임시 registry/audit와 127.0.0.1:9 브로커 환경에서 UUID4 job_id/dispatch_token, attempt_id=1, timeout_sec=1인 dispatch를 만든다.
  2. 실제 remote.handle_dispatch(dispatch, registry_dir, "probe", None)를 실행한다. 테스트에서는 heartbeat 타이머 생성만 생략하도록 remote._publish를 감싸고, started/error 발행·로컬 기록은 실제 producer를 사용한다. 실제 에이전트는 실행하지 않는다.
  3. 종료 레코드를 LoopRunner._subjob_state()에 전달한다.

실측:

runner rc: 2
record status: error
get_feedback: deadline_timeout (remote runner)
expected: CLOSED-UNCONFIRMED / deadline_timeout (remote runner)
actual: error / deadline_timeout (remote runner)

영향 및 수정 방향

이 원격 마감은 에이전트가 계속 작업할 수 있는 미확인 종결인데, 일반 error로 집계되어 미확인 종결 수와 해당 경고가 누락된다. error도 실패 규칙의 대상이고 하위 잡이 있는 경우 지문 가드는 켜지므로, 이 사례만으로 거짓 PASS가 발생했다고 주장하지는 않는다.

실제 생산자의 사유를 canonical reason으로 정규화하거나, 알려진 원격 마감 문자열을 분류기에 포함시킨다. 임의의 모든 error를 마감으로 취급해서는 안 된다. 실제 handle_dispatch(..., exec_cmd=None)의 마감 결과를 소비하는 회귀 테스트가 필요하다. 일회성 Python 프로세스에서 이 정확한 문자열을 인식하게 했을 때 기대한 CLOSED-UNCONFIRMED가 됨을 확인했다.

실제 producer 탐침 자료: /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/c1dec920-extra-uj1j3nhp/.

검증 결과 및 범위

실행 인터프리터는 브리프의 main venv Python이며, 검증 경로에서 AGENT_PYTHON_BIN=<해당 Python> PYTHONDONTWRITEBYTECODE=1 python -m pytest -q -p no:cacheprovider --basetemp=<검증 경로 밖 임시 경로>를 사용했다.

선택 파일:

  • test_issue34_job_tree.py — 신규 17개 사례 포함
  • test_issue21_worktree_prereqs.py, test_issue21_phase2_loop_a.py, test_issue21_loop_b.py, test_issue21_acceptance.py
  • test_issue5_phase1_delegation.py
  • test_tier4_e2e.py
  • test_issue23_24_startup_path.py
  • test_issue7_workspace_packing.py, test_herdr_shim_contract.py

총 287개 서로 다른 선택 항목의 통과를 확인했다. 한 번의 무조건적인 전체 green 실행이라는 의미는 아니며, 실행 조건과 원시 결과는 다음과 같다.

실행 결과
선택 묶음 최초 집계 284 passed, 3 failed / 894.44s
Issue21 L11, A4 재실행 2 passed / 2.26s
Tier4 scenario5 별도 재실행 1 passed / 338.00s, MAM_INJECT_ACK_GRACE_S=1
변경 셸 bash -n, 변경 Python AST 구문 검사 통과
git diff --check base..HEAD 통과

L11/A4의 실패는 테스트가 mam-review 문자열이 없어야 한다고 검사하는데 basetemp 이름을 mam-review-c1dec920.*로 정했기 때문이다. 코드의 실제 경로 누출/정리 실패가 아니며, 이 문자열이 없는 /tmp/c1dec920-rerun.*에서 두 항목이 통과했다.

최초 Tier4 scenario5는 모의 에이전트가 started를 발행하지 않아 잡마다 180초 ACK 대기를 하는 것을 확인하고 해당 샌드박스 loop 프로세스 트리만 중단했다. 따라서 최초 실패의 rc=143은 제품 결함 판정에 사용하지 않았다. 같은 테스트를 ACK grace=1로 재실행해 실제 두 리뷰 회차와 최종 PASS까지 통과했다. 기본 180초 설정으로 끝까지 완료한 결과는 주장하지 않는다.

역검증은 임시 skills 사본에서 runner.py만 base 버전으로 교체하여 신규 T3와 T5b[True]를 실행했다. 2 failed / 10.45s로, 자손 대기와 CLOSED-UNCONFIRMED 리뷰 근거가 없다는 이유로 실패했다. 제품 파일에는 역변경을 적용하지 않았다.

추가 확인:

  • 실제 래퍼/등록부로 부모 flag, 존재하지 않는 부모 거절, MAM_JOB_ID fallback, 직접 자식 목록을 검사하는 신규 테스트가 통과했다. 깊이는 조회 시 계산된다. 순환/자기 참조는 조회의 visited 집합으로 무한 탐색을 방지하며, Python register_job API 자체가 모든 잘못된 parent 입력을 거부하는 것은 아니다.
  • 503개 임시 레코드(자손 502개) 탐침에서 깊이 1/2/3과 자기 참조 배제가 맞았고, 조회는 약 0.0098초였다. 매 폴링 전체 스캔은 남아 있다. MAM_JOB_TREE_MAX는 스캔 자체의 상한이 아니라 대기를 중단하는 상한이며, 이후 리뷰 표에서 열린 잡을 숨기지 않는다.
  • 실행·수정·순응 경로의 wait_for_job_tree 연결과 이전 creator 잡 자손의 후속 회차 노출을 확인했다. 자손 대기 시간/개수 초과는 루프 즉시 실패가 아니라 리뷰 근거로 처리하는 설계다. 실제 signal handler는 checkpoint 후 exit한다.
  • 자손과 귀속 불명 열린 잡이 모두 없으면 prompt 추가 블록은 빈 문자열이다. 자신의 created_jobs는 제외한다. 다른 루프의 부모 없는 새 열린 잡은 시간 기반 휴리스틱으로 나타날 수 있다. 문서의 기본 wait-open-jobs=off 및 무관한 잡에 대한 경고와 일치하지만, 이 목록을 해당 creator 소유임이 입증된 목록으로 해석해서는 안 된다.
  • 임시 Git 저장소에서 .mam, jobs, 상대경로 audit 파일 변경은 fingerprint를 바꾸지 않고 소스 변경은 바꾸는 것을 확인했다. 소스 밖 제어 경로 제외와 identity 모드 분리는 코드로 확인했다.
  • 같은 서버 다른 workspace 조회, 명시적 불일치/외부 pane scope 보존, 기존 교차 서버 규칙, workspace packing, shim 계약 테스트가 통과했다.
  • parent 안내문 및 loop 문서는 주요 대기/리뷰/지문 동작을 설명한다. 위 R1/R2는 그 설명과 실제 terminal reason 소비 계약의 차이다.
  • 예정된 4.7.4 버전 bump 부재는 거절 사유로 삼지 않았다.

두 종결 분류 결함을 수정하고 실제 producer → reason reader → 리뷰 표까지 잇는 회귀 테스트를 추가한 뒤 재리뷰해야 한다.

[VERDICT: NOT PASS]