Files
multi-agent-mux/.agents/reports/planner-reviewer-codex-01/report-ee9f0555.md
T
GodopuandClaude Opus 5.5 53f87e4560 release(v4.8.0): bump framework and eight skills to 4.8.0 in lockstep; archive PR #41 reviews (three rounds)
Issue #33 (job budget, wall_clock_timeout vs deadline_timeout) and
Issue #30 (notify-only stall watcher). Reviews of PR #41 in the merge
workspace: round 1 (@1673818) claude N1 / codex R1 R2 NOT PASS, agy grok
PASS; round 2 (@af0a2a2) grok N1 / codex D1 D2 NOT PASS, claude agy PASS;
round 3 (@9200562) 4/4 PASS. Reports and the round-3 identity sidecars go
to .agents/reports/<session>/. The design consensus on items A-D is in the
VERSIONS.md entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-09-26 16:08:37 +09:00

68 lines
9.4 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.
# PR #41 3회차 델타 리뷰 — ee9f0555
**PASS.** 2회차 D1·D2를 재현했던 독립 탐침이 이제 prompt를 보내지 않는다. 감시자 정리의 5초 유예도 제거됐다. 이번 delta에서 새 병합 차단 결함을 발견하지 않았다.
## 검증 identity 및 범위
- base: `af0a2a2557768fa316203b177e1b90e9f7283a1a`
- head: `9200562c914ab4f6e6d8a599cac371a07f99b25d`
- tree: `c8425e2259544c3a34210858f331b0563f15325c`
- loop: `loop-20260926T062809Z-914379`
- 검증 cwd: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20260926T062809Z-914379/1`
4개 파일(+144/−31)의 delta를 검토했다. 검증 트리는 수정하지 않았으며 HEAD/tree 일치와 깨끗한 git 상태를 확인했다. 실제 에이전트/herdr 세션을 조작하지 않았다. 테스트와 추가 탐침의 레지스트리·HOME·프로세스는 임시 경로에 격리했다.
사용자의 후속 지시에 따라 본 체크아웃의 업데이트된 `.mam.env`를 확인했다. 비밀값을 출력하지 않고 새 MQTT 연결 설정만 추가 테스트 환경에 전달했다. 실제 세션 등록부 경로나 herdr 설정은 테스트에 새로 전달하지 않았다.
## 이전 차단 항목 처리
| 항목 | 판정 | 근거 |
| --- | --- | --- |
| D1: 알림 발행 중 완료/재디스패치 후 prompt | 닫힘 | `stall_watch.py:408`의 `_may_prompt()`가 블로킹 `_notify()` 이후에 최신 레코드와 부모를 확인한다. 실제 publisher 로직을 사용한 이전 탐침에서 completed/attempt 2 모두 prompt 0회. |
| D1: 마지막 읽기 오류가 재주입 허가로 취급됨 | 닫힘 | `stall_watch.py:278`~`:279`가 strict 검사에서는 오류를 False로 처리한다. 깨진 JSON·없는 레코드·PermissionError 모두 최종 게이트에서 보류. 관측 루프의 일시 오류 허용과 분리됐다. |
| D2: herdr 대기 중 부모 소멸 뒤 알림·prompt | 닫힘 | `stall_watch.py:329`에서 wait 반환 직후, 오류 경고보다 먼저 부모를 확인한다. 독립 TIMEOUT/TRANSPORT_ERROR 탐침에서 알림·prompt 모두 0회. 발행 중 부모 소멸도 마지막 게이트에서 차단됐다. |
| grok N1: TERM 유예 5초가 종결 경로를 지연 | 닫힘 | 래퍼 `:715`~`:716`이 KILL+wait로 즉시 회수한다. 새 즉시 반환 테스트는 head에서 통과하고 base의 5초 유예 함수에서 assertion으로 실패했다. |
표의 Python 경로는 `.agents/skills/lib_py/stall_watch.py`, 래퍼 경로는 `.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job`이다.
## KILL 전환 판단
전환은 타당하다. 이 감시자는 실제 작업 에이전트가 아니라 관측·알림 보조 프로세스다. 자체 registry 트랜잭션이나 종료 때 반드시 기록해야 할 체크포인트를 보유하지 않는다. 이전 TERM 핸들러도 `os._exit()`였으므로 정상 finally 정리나 버퍼 flush를 보장하던 동작을 새로 잃는 변경은 아니다. 빈 PID와 이미 종료한 자식의 처리·오류 보고 분기도 유지된다. 자기 자식을 KILL한 뒤 wait로 회수하는 경로에 종전의 TERM 무시/5초 유예 문제는 없다.
다만 KILL은 프로세스 그룹 전체를 죽이지 않는다. 실행 중이던 herdr/publish 자식은 잠시 남을 수 있다. `agent wait`에는 요청한 timeout이 있고 publisher는 자체 CONNACK 10초/PUBACK 5초 제한을 갖는다(`lib_py/delegation/publisher.py:23`~`:24`). 일반적인 정상 CLI/네트워크 실패 경로에서는 이 자식들이 끝난다. 반면 `HerdrSource._herdr`의 30초, `_notify`의 120초는 감시자 측 `subprocess.run` timeout이므로 부모가 KILL되면 그 타이머까지 살아남는다고 주장할 수는 없다. 따라서 외부 CLI 자체가 영구 정지하는 상황까지 “모든 자식의 종료 보장”으로 확대하지 않는다. 이는 이전 `os._exit()` 종료에도 있던 한계이며 이번 delta의 새 차단 결함으로 분류하지 않았다. 이번 실행 후 테스트 감시자·heartbeat 잔류 여부를 별도로 확인했다.
## 마지막 게이트와 경합 판단
`_may_prompt()`(`stall_watch.py:296`~`:301`)가 발행 뒤에 있고, 다음 줄의 실제 prompt 호출까지 추가 네트워크 대기를 두지 않는 점이 핵심이다. 이전의 최대 120초 발행 대기를 사이에 둔 오래된 관측 문제는 해결됐다. 잡 상태를 읽지 못한 회차에는 prompt를 보류하며, 발행 rc 0/2를 최신 dispatch의 생존 증명으로 사용하지 않는다.
최종 확인과 herdr 입력 사이에 다른 actor가 완료/재디스패치를 수행하는 짧은 경합은 여전히 가능하다. 이는 원자적 입력 프로토콜이 아니므로 절대적인 선형화 보장은 아니다. 그러나 이번 기능은 잡당 한 번의 계속 요청과 알림을 제공하며 잡 종결·자동 승인을 수행하지 않는다. 이 범위에서 블로킹 I/O 뒤 최신 상태를 확인하는 현재 게이트는 수용 가능하다. registry lock을 외부 RPC 동안 붙잡는 확장 설계를 이번 PR에 요구하지 않는다.
독립 탐침에서는 `_notify=True` 대역만 쓰지 않았다. 이전 회차의 탐침을 그대로 head에 적용해 실제 `publish_event.main()`의 wire 단계에 completed/재디스패치를 끼워 넣었다. publisher rc는 여전히 0이었으나 두 경우 모두 prompt가 0회였다. 부모 소멸도 wait 내부에서 실제로 조건을 바꾸는 별도 모의 관측으로 확인했다.
## 브로커 지연 분류
브리프에 기술된 subscribe 지연 사례를 브로커 의존 테스트의 타이밍 플레이크로 분류하는 것은 타당하다. `job_subscriber.py:489`~`:491`은 구독이 확인되기 전에는 idle 기준 시각을 계속 갱신하며, 최초 이벤트 전에는 ack 여유도 포함한다. 따라서 등록 후 14초에 completed를 쓰는 테스트는 구독 시작 자체가 늦으면 아직 열린 잡에 completed가 들어갈 수 있다. 이를 곧바로 감시자 stop 지연의 증거로 삼을 수 없다. 단순히 “ignored TERM 로그가 없다”는 사실만이 아니라 구독/이벤트 시각과 이 제어 흐름을 함께 봐야 한다.
이번 원본 선택 suite는 그대로 통과했다. 이어 사용자가 업데이트한 사설 브로커 설정으로 `test_wrapper_idle_timeout_grace_ignores_pane_busy_token`을 추가 실행했고 17.00초에 통과했다. 이전 회차에는 같은 모듈에서 공개 브로커 CONNACK 실패도 직접 관측했다. 이 증거들은 브리프의 분류와 부합한다. 다만 다른 호스트의 과거 세 실행 로그 전체를 이번에 독립 재현했다고 주장하지 않는다.
## 테스트 평가 및 비차단 메모
추가 테스트는 중요한 회귀를 잡는다. 다만 다음 정밀도 개선은 권한다.
- `tests/test_issue30_stall_watch_stop.py:421`~`:423`의 부모 생존 대역은 호출 횟수 기준이다. head에서는 첫 루프 머리·첫 wait 뒤의 두 호출 다음, 두 번째 루프 머리에서 이미 False가 되어 종료한다. 이름 그대로 “두 번째 wait 도중 소멸”을 강제하려면 wait 대역 안에서 별도 alive 상태를 바꾸는 것이 낫다. 이번 리뷰의 독립 탐침은 그렇게 구성해 실제 wait 뒤 검사를 확인했다.
- unreadable 테스트를 base에 그대로 연결하면 `_dispatch_open(strict)` 시그니처 차이로 TypeError가 먼저 난다. 이를 의도한 행동 검증으로 세지 않았다. 대신 임시 head 사본에서 `return not strict`만 `return True`로 되돌려 새 API를 유지한 채 테스트를 실행했고, 실제 prompt 발생에 따른 AssertionError를 확인했다.
- SKILL.md `:53`의 “최대 한 wait, ≤60초”는 herdr wait 도중 부모가 사라진 경우의 설명으로 한정하는 편이 정확하다. 발행 중 부모가 사라지면 진행 중인 발행 호출이 끝날 때까지도 기다릴 수 있다. 현재 코드는 그 뒤 prompt를 보내지 않으므로 D1·D2는 해결됐지만, 60초를 모든 외부 호출에 대한 전역 상한으로 읽으면 과장이다.
2회차에 지적했던 테스트 실패 경로의 최종 cleanup 보강 및 이전 회차의 #17 관련 비차단 부채는 이번 delta 밖의 후속 항목으로 유지한다. 새 차단 조건으로 올리지 않았다.
## 실행 결과
- 브리프의 worktree `.venv/bin/python`, `AGENT_PYTHON_BIN`, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, 외부 `--basetemp=/tmp/ee9f0555-suite` 사용.
- 지정된 `test_issue30_stall_watch_stop.py`, `test_issue30_stall_watch.py`, `test_issue14_delivery_ack.py`: **70 passed, 1 skipped, 426.01초**. skip은 Linux /proc 전용 S6이다.
- 업데이트된 MQTT 설정을 적용한 위 idle-grace 사례: **1 passed, 17.00초**. 기존 suite와 중복된 사례이며 별도 고유 테스트 수로 더하지 않았다.
- 독립 추가 확인: 실제 publisher 완료/재디스패치 2건, wait 중 부모 소멸 1건, 최종 게이트 정상/깨진 JSON/레코드 없음/권한 오류 4건, 발행 중 부모 소멸 및 오류 wait 중 부모 소멸 2건 통과.
- 부정 검증: base에서 완료·재디스패치·부모 소멸 테스트 3건 및 즉시 stop 테스트 1건이 예상한 assertion으로 실패. strict 정책만 되돌린 임시 사본의 unreadable 테스트 1건도 예상한 assertion으로 실패. 검증 트리 파일은 변경하지 않았다.
- `git diff --check af0a2a2`, 래퍼 `bash -n` 통과. HEAD/tree 및 깨끗한 git 상태 재확인.
[VERDICT: PASS]