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, baseed293dc-> heade80186f, 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
86 lines
7.2 KiB
Markdown
86 lines
7.2 KiB
Markdown
# 🔍 Review Report — PR #38 델타 리뷰 2회차 (job `895b0370`)
|
||
|
||
- **Reviewer**: `planner-reviewer-claude-01` (role: planner,reviewer) · 2026-09-25
|
||
- **대상 델타**: `ed293dc..e80186f` (2커밋, 3파일 `+73/−10`, `git diff --check` 클린), base `main 7a4a0c2` (v4.7.3).
|
||
- **검증 경로**: `/var/folders/.../T/mam-review/loop-20260925T072328Z-2f5853/1`. **identity 확인**: `HEAD = e80186f5…4774`, `HEAD^{tree} = 35018688…7a72` — 브리프와 일치.
|
||
- **1회차 내 판정**: PASS + K1~K4 (job `107d435d`). 이번에는 N1/R1(cancelled 사유)·R2(원격 마감 문구)·K1(STRAY_RULE)의 정정을 본다.
|
||
- **안전 규칙 준수**: 검증 경로·소스 워크트리·본 체크아웃(`main 7a4a0c2`) 모두 수정·커밋·checkout 없음. 실험은 in-process 러너(`env -u MAM_VERSION`, 임시 등록부·감사 로그)와 되돌린 사본에서만. 실제 herdr·세션·`.mam`·데몬 조작 없음, push·merge·PR 상태 변경 없음.
|
||
|
||
## 결론 요약
|
||
|
||
**세 정정 모두 지적을 닫고, 1회차 판정을 뒤집을 요소는 없다.** 종결 사유 12가지를 `_subjob_state` 에 넣어 분류가 설계대로임을 확인했고(감사 ndjson 경로·`.events.log` 폴백 양쪽), `get_feedback` 의 기본 동작은 그대로다. 되돌린 사본에서 T13×2·T14·T6 만 실패한다. 비차단 관찰 1건(L1).
|
||
|
||
## 1. N1/R1 — `cancelled` 사유 (항목 1)
|
||
|
||
- `registry.get_feedback(job_id, registry_dir, events=("completed","error"))` (`registry.py:316-317`): 기본값이 종전과 같아 호출자 — 래퍼의 `get-feedback` CLI 3곳(`:556`,`:565`,`:593`)과 `registry.py get-feedback` 서브커맨드(`:668`) — 는 인자 없이 부르므로 **동작 불변**. 실측: `error "first"` 뒤 `cancelled "session_busy"` 가 온 잡에서 기본 호출은 `'first'`, `events=(…,"cancelled")` 는 `'session_busy'`.
|
||
- `_subjob_state`(`runner.py:684-702`)만 `cancelled` 를 포함해 읽는다(`:691`).
|
||
- 두 경로 모두 확인: `.events.log` 에만 `cancelled` 가 있는 잡 → `NOT-DISPATCHED/target_not_found`; **감사 ndjson 에만**(`.events.log` 없음, `source_event: cancelled`) 있는 잡 → `NOT-DISPATCHED/session_busy`.
|
||
|
||
## 2. R2 — 첫 단어 판정 (항목 1)
|
||
|
||
`closed_kind = reason.split(" ", 1)[0]`(`:696`) 을 `ORCHESTRATOR_CLOSED` 와 비교한다. 원격 러너의 실제 문구는 `remote.py:352` 의 `"deadline_timeout (remote runner)"` 다. 오분류 여부를 사유 12가지로 실측:
|
||
|
||
| 상태·사유 | 분류 |
|
||
|---|---|
|
||
| `error` `deadline_timeout` (래퍼) | CLOSED-UNCONFIRMED |
|
||
| `error` `deadline_timeout (remote runner)` | **CLOSED-UNCONFIRMED** (R2 정정) |
|
||
| `error` `agent_blocked` | CLOSED-UNCONFIRMED |
|
||
| `error` 에이전트 자신의 사유(`tests failed on 3 cases`) | `error`(실패) — 첫 단어 `tests` 는 튜플 밖 |
|
||
| `error` 사유 없음 | CLOSED-UNCONFIRMED (기존 규칙) |
|
||
| `cancelled` `target_not_found` / `session_busy` / `rolled back by create_session` | **NOT-DISPATCHED** (N1 정정; `session_busy` 는 첫 단어 판정에 걸리지 않고 cancelled 분기로 간다) |
|
||
| `cancelled` 사유 없음 / `operator abort` | `cancelled`(실패 집계) |
|
||
| `completed` `all done` | completed |
|
||
|
||
첫 단어 판정이 `session_busy`·`target_not_found`·`rolled` 등을 미확인 종결로 오분류하지 않는다. 남는 이론적 여지는 에이전트가 **자기 사유를 `deadline_timeout`/`agent_blocked` 로 시작하는 문장으로 쓰는 경우**뿐인데, 이 두 단어는 오케스트레이터 예약어라 실무에서 겹치지 않는다(L1, 정보).
|
||
|
||
## 3. K1 — `STRAY_RULE` (항목 1)
|
||
|
||
`runner.py:41-42` 의 문장은 "귀속되지 않은 열린 잡은 `--parent-job` 없이 등록된 이 작업의 하위 위임일 수 있다; 그 산출물이 작업 목표에 필요하면 NOT PASS" 이고, `_subjobs_block` 은 stray 가 있을 때만 stray 줄 뒤에 붙인다(`:727-729`). SUBJOB_RULE(하위 잡이 있으면 대체 완료 입증이 없는 한 NOT PASS)과 **모순되지 않는다** — stray 는 무관한 잡일 수 있으므로 "필요하면" 이라는 조건부가 맞고, 둘 다 같은 방향(미완 → NOT PASS)이다. stray 가 없으면 프롬프트는 1회차와 바이트 동일(`if not rows and not stray: return ""` 유지).
|
||
|
||
## 4. 테스트 (항목 1·2)
|
||
|
||
### 4-1. 수정 없이는 실패하는가 — 확인함
|
||
|
||
검증 경로 사본에서 `registry.py`·`runner.py` 만 `ed293dc` 판으로 되돌리고 `-k "t13 or t14 or t6 or t5"` 를 돌렸다:
|
||
|
||
```
|
||
4 failed, 3 passed, 13 deselected in 22.39s
|
||
FAILED test_t13_…[target_not_found] ← N1
|
||
FAILED test_t13_…[session_busy] ← N1
|
||
FAILED test_t14_the_remote_runners_deadline_wording_is_closed_unconfirmed ← R2
|
||
FAILED test_t6_an_unattributed_open_job_is_shown_and_waited_for_only_on_request ← K1 (새 단언)
|
||
```
|
||
|
||
T5/T5b(`deadline_timeout` 정확 일치·늦은 쓰기 무효화)는 양쪽에서 통과 — 기존 분류가 정정으로 바뀌지 않았다. T13·T14 는 실제 `registry.py close` + `publish_event` 로 종결을 만드는 시험이라(mock 아님) 원격 러너의 실제 문구 그대로를 검증한다.
|
||
|
||
### 4-2. 검증 경로에서의 실행
|
||
|
||
```
|
||
tests/test_issue34_job_tree.py tests/test_issue5_phase1_delegation.py tests/test_issue14_delivery_ack.py
|
||
tests/test_loop_cli.py tests/test_issue25_plan_reuse.py tests/test_issue21_loop_b.py
|
||
→ 158 passed in 271.04s failed 0 · skipped 0
|
||
```
|
||
|
||
새 파일 20건 전부 통과, `registry.py` CLI 스냅샷·help 캡처(issue5 phase1) 통과 → `get_feedback` 시그니처 확장이 CLI 계약을 건드리지 않는다. 실제 `.mam/jobs` 전후 **차이 0**, 검증 경로 tracked 파일 변화 없음(HEAD `e80186f`), 본 체크아웃 `main 7a4a0c2`.
|
||
|
||
## 5. 병합 차단 여부 (항목 2)
|
||
|
||
1회차 PASS 를 뒤집을 요소는 없다. 델타는 `get_feedback` 의 기본값 보존 인자 하나, `_subjob_state` 의 두 줄, `STRAY_RULE` 한 문장, 회귀 3건이며, 정정 대상만 정확히 실패→통과로 바뀌었다. 버전 락스텝 bump 부재(`lib.sh:32` `4.7.3`, 오케스트레이터가 4.7.4 로 처리 예정) 외에 **병합을 막을 결함은 없다**. 1회차 K2(`MAM_JOB_TREE_MAX` 초과 시 대기 생략)·K3(`target_not_found` 의 rc 5)·K4(폴링마다 전체 스캔)는 이 델타 범위 밖이라 그대로 열려 있다.
|
||
|
||
| # | 비차단 관찰 | 근거 |
|
||
|---|---|---|
|
||
| L1 | 첫 단어 판정은 에이전트가 자기 사유를 `deadline_timeout`/`agent_blocked` 로 시작해 쓰면 미확인 종결로 본다 — 두 단어는 오케스트레이터 예약어라 실무 충돌은 없다 | `runner.py:696`, §2 표 |
|
||
|
||
## 6. 교차검증 가이드
|
||
|
||
```bash
|
||
RP=/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20260925T072328Z-2f5853/1
|
||
PY=/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python
|
||
git -C "$RP" rev-parse HEAD 'HEAD^{tree}' # e80186f… / 3501868…
|
||
# in-process (env -u MAM_VERSION): 임시 등록부에 12가지 종결 사유를 .events.log / 감사 ndjson 으로 심고 _subjob_state 호출 (§2 표)
|
||
# 되돌린 사본: registry.py·runner.py 를 ed293dc 판으로 → -k "t13 or t14 or t6 or t5" → 4 failed / 3 passed
|
||
(cd "$RP" && AGENT_PYTHON_BIN="$PY" PYTHONDONTWRITEBYTECODE=1 "$PY" -m pytest -p no:cacheprovider -q --basetemp=/tmp/bt tests/test_issue34_job_tree.py) # 20 passed
|
||
```
|
||
|
||
[VERDICT: PASS]
|