release(v4.7.3): bump framework and eight skills to 4.7.3 in lockstep; archive PR #37 merge-side reviews
Issue #36: delegated prompts travel by file, a delegation that fails ends the loop with rc 1. VERSIONS.md carries the entry; the merge-side reports (round 1 @59be1c8 and round 2 @708262a with identity sidecars, base59be1c8-> head708262a, 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
This commit is contained in:
1 parent
708262a598
commit
c3e6a7e96d
28 files changed
+740
-26
No files matched your search
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"loop_id": "loop-20260924T085626Z-b8987e",
|
||||
"base_sha": "59be1c89209a232059aec0c5aeecc2329929cb49",
|
||||
"head_sha": "708262a598c4301c0ccc70efba5401093b4b1e30",
|
||||
"tree_sha": "d601c2d01bb4c09931e5e1989e140b01d51f5b96",
|
||||
"review_commit": "708262a598c4301c0ccc70efba5401093b4b1e30",
|
||||
"reviewer": "planner-reviewer-claude-01",
|
||||
"job_id": "1a9fbbc4",
|
||||
"verdict": "PASS",
|
||||
"transport": "herdr",
|
||||
"evidence": "review-tree",
|
||||
"isolation": "worktree",
|
||||
"verified_at": "2026-09-24T09:09:43Z"
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
# 🔍 Review Report — PR #37 델타 리뷰 2회차 (job `1a9fbbc4`)
|
||||
|
||||
- **Reviewer**: `planner-reviewer-claude-01` (role: planner,reviewer) · 2026-09-24
|
||||
- **대상 델타**: `59be1c8..708262a` (1커밋, 3파일 `+63/−7`, `git diff --check` 클린), base `main 1debc7d` (v4.7.2).
|
||||
- **검증 경로**: `/var/folders/.../T/mam-review/loop-20260924T085626Z-b8987e/1`. **identity 확인**: `HEAD = 708262a5…1e30`, `HEAD^{tree} = d601c2d0…5b96` — 브리프와 일치.
|
||||
- **1회차 내 판정**: PASS + H1~H5 (job `4ab12ae2`). 이번에는 H1(러너 쓰기 실패)·H3(빈 파일)·S9 이식성의 정정을 본다.
|
||||
- **안전 규칙 준수**: 검증 경로·소스 워크트리·본 체크아웃(`main 1debc7d`) 모두 수정·커밋·checkout 없음. 러너 실험은 in-process 스텁, 래퍼는 `--dry-run`+`--agent human`+도달 불가 브로커, 되돌린 사본은 scratchpad. 실제 herdr·세션·`.mam`·데몬 조작 없음, push·merge·PR 상태 변경 없음.
|
||||
|
||||
## 결론 요약
|
||||
|
||||
**세 정정 모두 지적을 닫고, 1회차 판정을 뒤집을 요소는 없다.** 내가 1회차에서 H1 을 재현했던 바로 그 스크립트를 정정된 러너에 다시 돌려 "실패 제출·부분 파일 없음·래퍼 미실행"을 확인했고, 정상·exec 실패·쓰기 실패 세 경로에서 파일이 **정확히 한 번**만 지워지는 것도 확인했다. 비차단 관찰 1건(I1).
|
||||
|
||||
## 1. R1/H1 — 러너의 쓰기 실패 (항목 1)
|
||||
|
||||
`runner.py:472-491`: `mkdir`·`NamedTemporaryFile`·`write` 가 하나의 `try` 로 들어가고, `prompt_file` 은 `""` 로 초기화된 뒤 파일이 생기는 즉시(`write` 이전) 이름을 받는다. `except OSError` 는 이름이 있으면 지우고 `CompletedProcess(…, 126, "", "cannot write the prompt file under <mam_dir>: <err> (prompt N bytes)")` 를 돌려준다. exec 용 `try/finally`(`:504-516`)는 그 뒤에 따로 있어 두 블록이 겹치지 않는다.
|
||||
|
||||
### 실측 (검증 경로 러너 in-process)
|
||||
|
||||
| 경로 | 결과 |
|
||||
|---|---|
|
||||
| **쓰기 실패**(1회차 H1 재현 스크립트 그대로: `write` 가 `OSError(28)`) | `delegate_job` 이 **None** 을 반환(예외 없음), 로그 `cannot write the prompt file under …: [Errno 28] No space left on device (prompt 5 bytes)`, `.mam` 잔존 **0**, 래퍼 호출 **0** |
|
||||
| 정상 | 래퍼 호출 시점에 파일 존재·내용 `'hello'`·권한 **0600**, `os.unlink` 호출 **1회**, 잔존 0 |
|
||||
| exec 실패(`subprocess.run` 이 `OSError(2)`) | rc 126 경로, `os.unlink` 호출 **1회**, 잔존 0, 반환 None |
|
||||
|
||||
- 반환하는 `CompletedProcess` 는 exec 실패와 같은 rc 126 이라 기존 처리(`submit failed (exit 126)` 로그, busy 재시도 없음, id 없음 → `failed_submit_jobs` 미등록·None 반환 → 호출자 `return 1`)에 그대로 합류한다. S4c 가 `submit failed (exit 126)`·`cannot write the prompt file`·`Traceback` 부재·래퍼 미실행·잔존 없음을 한꺼번에 단언한다.
|
||||
- `NamedTemporaryFile` 생성 자체가 실패하면 `prompt_file` 이 빈 채로 `except` 에 들어가 `unlink` 를 건너뛴다 — 지울 것이 없으니 맞다. `mkdir` 실패도 같은 블록이다.
|
||||
|
||||
## 2. H3 — 빈 `--prompt-file` 거부 (항목 2)
|
||||
|
||||
`multi-agent-mux-delegate-job:193` 의 `[[ -s "$PROMPT_FILE" ]]` 는 **래퍼 `submit` 의 입력**에만 걸린다. 정당한 빈 프롬프트 사용처를 막는지 실측:
|
||||
|
||||
| 경로 | 결과 |
|
||||
|---|---|
|
||||
| 래퍼 `submit --prompt-file empty.md` | `submit: --prompt-file <절대경로> is empty`, rc 1 (S1e) |
|
||||
| 래퍼 `submit --prompt-file newline.md`(개행 1바이트) | 등록됨 — `-s` 는 바이트 크기 기준이라 공백만 있는 파일은 통과한다(I1) |
|
||||
| 래퍼 `submit --prompt ""` | 종전대로 `submit requires --prompt or --prompt-file` 거부 — 텍스트 형식과 대칭 |
|
||||
| `registry.py register --prompt-file empty.md`(직접) | 등록됨(rc 0) — 등록부는 그대로 |
|
||||
| loop 형 회차의 `update --prompt-file "$job_dir/prompt.md"` | `cmd_submit` 을 거치지 않으므로 검사 밖(변경 없음) |
|
||||
|
||||
즉 새 검사는 "빈 텍스트를 거부하던" 기존 규칙을 파일 형식으로 확장한 것이고, 등록부·loop 회차 경로는 건드리지 않는다. 막히는 정당한 사용처는 없다.
|
||||
|
||||
## 3. R2/N1 — S9 의 macOS 이식성
|
||||
|
||||
`sys.platform == "linux"` 일 때만 `File too large` 를 단언하고, rc≠0·`prompt-shloop-*` 잔존 없음·`delegate_job_safe: cannot write the prompt file` 은 모든 플랫폼에서 단언한다. 1회차에서 내가 하네스를 직접 돌려 확인한 macOS 동작(rc 1·잔존 없음·원인 문구 출력)과 정확히 일치하는 계약이다.
|
||||
|
||||
## 4. 테스트 (항목 1·3)
|
||||
|
||||
### 4-1. 수정 없이는 실패하는가 — 확인함
|
||||
|
||||
검증 경로 사본에서 `runner.py` 와 래퍼만 `59be1c8` 판으로 되돌리고 `-k "s4c or s1e or s9 or s4 or s1"` 을 돌렸다:
|
||||
|
||||
```
|
||||
2 failed, 6 passed, 1 skipped in 3.11s
|
||||
FAILED test_s4c_a_prompt_file_that_cannot_be_written_is_a_failed_submit ← H1
|
||||
FAILED test_s1e_an_empty_prompt_file_is_refused ← H3
|
||||
```
|
||||
|
||||
S9 는 되돌린 사본에서도 통과한다 — `run_loop.sh` 쪽 수정은 이미 `59be1c8` 에 있었고 이번 델타는 테스트의 단언만 이식 가능하게 바꿨기 때문이다(macOS 에서 통과 = R2 정정 확인). S1/S1c/S1d/S4/S4b 는 양쪽 모두 통과(불변 조건).
|
||||
|
||||
### 4-2. 검증 경로에서의 실행
|
||||
|
||||
```
|
||||
tests/test_issue36_prompt_transport.py tests/test_issue5_phase1_delegation.py tests/test_loop_cli.py
|
||||
tests/test_issue14_delivery_ack.py tests/test_issue25_plan_reuse.py
|
||||
→ 127 passed, 1 skipped in 215.94s failed 0 (skip = linux_only S1b)
|
||||
```
|
||||
|
||||
1회차의 유일한 실패였던 S9 가 이 호스트에서 통과하고, 새 파일 24건(23 통과 + Linux 전용 skip 1)과 인접 계약 스위트가 모두 통과한다.
|
||||
|
||||
### 4-3. 격리
|
||||
|
||||
| 항목 | 결과 |
|
||||
|---|---|
|
||||
| 실제 `.mam/jobs` 전후 | **차이 0** |
|
||||
| `prompt-*` 잔존(실제 `.mam` / 검증 경로 `.mam`) | **0 / 0** |
|
||||
| 검증 경로 tracked 파일 | 변화 없음(HEAD `708262a`), 본 체크아웃 `main 1debc7d` |
|
||||
|
||||
## 5. 병합 차단 여부 (항목 3)
|
||||
|
||||
1회차 PASS 를 뒤집을 요소는 없다. 델타는 러너의 `try` 범위 확장(`runner.py:472-491`), 래퍼의 `-s` 한 줄(`:193`), 테스트 3건(S4c·S1e·S9 조건부 단언)뿐이고, 정정 대상만 정확히 실패→통과로 바뀌었다. 버전 락스텝 bump 부재(`4.7.2`, 오케스트레이터가 4.7.3 으로 처리 예정) 외에 **병합을 막을 결함은 없다**. 1회차 H2(loop SKILL.md 의 `MAM_DIFF_MAX_*` 의미)·H4(`$PROMPT` 후행 개행)는 이 델타 범위 밖이라 그대로 열려 있다.
|
||||
|
||||
| # | 비차단 관찰 | 근거 |
|
||||
|---|---|---|
|
||||
| I1 | `-s` 는 바이트 크기 검사라 개행·공백만 있는 프롬프트 파일은 통과한다. 텍스트 형식(`--prompt ""`)의 거부와 완전히 대칭이려면 `[[ -n "$(tr -d '[:space:]' < "$PROMPT_FILE")" ]]` 류가 필요하지만, 실무에서 그런 입력은 없다 | `multi-agent-mux-delegate-job:193`, 실측 |
|
||||
|
||||
## 6. 교차검증 가이드
|
||||
|
||||
```bash
|
||||
RP=/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20260924T085626Z-b8987e/1
|
||||
PY=/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python
|
||||
git -C "$RP" rev-parse HEAD 'HEAD^{tree}' # 708262a… / d601c2d…
|
||||
# H1 재현 스크립트(1회차 §1 의 것)를 정정된 러너에 → returned None, leftover [], wrapper called 0
|
||||
# 되돌린 사본: runner.py·래퍼를 59be1c8 판으로 → -k "s4c or s1e or s9 or s4 or s1" → 2 failed / 6 passed / 1 skipped
|
||||
(cd "$RP" && AGENT_PYTHON_BIN="$PY" PYTHONDONTWRITEBYTECODE=1 "$PY" -m pytest -p no:cacheprovider -q --basetemp=/tmp/bt tests/test_issue36_prompt_transport.py) # 23 passed, 1 skipped
|
||||
```
|
||||
|
||||
[VERDICT: PASS]
|
||||
@@ -0,0 +1,131 @@
|
||||
# 🔍 Review Report — PR #37 병합 전 독립 리뷰 (job `4ab12ae2`)
|
||||
|
||||
- **Reviewer**: `planner-reviewer-claude-01` (role: planner,reviewer) · 2026-09-24
|
||||
- **대상**: PR #37 `fix/issue-36` **@ `59be1c8`**, base `main 1debc7d` (v4.7.2). 6커밋, 11파일 `+765/−30`, `git diff --check` 클린.
|
||||
- **검증 경로**: `/var/folders/.../T/mam-review/loop-20260924T080014Z-2b3df0/1`. **identity 확인**: `HEAD = 59be1c89…cb49`, `HEAD^{tree} = a009c906…fab7` — 브리프와 일치.
|
||||
- **안전 규칙 준수**: 검증 경로·소스 워크트리(`multi-agent-mux-wt/pr-37`)·본 체크아웃(`main 1debc7d`) 모두 수정·커밋·checkout 없음. 실험은 scratchpad 임시 디렉터리(uuid4)에서만(래퍼는 `--dry-run` + `--agent human` + 도달 불가 브로커, 러너는 in-process 스텁), pytest 는 `AGENT_PYTHON_BIN` + `--basetemp=<검증 경로 밖>`. 실제 herdr·세션·`.mam`·데몬 조작 없음, push·merge·PR 상태 변경 없음.
|
||||
|
||||
## 결론 요약
|
||||
|
||||
**병합을 막을 결함은 없다.** 프롬프트는 러너·래퍼·lib.sh 도우미·옛 셸 러너·create 온보딩 다섯 경로 모두에서 파일로 간다(`grep -rn -- '--prompt '` 의 잔존 호출은 전부 셸 **함수** 인자이거나 문서/기본값이다). 비차단 지적 4건(H1–H4). 그중 **H1 은 재현한 실제 결함**이다: 파이썬 러너는 프롬프트 파일 **쓰기**가 실패하면(ENOSPC 등) 부분 파일을 `.mam` 에 남기고 예외를 그대로 던진다 — PR 이 셸 쪽 두 곳에서 고친 바로 그 부류가 러너에는 빠져 있다. 디스크 고갈 급의 경계 상황이라 차단으로 보지 않았지만, 릴리스 커밋이나 즉시 후속에서 세 줄로 닫을 것을 권한다.
|
||||
|
||||
## 1. 전달 경로의 완전성 (항목 1)
|
||||
|
||||
| 경로 | 이전 | 이제 | 근거 |
|
||||
|---|---|---|---|
|
||||
| 파이썬 러너 `_run_cmd` | `--prompt <text>` argv | `.mam/prompt-<loop_id>-XXXX.md` 에 쓰고 `--prompt-file` | `runner.py:467-502` |
|
||||
| 래퍼 `submit` → `registry.py register` | `--prompt "$PROMPT"` | 파일로 왔으면 `--prompt-file` 그대로 전달, 텍스트로 왔으면 종전대로 | `multi-agent-mux-delegate-job:186-208` |
|
||||
| 래퍼 loop 타입의 회차 `update` | `--prompt "$current_prompt"` | `$job_dir/prompt.md` 에 쓰고 `--prompt-file` | `:405-450` |
|
||||
| `lib.sh` `delegate_submit_job` / `_delegate_submit_job_legacy` (제출 도우미 11곳의 종착) | argv | `_delegate_prompt_file` 로 `.mam/prompt-submit-XXXXXX` 에 쓰고 `--prompt-file`, 성공·실패 모두 `rm -f` | `lib.sh:2038-2046, 2049-2064, 2160-2174` |
|
||||
| `lib_py/delegation/cli.py submit` | `--prompt` 필수 | `--prompt`/`--prompt-file` 상호 배타, register 로 그대로 전달 | `cli.py:29-33, 64-66` |
|
||||
| 옛 셸 러너 `MAM_LOOP_PY=0` | `delegate_job_safe … --prompt "<text>"` (함수 인자) | 함수 안에서 `--prompt` 를 `.mam/prompt-shloop-XXXXXX` + `--prompt-file` 로 바꿔 래퍼 exec | `run_loop.sh:196-221`. `run_loop.sh` 의 잔존 `--prompt "…"` 11곳은 이 **함수** 호출이라 exec 을 거치지 않는다 |
|
||||
| `create_session.sh --submit-job` | 주입문에 과제 본문 포함 | 과제는 `<jobs>/<id>/brief.md` 에 쓰고 pane 에는 **경로만** | `create_session.sh:625-646` |
|
||||
|
||||
### 1-1. 래퍼의 파일 처리 (실측, `--dry-run --agent human`)
|
||||
|
||||
- 상대 `--prompt-file "my prompts/p 1.md"` 는 **호출자 cwd 기준**으로 절대화된다(`:191`, `cd "$WORKDIR"` 이전) — `--workdir` 가 다른 곳이어도 찾는다. 상대 `--registry-dir "reg dir"` 는 종전대로 `--workdir` 기준이다(기존 의미 유지). 공백 경로 모두 안전.
|
||||
- 등록된 `prompt` 는 **바이트 단위로 동일**(`'line1\nline2\n\n'`, 후행 개행 보존) — registry 가 파일을 직접 읽기 때문. 래퍼의 `$PROMPT` 변수(`$(<file)`)는 후행 개행을 잃지만 그것은 브리프 본문에만 쓰인다(H4).
|
||||
- 래퍼는 호출자의 파일을 **지우지 않는다**(소유권은 호출자). `--prompt` 와 `--prompt-file` 동시 → 거부, 없는 파일 → `cannot read --prompt-file <절대경로>` 거부.
|
||||
- 읽는 시점: register 는 래퍼의 첫 단계(`:207`)이고 래퍼는 잡이 끝날 때까지 블록하므로, 러너·lib.sh·셸 러너가 **래퍼 반환 후** 지우는 순서는 안전하다.
|
||||
|
||||
### 1-2. 임시 파일의 위치·권한·정리
|
||||
|
||||
| 생성자 | 위치 | 권한 | 정리 |
|
||||
|---|---|---|---|
|
||||
| 러너 | 제어 루트 `.mam/` (`self.mam_dir`) | `NamedTemporaryFile` → 0600 | `finally: os.unlink` — 성공·실패·**OSError(exec 실패)** 모두 (`:498-502`) |
|
||||
| lib.sh | `${WORKSPACE_ROOT}/.mam/` | `mktemp` → 0600 | 제출 뒤 `rm -f`; **쓰기 실패 시 `rm -f` 후 return 1** (`lib.sh:2042`) |
|
||||
| 셸 러너 | `$MAM_REAL_ROOT/.mam/` | `mktemp` → 0600 | 래퍼 반환 뒤 `rm -f`; 쓰기 실패 시 제거 + 원인 로그 + return 1 (`:201-205`) |
|
||||
| create | `<jobs>/<id>/brief.md` (잡 디렉터리, 보존 대상) | umask | 실패 시 잡 `error` + exit 1 (`:632-637`) |
|
||||
|
||||
### H1 (비차단이지만 실제 결함, 재현) — 러너의 쓰기 실패는 부분 파일을 남기고 예외를 던진다
|
||||
|
||||
`runner.py:472-477` 의 `with NamedTemporaryFile(...) as fh: fh.write(p_text)` 는 `try` **바깥**에 있다. 파일이 만들어진 뒤 `write` 가 실패하면(ENOSPC·EIO) `finally` 의 `unlink` 에 닿지 않는다. 검증 경로의 러너를 in-process 로 띄우고 `write` 가 `OSError(28)` 을 내도록 감싼 결과:
|
||||
|
||||
```
|
||||
RAISED: OSError [Errno 28] No space left on device ← delegate_job 밖으로 그대로 전파(트레이스백, 126 처리 아님)
|
||||
leftover prompt files in .mam: ['prompt-loop-…-2ussg8e6.md'] ← 부분 파일 잔존
|
||||
wrapper called: 0
|
||||
```
|
||||
|
||||
같은 부류를 PR 은 `lib.sh:2042`(`|| { rm -f "$f"; return 1; }`)과 `run_loop.sh:201-205` 에서 고쳤고 S9 로 고정했지만, 러너에는 대응이 없고 테스트(S4/S4b)도 `subprocess.run` 의 OSError 만 다룬다. 브리프 항목 1 의 "쓰기 실패 시 부분 파일이 남지 않는지"에 대해 러너는 **아니오**다.
|
||||
|
||||
→ 권고: `with` 블록을 `try` 안으로 옮기고 `except OSError` 에서 `unlink` 후 `CompletedProcess(cmd, 126, "", "cannot write the prompt file: …")` 를 돌려주면 exec 실패와 같은 "실패 제출" 경로로 합쳐진다(3–4줄). `self.mam_dir.mkdir` 실패도 같은 블록에서 잡힌다.
|
||||
|
||||
## 2. 실패 처리 (항목 2)
|
||||
|
||||
- **exec 실패(OSError → rc 126)**: `_run_cmd` 가 `CompletedProcess(cmd, 126, "", "cannot start the wrapper: … (prompt N bytes, wrapper …)")` 를 돌려주므로 첫 시도·busy 재시도 모두 같은 "실패 제출" 경로로 들어간다. 재시도 실패 시 job id 가 없는 경우에도 이제 `log_error` 를 남긴다(`runner.py:525`, 이전에는 무언). `failed_submit_jobs` 처리는 바뀌지 않았다(id 가 있으면 등록, 없으면 `None` 반환 → 호출자가 `return 1`). S4/S4b 가 E2BIG·EAGAIN 으로 고정.
|
||||
- **재심·중재 미등록 → rc 1**: `:1354-1357`, `:1376-1378`. 판정 잡이 등록조차 안 됐는데 "반론 기각"이나 "중재 없음"으로 흘러가던 것을 실패로 바꾼 것이라 맞다.
|
||||
- **순응(compliance) 잡 `wait_for_job` 실패 → rc 1** (`:1392-1395`): 의도된 강화로 판단한다. 근거는 **정규 수정 잡과의 대칭**이다 — 일반 correction 잡도 `if not correct_jid or not self.wait_for_job(correct_jid): return 1` (`:1323-1326`) 로 끝난다. 순응 잡은 "반론이 기각됐으니 그대로 고쳐라"는 수정 잡의 변형이므로, 시간 초과·에러로 닫힌 잡을 두고 다음 리뷰 라운드로 가면 **바뀌지 않은 트리를 다시 리뷰**하게 된다. 루프가 `wait_for_job` 실패를 통과시키는 자리는 이제 self-review(`:1187`)뿐이고 그것은 판정 리포트로 따로 걸러진다. S3c 가 세 경로(comply/adjudication/arbitration)의 미등록을 고정한다.
|
||||
|
||||
## 3. `MAM_DIFF_MAX_BYTES`/`MAM_DIFF_MAX_LINES` (항목 3)
|
||||
|
||||
- `diff_collect.sh:9-11` 주석이 "증거 크기의 상한이지 전달 상한이 아니다"를 명시하고, 상한값·잘림 마커·`runner.py` 의 l15 규칙(잘린 diff 면 diff-only 원격 리뷰어 거부, 로컬 진행)은 **변경되지 않았다**(runner 의 해당 블록은 diff 밖). S5 가 "옛 사각지대"(131,072 < diff < 200,000 바이트)의 diff 가 통째로 전달됨을 고정한다.
|
||||
- **H2 (비차단, 문서)**: loop SKILL.md 에는 이 두 변수도, 잘림 마커의 의미도 적혀 있지 않다(grep 0건). delegate-job SKILL.md 는 argv 상한만 말한다. "전달은 무제한, 증거는 200 KB/4,000 줄에서 잘리며 잘린 diff 는 diff-only 원격 리뷰어에게 증거가 못 된다"는 한 문단이 loop SKILL.md 에 있어야 항목 3 의 "일관 반영"이 완성된다.
|
||||
|
||||
## 4. `create_session.sh` (항목 4)
|
||||
|
||||
- 잡 등록은 종전대로 lib.sh 의 `delegate_submit_job`(파일 경유)이고, 그 뒤 `<jobs>/<id>/brief.md` 에 과제를 쓰고 pane 에는 `Task: read the complete task in <절대경로> and follow it.` 만 주입한다(`:625-646`). `$pub`(`delegate_publish_cmd` 의 `--expect-dispatch-token`·`--attempt-id`)와 `started` 기반 판정은 **손대지 않았다** — #23 계약 유지. S8 이 주입문에 brief 경로는 있고 과제 본문은 없음, `--expect-dispatch-token`·`--event started` 가 남아 있음을 단언한다.
|
||||
- 경로: `${MAM_JOBS_DIR:-${WORKSPACE_ROOT:-$PWD}/.mam/jobs}` 를 절대화하므로 워크트리 creator(제어 루트 바인딩)도 자기 cwd 와 무관하게 읽을 수 있다. 래퍼가 만드는 `brief.md` 와 같은 자리다.
|
||||
- 실패 시 잡 `error` + `JOB_TERMINALIZED=1` + exit 1 → 기존 트랩이 세션을 되돌린다(초기화 실패와 같은 취급).
|
||||
|
||||
## 5. 새 테스트 22건 (항목 5)
|
||||
|
||||
- **실제 경로를 지나는가**: S1 은 실제 래퍼(`bash multi-agent-mux-delegate-job submit --dry-run`)와 실제 `registry.py` 로 140,000 바이트를 등록해 **바이트 단위 동일**을 단언한다. S2/S3/S3b/S3c 는 `python -m lib_py.loop` 를 하위 프로세스로 띄우고 가짜 래퍼가 받은 인자를 검사한다. S7/S7b/S9 는 `run_loop.sh`(MAM_LOOP_PY=0)를 실제로 돈다. S8 은 mock herdr 위에서 `create_session.sh` 를 실제로 돈다. mock 으로 우회하는 것은 S4/S4b(`subprocess.run` 을 OSError 로 대체 — 커널 대신 코드 경로를 검증하는 것이 목적)뿐이다.
|
||||
- **macOS**: `MAX_ARG_STRLEN` 이 없어 단일 인자 150 KB 가 통과하므로 S1b(음성 대조군)는 `linux_only` 로 **skip** 되고, S8 의 `strace` 검사는 Linux 에서만 붙는다. 즉 이 호스트에서 스위트는 **커널 상한이 아니라 코드 경로**(파일로 전달됐는가)를 검증한다 — 브리프의 우려 그대로이며, 설계상 옳다. 실제 상한 재현은 Linux CI 몫.
|
||||
- **격리**: `_env()` 가 `HOME`·`WORKSPACE_ROOT`·`DELEGATE_JOB_LOGS_DIR` 를 `tmp_path` 로, 브로커를 `127.0.0.1:9` 로 고정하고 `MAM_JOBS_DIR`/`MAM_REAL_ROOT` 를 제거한다. 실제 `.mam`·홈 접촉 없음(§6 실측).
|
||||
- **수정 없이는 실패하는가 — 확인함**: 검증 경로 사본에서 PR 이 바꾼 소스 8파일을 `1debc7d` 판으로 되돌리고 새 파일을 돌린 결과 **18 failed / 3 passed / 1 skipped(S1b)**. 통과 3건은 정정 전에도 성립하던 것(래퍼 실패 → rc 1 등)이다.
|
||||
|
||||
## 6. 검증 경로에서의 테스트 실행 (항목 5·6)
|
||||
|
||||
```
|
||||
tests/test_issue36_prompt_transport.py tests/test_issue5_phase1_delegation.py tests/test_issue14_delivery_ack.py
|
||||
tests/test_loop_cli.py tests/test_issue21_loop_b.py tests/test_issue21_acceptance.py tests/test_issue23_24_startup_path.py
|
||||
tests/test_issue25_plan_reuse.py
|
||||
→ 1 failed, 237 passed, 1 skipped in 422.71s (0:07:02)
|
||||
```
|
||||
|
||||
- **skip 1**: S1b(`linux_only`) — 설계된 것(§5).
|
||||
- **failed 1: S9 — 코드 결함이 아니라 테스트의 macOS 이식성 문제(H5).** S9 는 `ulimit -f 1` 로 쓰기를 실제로 실패시킨 뒤 세 가지를 단언한다: rc≠0, stderr 에 `File too large`, 부분 파일 없음 + 원인 문구. 같은 하네스를 scratchpad 에서 직접 돌린 결과 **rc=1, 부분 파일 없음, `delegate_job_safe: cannot write the prompt file` 출력** — 즉 PR 이 고친 동작(제거 + 원인 표시)은 macOS 에서도 성립한다. 실패한 것은 두 번째 단언뿐이다: macOS 의 `/bin/bash` 3.2 는 `printf` 의 쓰기 실패에 대해 `printf: write error: File too large` 문자열을 내지 않는다(Linux bash 5 의 형식). `strerror(EFBIG)` 자체는 macOS 에서도 "File too large" 이므로 순수하게 bash 버전 차이다. S1b 처럼 `linux_only` 로 두거나, 그 단언을 빼고 원인 문구 단언만 남기면 된다.
|
||||
- 기존 계약 스위트(issue5 phase1 의 registry CLI 스냅샷·help 캡처, issue14, loop_cli, issue21 loop B·acceptance, issue23/24 S7*, issue25)는 **전부 통과** — `--prompt` 하위 호환과 온보딩 계약이 유지된다(항목 4·6).
|
||||
|
||||
### 격리·위생 실측
|
||||
|
||||
| 항목 | 결과 |
|
||||
|---|---|
|
||||
| 실제 `.mam/jobs` / `.mam/delegate_job_logs` 전후 | **차이 0 / 차이 0** |
|
||||
| 실제 `.mam` 의 `prompt-*` 잔존 | **0** |
|
||||
| 검증 경로 `.mam` 의 `prompt-*` 잔존 | **0** (러너·셸 러너·lib.sh 의 정리가 실제 실행에서도 성립) |
|
||||
| 검증 경로 tracked 파일 | 변화 없음(HEAD `59be1c8`), 본 체크아웃 `main 1debc7d` |
|
||||
| 실행 시간 | 새 파일 22건은 34 s 안팎(되돌린 사본 기준 34.5 s); 전체 7 분은 issue14/18 계열의 벽시계 시험 몫 |
|
||||
|
||||
## 7. 문서 (항목 7)
|
||||
|
||||
- delegate-job SKILL.md `:44-48`: `--prompt-file` 의 의미와 argv 상한, "루프는 항상 파일을 쓴다" 명시 ✓. 래퍼 usage(`:112-121`)도 갱신 ✓.
|
||||
- loop SKILL.md `:267`: 파이프라인에서의 종료 코드(`pipefail`/`PIPESTATUS`) 안내 ✓. 그러나 `MAM_DIFF_MAX_*` 의 새 의미는 없다(H2).
|
||||
- `.claude/skills`: 본 체크아웃의 것은 git 에 추적되지 않는 로컬 심링크(`../../.agents/skills/*`)라 자동 동기화된다 — 배포 사본 정합 문제 없음.
|
||||
|
||||
## 8. 병합 차단 여부 (항목 8)
|
||||
|
||||
버전 락스텝 bump 부재(`lib.sh:32` `4.7.2`, 오케스트레이터가 4.7.3 으로 처리 예정) 외에 **병합을 막을 결함은 없다**. 다만 H1 은 릴리스 커밋에 함께 넣거나 즉시 후속으로 닫기를 권한다 — 셸 쪽과 같은 부류이고 수정이 세 줄이다.
|
||||
|
||||
| # | 지적 | 성격 | 근거 |
|
||||
|---|---|---|---|
|
||||
| H1 | 러너의 프롬프트 파일 **쓰기** 실패 시 부분 파일 잔존 + 예외 전파(126 실패 제출로 합류하지 않음) — 재현 | 비차단(경계 상황), **후속 권고 1순위** | `runner.py:472-477` vs `:490-502`; §1 H1 |
|
||||
| H2 | `MAM_DIFF_MAX_*` 의 "증거 상한, 전달 무제한" 의미가 loop SKILL.md 에 없음 | 문서 | `diff_collect.sh:9-11` 에만 있음 |
|
||||
| H3 | 래퍼는 **빈** `--prompt-file` 을 받아들여 빈 프롬프트를 등록한다(실측) | 사소 | `multi-agent-mux-delegate-job:195` 의 `-n "$PROMPT" \|\| -n "$PROMPT_FILE"` |
|
||||
| H4 | 래퍼의 `$PROMPT="$(<file)"` 는 후행 개행을 잃는다 — registry 의 프롬프트(파일 그대로)와 브리프 본문이 그 점에서 다를 수 있음 | 사소 | `:193` |
|
||||
| H5 | S9 의 `File too large` 단언이 macOS bash 3.2 에서 실패(문자열 형식 차이); 실질 단언 2개는 통과 | 테스트 이식성 | §6 |
|
||||
|
||||
## 9. 교차검증 가이드
|
||||
|
||||
```bash
|
||||
RP=/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20260924T080014Z-2b3df0/1
|
||||
PY=/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python
|
||||
git -C "$RP" rev-parse HEAD 'HEAD^{tree}' # 59be1c8… / a009c90…
|
||||
grep -rn -- '--prompt ' "$RP/.agents/skills" | grep -v '\.md:' # 래퍼 usage/기본값, cli 주석, run_loop.sh 의 함수 호출뿐
|
||||
# H1 재현: 러너 in-process, NamedTemporaryFile.write 가 OSError(28) → .mam/prompt-* 잔존 + 예외 전파 (§1 H1 의 스크립트)
|
||||
# S9 하네스 직접 실행(macOS): rc=1, 부분 파일 없음, "cannot write the prompt file" 출력, 단 "File too large" 문자열 없음
|
||||
# 되돌린 사본: 소스 8파일을 1debc7d 판으로 → tests/test_issue36_prompt_transport.py → 18 failed / 3 passed / 1 skipped
|
||||
```
|
||||
|
||||
[VERDICT: PASS]
|
||||
Reference in new issue
Block a user