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]
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
# PR #37 독립 리뷰
|
||||||
|
|
||||||
|
- Job: `2e264c0c`; reviewer: `planner-reviewer-codex-01`
|
||||||
|
- Loop: `loop-20260924T080014Z-2b3df0`
|
||||||
|
- Base: `1debc7d92444385b9200e821efa0ff0f6d82023e`
|
||||||
|
- Head: `59be1c89209a232059aec0c5aeecc2329929cb49`
|
||||||
|
- Tree: `a009c9062d9818f343ac57e6c72cda932887fab7`
|
||||||
|
- 검증 경로: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20260924T080014Z-2b3df0/1`
|
||||||
|
|
||||||
|
파일 전달의 정상 경로와 기존 계약은 검증됐지만, Python 러너의 파일 쓰기 실패 정리 누락과 새 테스트의 macOS 실패가 남아 있다. 아래 두 항목을 수정한 뒤 재검토가 필요하다. 계획된 4.7.3 버전 변경과 별개의 지적이다.
|
||||||
|
|
||||||
|
## R1 — P2: Python 러너의 임시 파일 쓰기 실패가 정리 범위 밖에 있음
|
||||||
|
|
||||||
|
근거: `.agents/skills/lib_py/loop/runner.py:472`–`:477`, `:490`–`:502`.
|
||||||
|
|
||||||
|
`NamedTemporaryFile(delete=False)` 생성과 `fh.write()` 및 context manager 종료 시 flush/close가 `try/except/finally`보다 먼저 실행된다. 따라서 쓰기 또는 flush가 실패하면 `OSError`가 `delegate_job()` 밖으로 전파되고, 뒤의 `os.unlink(prompt_file)`에 도달하지 않는다. 특히 경로 변수도 쓰기 다음에 지정되어 있어 단순히 try 위치만 바꾸는 것으로는 충분하지 않다. 새 S9는 셸 러너의 부분 파일 정리만 검사하므로 이 Python 경로를 검출하지 못한다.
|
||||||
|
|
||||||
|
재현 절차:
|
||||||
|
|
||||||
|
1. 독립 Python subprocess에서 `WORKSPACE_ROOT`, `HOME`, 로그 경로를 새 `/tmp` 샌드박스로 설정하고, 검증 트리의 `LoopRunner(['--creator', 'c', '--task', 't'])`를 생성한다.
|
||||||
|
2. 그 subprocess에서만 `signal.signal(signal.SIGXFSZ, signal.SIG_IGN)`과 `resource.setrlimit(resource.RLIMIT_FSIZE, (1024, 1024))`를 적용한다.
|
||||||
|
3. `runner.delegate_job('c', 'claude', 'Worker', 'x' * 20000)`를 호출하고 예외와 `.mam/prompt-*`를 확인한다. 실제 wrapper/에이전트가 실행되기 전에 파일 쓰기에서 실패한다.
|
||||||
|
|
||||||
|
기대: 원인을 기록하는 정상적인 제출 실패 반환과 임시 파일 정리.
|
||||||
|
|
||||||
|
실제: `[Errno 27] File too large`가 처리되지 않은 `OSError`로 전파됐고, `.mam/prompt-loop-…md`가 **1,024바이트, 권한 0600**으로 남았다. 실제 `LoopRunner.delegate_job()`으로 재현했으며, 오류를 흉내 내는 subprocess mock은 사용하지 않았다. 생성/쓰기 실패를 처리해야 한다는 이번 브리프의 요구를 충족하지 못한다.
|
||||||
|
|
||||||
|
수정 방향: 임시 파일 경로를 미리 초기화하고, 디렉터리 생성·파일 생성·쓰기·close·wrapper 실행을 정리 가능한 하나의 try/finally 범위에 둔다. 생성된 파일의 이름은 `write()` 전에 저장한다. 파일 준비 실패도 원인이 드러나는 실패 제출 결과로 돌려주고, 첫 시도와 busy 재시도에서 같은 규칙을 적용한다. 메시지는 wrapper 실행 실패와 파일 준비 실패를 구분하는 것이 정확하다.
|
||||||
|
|
||||||
|
대안 검증: 검증 트리에서 추출한 동일 `_run_cmd`를 임시 harness에서만 변경해, 위 작업들을 기존 예외/정리 범위 안으로 옮기고 경로를 쓰기 전에 저장했다. 같은 실제 파일 크기 제한에서 원본은 예외와 1,024바이트 파일을 남겼지만, 후보는 **rc 126 실패 결과와 빈 leftovers 목록**을 반환했다. 제품 코드는 수정하지 않았다. 이 상황을 Python 러너의 회귀 테스트에도 추가해야 한다.
|
||||||
|
|
||||||
|
## R2 — P2: 새 S9가 macOS에서 플랫폼별 오류 문구 때문에 실패함
|
||||||
|
|
||||||
|
근거: `tests/test_issue36_prompt_transport.py:602`.
|
||||||
|
|
||||||
|
재현: 브리프의 인터프리터와 격리 옵션으로 `tests/test_issue36_prompt_transport.py::test_s9_shell_loop_removes_a_partial_prompt_file_and_says_why`를 실행한다.
|
||||||
|
|
||||||
|
기대: 실제 `RLIMIT_FSIZE` 쓰기 실패, 비정상 종료, 부분 파일 제거, 프로젝트가 출력하는 원인 메시지를 검증하며 지원 플랫폼에서 통과해야 한다.
|
||||||
|
|
||||||
|
실제: 전체 선택 실행과 S9 단독 재실행 모두 `assert "File too large" in result.stderr`에서 실패했다. 이 macOS 실행 환경의 bash stderr에는 그 문구가 없었다. 프로젝트 자체의 `delegate_job_safe: cannot write the prompt file ...` 메시지는 출력됐다.
|
||||||
|
|
||||||
|
수정 방향 및 검증: 플랫폼별 bash/OS 오류 문자열을 필수 계약으로 삼지 말고, 비정상 반환 코드·부분 파일 없음·프로젝트의 고유 오류 메시지를 유지해서 검사한다. 임시 namespace에서 해당 영어 문자열 assertion 한 줄만 제외한 동일 S9를 새 샌드박스에 실행했으며, 나머지 **실제 쓰기 실패와 정리/진단 assertion은 모두 통과**했다. 테스트 전체를 skip하거나 정리 assertion을 제거할 필요는 없다.
|
||||||
|
|
||||||
|
## 나머지 검토 결과
|
||||||
|
|
||||||
|
- 전달 경로: `--prompt` 잔여 호출자를 직접 검색했다. 옛 셸 러너의 11개 내부 호출은 shell 함수 `delegate_job_safe`로 들어가며, 이 함수가 exec 전에 파일로 변환한다. Python 러너, lib.sh의 Python/legacy 제출 도우미, delegation CLI, wrapper의 registry register/update 경로는 파일 전달을 사용한다. 명시적 `--prompt` CLI 호환 경로는 남아 있다.
|
||||||
|
- 경로/크기: 실제 이 호스트의 `SC_ARG_MAX`보다 큰 **1,114,112바이트** 프롬프트를 argv로 전달하면 `OSError(errno=7)`가 발생했다. 같은 내용은 상대경로의 공백 포함 파일명, 다른 `--workdir`, 공백 포함 registry 경로 조합으로 실제 wrapper/registry를 거쳐 원문 그대로 등록됐다. Linux 단일 인자 131,072바이트 상한을 macOS에서 재현했다고 주장하는 것이 아니다.
|
||||||
|
- 파일 읽기 시점: wrapper가 registry를 동기 실행하고 registry가 그 호출 중 파일 내용을 읽으므로, 호출자 쪽 정상 반환 후 임시 파일 삭제와 충돌하지 않는다. 정상 완료/exec OSError의 정리 테스트는 통과했다. R1은 그보다 앞선 쓰기 실패에 한정된다.
|
||||||
|
- 실패 처리: exec 실패와 busy 재시도의 rc 126 처리, 재심/중재/순응 미등록 시 비정상 종료 테스트가 통과했다. 별도 실제 루프 subprocess에서 fake agent가 순응 잡을 등록한 뒤 `error` 상태로 종료하도록 했을 때도 rc 1로 끝났고 다음 리뷰 라운드/성공 승격으로 진행하지 않았다. 구현·일반 수정 잡의 실패를 중단하는 기존 정책과 일관된 강화다. 다만 이때도 “Failed to register Creator compliance job”이라고 출력하는 문구는 등록 실패와 실행 실패를 구분하지 못한다(비차단 진단 개선 사항).
|
||||||
|
- diff 제한: 수집기의 바이트/줄 제한값은 바뀌지 않았고 증거 절단 표시도 유지된다. L15는 잘린 증거를 받는 diff-only 원격 리뷰어를 거부하고 로컬 리뷰는 허용하는 기존 규칙을 검증했다.
|
||||||
|
- create 온보딩: S7 계열과 새 S8이 통과했다. brief는 job registry 아래 절대경로로 만들어지고, 주입문은 과제 본문 대신 그 경로와 기존 dispatch-token/started 명령을 포함한다. 임시 제출 파일과 에이전트가 이후 읽을 영속 brief는 구분된다.
|
||||||
|
- 하위 호환: registry CLI 계약 스냅샷, shim help 캡처, loop CLI, worktree 문맥, Issue #14 acknowledgement 선택 테스트가 통과했다. `--prompt`는 계속 사용할 수 있다. 대표 역검증으로 샌드박스의 wrapper만 base 버전으로 교체하면 새 S1은 `unknown option: --prompt-file`로 실패했다. PR이 언급한 13건 전체의 과거 버전 실패 수를 재측정한 것은 아니다.
|
||||||
|
- 문서: delegate-job skill은 파일 옵션과 Linux argv 제한을 설명하고, loop skill은 위임 실패의 비정상 종료 및 pipefail 사용을 설명한다. 검증 트리에 `.claude/skills` 배포 사본은 없었다. 4.7.3 락스텝 bump는 브리프대로 오케스트레이터 후속 작업이다.
|
||||||
|
|
||||||
|
## 실행 증거
|
||||||
|
|
||||||
|
모든 pytest 실행은 검증 경로에서 브리프의 절대 Python 경로를 `AGENT_PYTHON_BIN`과 실행 명령에 함께 지정하고, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, 검증 경로 밖의 고유 `--basetemp`를 사용했다.
|
||||||
|
|
||||||
|
1. `test_issue36_prompt_transport.py`, `test_issue5_phase1_delegation.py`, `test_loop_cli.py`: **95 passed, 1 failed, 1 skipped — 91.15초**. 실패는 R2의 S9이며, skip은 Linux 전용 MAX_ARG_STRLEN 음성 대조군이다.
|
||||||
|
2. `test_issue23_24_startup_path.py`, `test_issue21_loop_b.py`, `test_issue21_worktree_prereqs.py`, `test_issue14_delivery_ack.py`에서 `s7 or l15 or t2_5 or wrapper_honours_registry_ack` 선택: **19 passed, 132 deselected — 63.39초**.
|
||||||
|
3. S9 단독 확인: 동일 문구 assertion에서 실패. 위 결과에 중복 합산하지 않았다.
|
||||||
|
4. `git diff --check`, 변경 셸 파일 5개의 개별 `bash -n`, 변경 Python 파일 4개의 AST 구문 검사 통과.
|
||||||
|
|
||||||
|
HEAD/tree가 브리프와 일치했고 검증 후 git 작업 트리도 깨끗했다. 제품 코드, creator 워크트리, 실제 herdr 서버/에이전트 세션을 수정하지 않았다. 제안 검증과 역검증은 임시 사본/독립 subprocess에 한정했다. 두 지적은 국소 수정으로 해결 가능하므로 재설계 에스컬레이션은 필요하지 않다.
|
||||||
|
|
||||||
|
[VERDICT: NOT PASS]
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"loop_id": "loop-20260924T085626Z-b8987e",
|
||||||
|
"base_sha": "59be1c89209a232059aec0c5aeecc2329929cb49",
|
||||||
|
"head_sha": "708262a598c4301c0ccc70efba5401093b4b1e30",
|
||||||
|
"tree_sha": "d601c2d01bb4c09931e5e1989e140b01d51f5b96",
|
||||||
|
"review_commit": "708262a598c4301c0ccc70efba5401093b4b1e30",
|
||||||
|
"reviewer": "planner-reviewer-codex-01",
|
||||||
|
"job_id": "d493b472",
|
||||||
|
"verdict": "PASS",
|
||||||
|
"transport": "herdr",
|
||||||
|
"evidence": "review-tree",
|
||||||
|
"isolation": "worktree",
|
||||||
|
"verified_at": "2026-09-24T09:09:43Z"
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# PR #37 델타 리뷰 2회차
|
||||||
|
|
||||||
|
- Job: `d493b472`; reviewer: `planner-reviewer-codex-01`
|
||||||
|
- Loop: `loop-20260924T085626Z-b8987e`
|
||||||
|
- Base: `59be1c89209a232059aec0c5aeecc2329929cb49`
|
||||||
|
- Head: `708262a598c4301c0ccc70efba5401093b4b1e30`
|
||||||
|
- Tree: `d601c2d01bb4c09931e5e1989e140b01d51f5b96`
|
||||||
|
- 검증 경로: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20260924T085626Z-b8987e/1`
|
||||||
|
|
||||||
|
직전 리뷰의 R1/R2가 해결됐고, 추가된 빈 파일 거부도 기존 wrapper 계약과 일치한다. 이번 델타에서 병합 차단 결함을 발견하지 않았다. 브리프에 명시된 4.7.3 락스텝 버전 변경은 오케스트레이터 후속 작업이다.
|
||||||
|
|
||||||
|
## R1/H1 — 파일 쓰기 실패 처리 및 정리: 해소
|
||||||
|
|
||||||
|
근거: `.agents/skills/lib_py/loop/runner.py:472`–`:491`, `:504`–`:516`.
|
||||||
|
|
||||||
|
경로를 빈 문자열로 초기화하고, 디렉터리 생성·임시 파일 생성·쓰기·context manager 종료를 새 try 범위에 포함했다. 파일 이름을 쓰기 전에 저장하므로 쓰기 또는 닫기 중 오류에서도 부분 파일을 지울 수 있다. 오류 경로는 원인이 포함된 rc 126의 `CompletedProcess`를 반환하며, 뒤의 wrapper 실행 try/finally에 들어가지 않는다. 정상 경로에서는 기존 finally가 파일을 지운다. 따라서 두 정리 경로가 같은 파일에 중복 적용되지 않는다.
|
||||||
|
|
||||||
|
실제 `RLIMIT_FSIZE=1024`와 `SIGXFSZ` 무시를 독립 subprocess에 적용해 재검증했다. 제품 코드에서 추출한 동일 `_run_cmd`에 실행/삭제 횟수 관찰만 추가한 결과:
|
||||||
|
|
||||||
|
| 조건 | 반환 코드 | wrapper 실행 시도 | 파일 삭제 호출 | 잔여 파일 |
|
||||||
|
|---|---:|---:|---:|---|
|
||||||
|
| 20,000바이트 쓰기 중 실제 EFBIG | 126 | 0 | 1 | 없음 |
|
||||||
|
| 2,000바이트 버퍼의 close/flush 시 실제 EFBIG | 126 | 0 | 1 | 없음 |
|
||||||
|
| 정상 실행 | 0 | 1 | 1 | 없음 |
|
||||||
|
| exec 실패(PATH에서 bash 제거) | 126 | 1 | 1 | 없음 |
|
||||||
|
| `.mam`이 디렉터리가 아닌 파일 | 126 | 0 | 0 | 없음 |
|
||||||
|
|
||||||
|
추출 함수뿐 아니라 실제 `LoopRunner.delegate_job()`도 별도 subprocess에서 같은 20,000바이트 쓰기 실패로 실행했다. 결과는 `None`, `delegate_job submit failed (exit 126)` 및 파일 쓰기 원인 로그, 잔여 파일 없음이었다.
|
||||||
|
|
||||||
|
busy 재시도 경로는 첫 wrapper 호출만 rc 5로 모사하고 그때 실제 파일 크기 제한을 설정했다. 두 번째 시도의 파일 쓰기가 EFBIG로 실패하자 `retry failed (exit 126)`이 기록됐고, wrapper는 한 번만 호출됐으며 결과는 `None`, 잔여 파일은 없었다. 미등록 상태에서 가짜 job ID나 `failed_submit_jobs` 항목을 만들지 않고 기존 실패 제출 흐름으로 돌아간다.
|
||||||
|
|
||||||
|
역검증: S4c에만 base의 `delegate_job` 메서드를 임시 namespace로 적용했다. 새 회귀는 처리되지 않은 ENOSPC로 실패했고 파일 한 개가 남았다. 수정된 HEAD의 S4c는 통과했다. 기존 R1 지적을 해소한다.
|
||||||
|
|
||||||
|
## R2/N1 — macOS S9 오류 문구: 해소
|
||||||
|
|
||||||
|
근거: `tests/test_issue36_prompt_transport.py:637` 이후.
|
||||||
|
|
||||||
|
libc/bash의 `File too large` 문구는 Linux에서만 요구한다. 비정상 반환 코드, 잔여 파일 없음, 프로젝트 고유의 `delegate_job_safe: cannot write the prompt file` 진단은 모든 플랫폼에서 여전히 검사한다. 현재 macOS에서 S9가 통과했다. 쓰기 실패와 정리의 검증 의도를 유지한 이식성 수정이다.
|
||||||
|
|
||||||
|
## H3 — 빈 프롬프트 파일 거부: 적절
|
||||||
|
|
||||||
|
근거: `.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job:193`.
|
||||||
|
|
||||||
|
읽을 수 있는 일반 파일 확인 다음에 `-s` 검사를 추가하므로, 0바이트 입력은 registry 등록 전에 거부된다. 기존 wrapper의 `--prompt ''`도 허용하지 않았으므로 유효한 기존 wrapper 사용처를 차단하는 변경은 아니다. registry 자체의 register/update 구현은 이번 델타에서 바뀌지 않았다. loop 형의 내부 `prompt.md`는 registry update에 직접 전달되므로 이 입구 검사와 혼동되지 않는다. 공백/개행으로만 된 비어 있지 않은 파일을 거부하는 의미로 확대된 변경도 아니다.
|
||||||
|
|
||||||
|
S1e가 통과했다. 샌드박스 skills 사본의 wrapper만 base로 교체하고 같은 S1e를 호출하면 assertion이 실패하며 빈 프롬프트 잡 한 개가 등록됐다. 새 검사가 기존 결함을 검출한다는 역검증이다.
|
||||||
|
|
||||||
|
## 검증 및 범위
|
||||||
|
|
||||||
|
- `git diff 59be1c8...HEAD`의 변경 파일 3개를 검토했다. HEAD/tree가 브리프와 일치했고 검증 후 작업 트리도 깨끗했다.
|
||||||
|
- `tests/test_issue36_prompt_transport.py` 전체: **23 passed, 1 skipped in 42.24s**. skip은 Linux 전용 MAX_ARG_STRLEN 음성 대조군이다. 기존 파일 전달, exec 오류/재시도, 셸 루프, create 온보딩 테스트도 이 실행에 포함된다.
|
||||||
|
- pytest에는 브리프의 절대 Python 경로를 실행 명령과 `AGENT_PYTHON_BIN`에 지정하고 `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, 검증 경로 밖의 고유 `/tmp/mam-review-d493b472.*` basetemp를 사용했다.
|
||||||
|
- `git diff --check`, 변경 wrapper의 `bash -n`, 변경 Python 파일 2개의 AST 구문 검사 통과.
|
||||||
|
- 위 실패 주입, 역검증, 실행/삭제 횟수 검사는 별도 임시 경로와 독립 subprocess에서 수행했다. 제품 코드, creator 워크트리, 실제 herdr 서버/에이전트 세션은 수정하지 않았다.
|
||||||
|
- 직전 리뷰의 넓은 계약 테스트 전체를 이번 트리에서 재실행했다고 주장하지 않는다. 이번 검증은 정정된 세 항목과 프롬프트 전달 회귀에 집중했다. 이전 판정을 유지할 추가 차단 사유를 발견하지 못했으며 재설계 에스컬레이션은 필요하지 않다.
|
||||||
|
|
||||||
|
[VERDICT: PASS]
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"loop_id": "loop-20260924T085626Z-b8987e",
|
||||||
|
"base_sha": "59be1c89209a232059aec0c5aeecc2329929cb49",
|
||||||
|
"head_sha": "708262a598c4301c0ccc70efba5401093b4b1e30",
|
||||||
|
"tree_sha": "d601c2d01bb4c09931e5e1989e140b01d51f5b96",
|
||||||
|
"review_commit": "708262a598c4301c0ccc70efba5401093b4b1e30",
|
||||||
|
"reviewer": "reviewer-agy-01",
|
||||||
|
"job_id": "3121d389",
|
||||||
|
"verdict": "PASS",
|
||||||
|
"transport": "herdr",
|
||||||
|
"evidence": "review-tree",
|
||||||
|
"isolation": "worktree",
|
||||||
|
"verified_at": "2026-09-24T09:09:43Z"
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# Pre-Merge Independent Delta Review Report: PR #37 (`fix/issue-36` Round 2)
|
||||||
|
|
||||||
|
- **Job ID**: `3121d389`
|
||||||
|
- **Target Agent**: `agy` (Session: `herdr:reviewer-agy-01`)
|
||||||
|
- **Role**: Reviewer
|
||||||
|
- **Target Worktree Context**:
|
||||||
|
- **Loop ID**: `loop-20260924T085626Z-b8987e`
|
||||||
|
- **Base Commit**: `59be1c89209a232059aec0c5aeecc2329929cb49` (Round 1 target)
|
||||||
|
- **Head Commit**: `708262a598c4301c0ccc70efba5401093b4b1e30` (1 delta commit: +68 / −7)
|
||||||
|
- **Tree SHA**: `d601c2d01bb4c09931e5e1989e140b01d51f5b96`
|
||||||
|
- **Verification Path**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20260924T085626Z-b8987e/1`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Executive Summary
|
||||||
|
|
||||||
|
This second review round examines delta commit `708262a` ("fix(issue-36): merge review - handle prompt write failure in runner; reject empty prompt file; make test_s9 stderr check portable").
|
||||||
|
|
||||||
|
The commit directly resolves all three findings identified during Round 1:
|
||||||
|
1. **R1 / H1 (Prompt Write Failure Handling in `runner.py`)**: `_run_cmd` now wraps temporary prompt file creation and writing in `try ... except OSError:`. If writing fails (e.g. `ENOSPC`, `EFBIG`), any partial prompt file is unlinked and a `CompletedProcess(..., 126, "", msg)` is returned. The wrapper is not invoked, no unhandled exceptions bubble to `LoopRunner.run()`, and the failure is logged cleanly. Verified by new test `test_s4c`.
|
||||||
|
2. **R2 / N1 (macOS Portability in `test_s9`)**: In `tests/test_issue36_prompt_transport.py`, line 637 guards the assertion `assert "File too large" in result.stderr` with `if sys.platform == "linux":`. This accommodates macOS `/bin/bash` 3.2.57 behavior while continuing to assert `result.returncode != 0`, clean file removal (`assert not leftovers`), and the portable MAM diagnostic (`assert "delegate_job_safe: cannot write the prompt file" in result.stderr`) across all platforms.
|
||||||
|
3. **H3 (Empty Prompt File Rejection)**: `multi-agent-mux-delegate-job` now asserts `[[ -s "$PROMPT_FILE" ]]` in `cmd_submit()`, rejecting empty files before registration. Verified by new test `test_s1e`.
|
||||||
|
|
||||||
|
All 23 applicable tests in `tests/test_issue36_prompt_transport.py` passed cleanly in the macOS verification environment (with 1 platform-specific test skipped on Darwin as intended).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Detailed Verification Against Checklist Items
|
||||||
|
|
||||||
|
### (1) Verification of the Three Delta Fixes & Side-Effect Analysis
|
||||||
|
- **Prompt File Creation & Cleanup in `runner.py` (R1/H1)**:
|
||||||
|
- `prompt_file` is initialized to `""`.
|
||||||
|
- In `_run_cmd`, `tempfile.NamedTemporaryFile` and `fh.write(p_text)` are wrapped in `try ... except OSError as e:`.
|
||||||
|
- If writing fails:
|
||||||
|
* If `prompt_file` exists, it is unlinked immediately in the `except` block.
|
||||||
|
* Returns `subprocess.CompletedProcess(["bash", str(wrapper_script), "submit"], 126, "", msg)`.
|
||||||
|
* Because it returns directly from the `except` block, the downstream `subprocess.run(cmd)` and the outer `finally:` block do not execute. The wrapper is never invoked with an incomplete file.
|
||||||
|
- On the normal path:
|
||||||
|
* The wrapper executes, and the outer `finally: os.unlink(prompt_file)` ensures the file is deleted exactly once.
|
||||||
|
- The returned exit code 126 triggers the existing retry mechanism in `delegate_job()`; if the write still fails, the error message is logged cleanly and `delegate_job()` returns `None`, terminating the loop with exit code 1 without tracebacks.
|
||||||
|
- Verified by `test_s4c_a_prompt_file_that_cannot_be_written_is_a_failed_submit`.
|
||||||
|
- **macOS Test Portability for `test_s9` (R2/N1)**:
|
||||||
|
- Scoping `assert "File too large" in result.stderr` to `sys.platform == "linux"` eliminates the macOS `AssertionError` that caused the Round 1 `NOT PASS`.
|
||||||
|
- Portable assertions across Darwin and Linux verify:
|
||||||
|
* `assert result.returncode != 0` (non-zero exit upon write failure).
|
||||||
|
* `assert not leftovers` (partial `.mam/prompt-shloop-*` files are deleted).
|
||||||
|
* `assert "delegate_job_safe: cannot write the prompt file" in result.stderr` (MAM error diagnostics printed to stderr).
|
||||||
|
- Verified by running `tests/test_issue36_prompt_transport.py::test_s9_shell_loop_removes_a_partial_prompt_file_and_says_why` on macOS (PASS).
|
||||||
|
- **Empty Prompt File Rejection (H3)**:
|
||||||
|
- `cmd_submit()` in `multi-agent-mux-delegate-job` checks `[[ -s "$PROMPT_FILE" ]] || { echo "submit: --prompt-file $PROMPT_FILE is empty" >&2; exit 1; }`.
|
||||||
|
- Rejects zero-byte prompt files fail-closed before invoking `registry.py`.
|
||||||
|
- Verified by `test_s1e_an_empty_prompt_file_is_refused`.
|
||||||
|
|
||||||
|
### (2) Impact of Empty File Rejection on Valid Workflows
|
||||||
|
- In MAM, all valid delegated prompts (task instructions, review requests, refinement briefs, compliance orders) require meaningful textual context.
|
||||||
|
- Loop brief provisioning (`prompt.md`) always writes non-empty markdown briefs containing metadata and task descriptions.
|
||||||
|
- Enforcing `-s` prevents unintended empty job dispatches without affecting legitimate system workflows.
|
||||||
|
|
||||||
|
### (3) Overall Merge Readiness
|
||||||
|
- Commit `708262a` cleanly addresses all review concerns with minimal, surgical changes (+68 / −7).
|
||||||
|
- `git diff 59be1c8...HEAD --check` reported 0 whitespace or formatting errors.
|
||||||
|
- Version bump to `v4.7.3` is scheduled for the orchestrator release commit.
|
||||||
|
- Zero blockers remaining.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Test Execution Summary
|
||||||
|
|
||||||
|
| Test Suite / Target | Result | Duration | Notes |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| `tests/test_issue36_prompt_transport.py` | **23 PASS, 1 SKIP** | 42.4s | 23 passed on macOS; `test_s1b` skipped (`linux_only` constant test); `test_s9`, `test_s4c`, `test_s1e` all pass |
|
||||||
|
| `git diff 59be1c8...HEAD --check` | **0 errors** | 0.05s | Clean formatting and no whitespace errors |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[VERDICT: PASS]
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
# Pre-Merge Independent Review Report: PR #37 (`fix/issue-36`)
|
||||||
|
|
||||||
|
- **Job ID**: `db46a0da`
|
||||||
|
- **Target Agent**: `agy` (Session: `herdr:reviewer-agy-01`)
|
||||||
|
- **Role**: Reviewer
|
||||||
|
- **Target Worktree Context**:
|
||||||
|
- **Loop ID**: `loop-20260924T080014Z-2b3df0`
|
||||||
|
- **Base Commit**: `1debc7d92444385b9200e821efa0ff0f6d82023e` (v4.7.2)
|
||||||
|
- **Head Commit**: `59be1c89209a232059aec0c5aeecc2329929cb49` (6 commits: +765 / −30)
|
||||||
|
- **Tree SHA**: `a009c9062d9818f343ac57e6c72cda932887fab7`
|
||||||
|
- **Verification Path**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20260924T080014Z-2b3df0/1`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Executive Summary
|
||||||
|
|
||||||
|
PR #37 resolves Issue #36 ("위임 프롬프트를 argv 가 아닌 파일로 전달하고, 위임 실패를 종료 코드 1 로 보고", Closes #36) across 6 commits and 11 files (+765 / −30):
|
||||||
|
- `59be1c8` fix(issue-36): create_session hands the agent a brief path, not the task; a prompt file that cannot be written is removed
|
||||||
|
- `f26952c fix(issue-36): the legacy shell loop and lib.sh's submit helpers pass prompts by file
|
||||||
|
- `14d11bf` fix(issue-36): a job that cannot be registered after a rebuttal ends the loop with rc 1
|
||||||
|
- `f5cadd9` test(issue-36): prompts over the argv cap go through the real wrapper, registry and loop; a failed delegation ends the loop with rc 1 and no traceback
|
||||||
|
- `c77806b` fix(issue-36): the loop passes every delegated prompt as a file; a wrapper that cannot start is a failed submit, on the first try and on the retry
|
||||||
|
- `68a5d31` fix(issue-36): registry and wrapper accept --prompt-file; the wrapper hands the same path on
|
||||||
|
|
||||||
|
The core architecture correctly eliminates the 131,072-byte `MAX_ARG_STRLEN` barrier on Linux by passing prompts via temporary files (`--prompt-file`) across the Python loop runner, delegate-job wrapper, `registry.py`, `lib.sh`, and `create_session.sh`. All legacy CLI contracts and `--prompt` backward compatibility remain intact.
|
||||||
|
|
||||||
|
However, during execution in the verification environment on macOS, test `test_s9_shell_loop_removes_a_partial_prompt_file_and_says_why` in `tests/test_issue36_prompt_transport.py` **failed** with an `AssertionError` at line 602 (`assert "File too large" in result.stderr`). This is a platform-dependent test expectation: GNU bash / glibc on Linux prints `write error: File too large` when `ulimit -f` is exceeded, whereas macOS `/bin/bash` (3.2.57) does not emit that string. Because the test suite must pass cleanly across supported platforms (macOS/Linux), a verdict of `NOT PASS` is issued.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Blocking Defect Details
|
||||||
|
|
||||||
|
### [Defect 1] Platform-Specific Assertion Failure on macOS in `test_s9`
|
||||||
|
- **File & Line**: `tests/test_issue36_prompt_transport.py:602`
|
||||||
|
- **Code**:
|
||||||
|
```python
|
||||||
|
598: harness.write_text('set -euo pipefail\nREPO_ROOT="$PWD"\nMAM_REAL_ROOT="$PWD"\n'
|
||||||
|
599: 'log_error() { printf "%s\\n" "$1"; }\n' + function +
|
||||||
|
600: '\ntrap "" XFSZ\nulimit -f 1\n'
|
||||||
|
601: 'output=$(delegate_job_safe submit --prompt "$(cat input.md)")\n')
|
||||||
|
602: result = subprocess.run(["bash", str(harness)], cwd=tmp_path, capture_output=True, text=True, timeout=60)
|
||||||
|
603: assert result.returncode != 0
|
||||||
|
604: assert "File too large" in result.stderr
|
||||||
|
605: leftovers = list((tmp_path / ".mam").glob("prompt-shloop-*"))
|
||||||
|
606: assert not leftovers, [(p.name, p.stat().st_size) for p in leftovers]
|
||||||
|
607: assert "delegate_job_safe: cannot write the prompt file" in result.stderr
|
||||||
|
```
|
||||||
|
- **Error Trace**:
|
||||||
|
```
|
||||||
|
FAILED tests/test_issue36_prompt_transport.py::test_s9_shell_loop_removes_a_partial_prompt_file_and_says_why
|
||||||
|
AssertionError: assert 'File too large' in 'xxxx...xxxxdelegate_job_safe: cannot write the prompt file under /private/tmp/.../.mam\n'
|
||||||
|
```
|
||||||
|
- **Root Cause**:
|
||||||
|
When `ulimit -f 1` is exceeded during `printf '%s' "$2" > "$prompt_file"`:
|
||||||
|
* On Linux (GNU bash 5.x / glibc), the shell writes `bash: line X: printf: write error: File too large` to stderr.
|
||||||
|
* On macOS (Darwin / bash 3.2.57), the redirection write fails and `printf` exits non-zero without printing the exact literal `"File too large"`.
|
||||||
|
* The actual implementation behavior is completely correct on macOS: `result.returncode != 0` (Line 603 passes), partial files are cleaned up (`assert not leftovers` Line 606 passes), and MAM's error message `delegate_job_safe: cannot write the prompt file` is printed to stderr (Line 607 passes).
|
||||||
|
* Line 604 strictly demands `"File too large"` unconditionally, causing the test to fail on macOS.
|
||||||
|
- **Remediation**:
|
||||||
|
Guard the assertion with `sys.platform == "linux"`, or allow MAM's error string to satisfy the failure check:
|
||||||
|
```python
|
||||||
|
if sys.platform == "linux":
|
||||||
|
assert "File too large" in result.stderr
|
||||||
|
```
|
||||||
|
or
|
||||||
|
```python
|
||||||
|
assert ("File too large" in result.stderr or "cannot write the prompt file" in result.stderr)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Detailed Verification of Other Checklist Items
|
||||||
|
|
||||||
|
### (1) Completeness of Prompt Transport Pathways
|
||||||
|
- **No Residual Raw `--prompt` Exec Invocations**:
|
||||||
|
- `lib_py/loop/runner.py`: `_run_cmd` creates a temporary file under `self.mam_dir` (`prompt-{loop_id}-*.md`) and invokes the wrapper with `--prompt-file <path>`. The temporary file is safely cleaned up in a `finally:` block.
|
||||||
|
- `lib_py/delegation/registry.py`: Supports mutually exclusive `--prompt` and `--prompt-file` in both `register` and `update` commands. Evaluated via `_prompt_from_args()` during CLI parsing before writing to registry JSON.
|
||||||
|
- `lib_py/delegation/cli.py`: Supports `--prompt-file`, forwarding to `registry.py`.
|
||||||
|
- `lib.sh`: `delegate_submit_job()` and `_delegate_submit_job_legacy()` both utilize `_delegate_prompt_file` and clean up via `rm -f "$prompt_file"`.
|
||||||
|
- `multi-agent-mux-delegate-job`: Iteration brief provisions write to `$job_dir/prompt.md` and invoke `registry.py update --prompt-file "$job_dir/prompt.md"`.
|
||||||
|
- `run_loop.sh`: `delegate_job_safe` intercepts `--prompt` and converts it into `--prompt-file "$prompt_file"`, unlinking it upon return.
|
||||||
|
- `create_session.sh`: Injected pane text no longer includes the full prompt text; instead, writes `$SUBMIT_JOB_PROMPT` to `$task_brief` (`.mam/jobs/<id>/brief.md`) and passes: `Task: read the complete task in $task_brief and follow it.`.
|
||||||
|
- Relative paths passed to `--prompt-file` in `cmd_submit` are converted to absolute paths against caller `pwd` before changing directory to `WORKDIR` (`multi-agent-mux-delegate-job:384`), preventing directory-shift bugs.
|
||||||
|
|
||||||
|
### (2) Error and Failure Handling
|
||||||
|
- **`OSError` Handling on Wrapper Execution**:
|
||||||
|
- In `runner.py`:
|
||||||
|
```python
|
||||||
|
except OSError as e:
|
||||||
|
msg = (f"cannot start the wrapper: {e} "
|
||||||
|
f"(prompt {len(p_text.encode('utf-8'))} bytes, wrapper {wrapper_script})")
|
||||||
|
return subprocess.CompletedProcess(cmd, 126, "", msg)
|
||||||
|
```
|
||||||
|
Wraps execution failure cleanly as returncode 126. The loop logs the failure and aborts with rc 1 without tracebacks (`test_s3b`, `test_s4`).
|
||||||
|
- **Fail-Closed Unregistered / Failed Jobs**:
|
||||||
|
- Re-adjudication failure (`if not adj_jid:`): terminates with error and rc 1.
|
||||||
|
- Arbitration failure (`if not arb_jid:`): terminates with error and rc 1.
|
||||||
|
- Compliance failure (`if not comp_jid or not self.wait_for_job(comp_jid):`): terminates with error and rc 1.
|
||||||
|
- Changing compliance failure from continue to `return 1` is an intentional, sound hardening: if the compliance job fails or times out after an overruled rebuttal, the Creator did not implement the required changes; proceeding to review un-updated code would be erroneous.
|
||||||
|
|
||||||
|
### (3) Evidence vs. Delivery Sizing (`MAM_DIFF_MAX_BYTES` / `MAM_DIFF_MAX_LINES`)
|
||||||
|
- In `diff_collect.sh`: Comments clarify that `MAM_DIFF_MAX_BYTES` (default 200,000) and `MAM_DIFF_MAX_LINES` (default 4000) bound the evidence shown to reviewers, while delivery itself is unbound due to `--prompt-file`.
|
||||||
|
- `test_s5_a_diff_in_the_former_blind_spot_is_delivered_whole` confirms a 150,000-byte diff (between 131,072 and 200,000 bytes) is not truncated and delivered intact.
|
||||||
|
|
||||||
|
### (4) `create_session.sh` Onboarding & Brief Alignment
|
||||||
|
- Task text is written to `.mam/jobs/<id>/brief.md`.
|
||||||
|
- Injected instruction provides the absolute path to `brief.md`.
|
||||||
|
- Handshake protocol (`delegate_publish_cmd`, `--event started`, token verification) remains 100% compliant with Issue #23. Verified by `test_issue23_24_startup_path.py` (19/19 tests passed).
|
||||||
|
|
||||||
|
### (5) Existing Test Contracts & Backwards Compatibility
|
||||||
|
- `test_issue5_phase1_delegation.py`: **66 / 66 passed**.
|
||||||
|
- `test_version_consistency.py`: **3 / 3 passed**.
|
||||||
|
- `test_loop_cli.py`: **9 / 9 passed**.
|
||||||
|
- `test_issue14_delivery_ack.py`: **12 / 12 passed**.
|
||||||
|
- `test_issue21_phase2_loop_a.py`: **29 / 29 passed**.
|
||||||
|
- Existing `--prompt` syntax remains fully supported for backward compatibility across all CLIs.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Test Execution Summary
|
||||||
|
|
||||||
|
| Test Suite | Result | Duration | Notes |
|
||||||
|
| :--- | :--- | :--- | :--- |
|
||||||
|
| `tests/test_issue36_prompt_transport.py` | **20 PASS, 1 FAIL, 1 SKIP** | 39.9s | `test_s9` failed on line 602 (`File too large` assertion on macOS); `test_s1b` skipped (`linux_only`) |
|
||||||
|
| `tests/test_issue5_phase1_delegation.py` | **66 / 66 PASS** | 12.1s | Registry CLI, backward compatibility with `--prompt` |
|
||||||
|
| `tests/test_issue23_24_startup_path.py` (S7/S8) | **19 / 19 PASS** | 63.1s | `create_session.sh` brief task injection & handshake |
|
||||||
|
| `tests/test_loop_cli.py` | **9 / 9 PASS** | 39.2s | Loop CLI regressions |
|
||||||
|
| `tests/test_issue14_delivery_ack.py` | **12 / 12 PASS** | 98.2s | Delivery acknowledgement contracts |
|
||||||
|
| `tests/test_issue21_phase2_loop_a.py` | **29 / 29 PASS** | 155.3s | Isolated worktree loop review regressions |
|
||||||
|
| `tests/test_version_consistency.py` | **3 / 3 PASS** | 0.3s | Version consistency checks |
|
||||||
|
| `git diff 1debc7d...HEAD --check` | **0 errors** | 0.05s | Clean formatting and no whitespace errors |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[VERDICT: NOT PASS]
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
# 🔍 Review Report — Job 93408cd8 (PR #37 @ `59be1c8`)
|
||||||
|
|
||||||
|
- **Reviewer**: `reviewer-grok-01` · 2026-09-24
|
||||||
|
- **검증 경로**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20260924T080014Z-2b3df0/1`
|
||||||
|
- **identity**: base `1debc7d` head `59be1c8` tree `a009c90` (일치)
|
||||||
|
- **본 체크아웃**: `main` `1debc7d` 유지
|
||||||
|
|
||||||
|
`git diff --check` 클린. 6커밋.
|
||||||
|
|
||||||
|
## NOT PASS
|
||||||
|
|
||||||
|
### N1. S9 가 macOS 에서 실패한다
|
||||||
|
|
||||||
|
`tests/test_issue36_prompt_transport.py:602` 가 stderr 에 리터럴 `File too large` 를 요구한다. 이 호스트(Darwin)에서 `ulimit -f 1` 로 `delegate_job_safe` 쓰기가 실패하면 rc 는 1 이고 stderr 는 이식 가능한 문장뿐이다:
|
||||||
|
|
||||||
|
`delegate_job_safe: cannot write the prompt file under …/.mam`
|
||||||
|
|
||||||
|
bash 가 libc 문구 `File too large` 를 찍지 않아 단언이 깨진다. 같은 스위트는 **1 failed, 99 passed, 1 skipped** (`s1b` 는 `linux_only`).
|
||||||
|
|
||||||
|
**대안**: 잔여 파일 없음과 `cannot write the prompt file` 은 모든 플랫폼에서 단언하고, `File too large` 는 Linux 에서만 보거나 빼라. 제품 경로(`run_loop.sh` 의 `rm -f` + stderr)는 이미 그 계약이다.
|
||||||
|
|
||||||
|
## 그 외 (N1 만 막음)
|
||||||
|
|
||||||
|
**(1) 전달.** 러너 `_run_cmd` 는 `--prompt-file` 만 (`runner.py:485`). 래퍼는 파일이면 그 경로를 registry 에 넘기고, 그 전에 읽는다 (`multi-agent-mux-delegate-job:188-199`). `cli.py` / `registry.py` 는 `--prompt` 와 `--prompt-file` 상호 배타. `lib.sh` 제출 두 곳과 `delegate_job_safe` 가 `--prompt` 를 파일로 바꾼다. 옛 셸 러너의 `--prompt` 호출은 모두 `delegate_job_safe` 를 탄다. 문서의 `--prompt` 예시는 하위 호환. 임시 파일은 제어 루트 `.mam/prompt-*`, 래퍼 반환 후 삭제. 셸 쓰기 실패는 `rm -f`.
|
||||||
|
|
||||||
|
**(2) 실패.** OSError → rc 126, 재시도도 같은 처리, 등록 실패면 `None` → 루프 rc 1. 재심·중재 **미등록** 은 rc 1. 순응 잡은 미등록뿐 아니라 `wait_for_job` 실패도 rc 1 (`runner.py:1393-1395`). 코드가 안 고쳐진 채 다음 라운드로 가면 안 되므로 강화가 맞다. 로그 문장이 wait 실패에도 “Failed to register” 인 것은 비차단.
|
||||||
|
|
||||||
|
**(3) diff 상한.** `diff_collect.sh` 주석: 증거만 자르고 전달은 파일. 잘린 diff 의 diff-only 원격 거부는 `runner.py:1204` 그대로.
|
||||||
|
|
||||||
|
**(4) create.** 주입문은 brief 절대경로만. `delegate_publish_cmd` 와 `--expect-dispatch-token` 유지. S8(100·130500) 통과.
|
||||||
|
|
||||||
|
**(5) 테스트.** 140KB 는 실제 wrapper/registry subprocess. S1b(E2BIG)는 Linux 전용 — macOS 는 커널 상한이 없어 skip 이 맞다. 경로는 파일을 검증한다. S8/S9 는 mock 이 아님.
|
||||||
|
|
||||||
|
**(6)–(8).** `--prompt` 짧은 문자열은 그대로. 4.7.3 bump 는 오케스트레이터 예정.
|
||||||
|
|
||||||
|
## 테스트
|
||||||
|
|
||||||
|
검증 경로, `AGENT_PYTHON_BIN` = 컨트롤 `.venv`, basetemp `/tmp`:
|
||||||
|
|
||||||
|
`test_issue36_prompt_transport.py` + `test_issue23_24_startup_path.py` + `test_loop_cli.py`
|
||||||
|
|
||||||
|
**1 failed, 99 passed, 1 skipped in 237.33s.**
|
||||||
|
|
||||||
|
[VERDICT: NOT PASS]
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"loop_id": "loop-20260924T085626Z-b8987e",
|
||||||
|
"base_sha": "59be1c89209a232059aec0c5aeecc2329929cb49",
|
||||||
|
"head_sha": "708262a598c4301c0ccc70efba5401093b4b1e30",
|
||||||
|
"tree_sha": "d601c2d01bb4c09931e5e1989e140b01d51f5b96",
|
||||||
|
"review_commit": "708262a598c4301c0ccc70efba5401093b4b1e30",
|
||||||
|
"reviewer": "reviewer-grok-01",
|
||||||
|
"job_id": "e44ac99c",
|
||||||
|
"verdict": "PASS",
|
||||||
|
"transport": "herdr",
|
||||||
|
"evidence": "review-tree",
|
||||||
|
"isolation": "worktree",
|
||||||
|
"verified_at": "2026-09-24T09:09:43Z"
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# 🔍 Review Report — Job e44ac99c (PR #37 델타 `59be1c8...708262a`)
|
||||||
|
|
||||||
|
- **Reviewer**: `reviewer-grok-01` · 2026-09-24
|
||||||
|
- **검증 경로**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20260924T085626Z-b8987e/1`
|
||||||
|
- **identity**: head `708262a` tree `d601c2d` (일치). 1회차 grok NOT PASS (S9) 를 이 커밋이 닫음.
|
||||||
|
- **본 체크아웃**: `main` `1debc7d` 유지.
|
||||||
|
|
||||||
|
`git diff --check 59be1c8...HEAD` 클린. 커밋 1개.
|
||||||
|
|
||||||
|
## (1) 세 정정 — 닫힘
|
||||||
|
|
||||||
|
**R1/H1.** `runner.py:472-491`: `prompt_file=""` 로 초기화하고, 이름을 쓰기 **전**에 잡은 뒤 `fh.write`. `OSError` 면 부분 파일을 지우고 rc 126 `CompletedProcess` 를 반환한다. 그 반환은 `subprocess.run` 의 try 에 들어가지 않으므로 래퍼는 실행되지 않고 unlink 는 한 번이다. 정상 경로는 아래 `finally` 가 한 번 지운다. rc 126 은 exec 실패와 같아 busy(5) 재시도가 아니고, stdout 에 job id 가 없으면 `None` → 루프 rc 1. S4c 통과. 임시 사본에서 이 except 를 빼면 S4c 가 `OSError: No space left on device` 로 실패했다.
|
||||||
|
|
||||||
|
**R2/N1.** S9 (`test_issue36_prompt_transport.py`) 는 rc≠0, `prompt-shloop-*` 없음, `cannot write the prompt file` 를 모든 플랫폼에서 보고, `File too large` 는 `sys.platform == "linux"` 일 때만. 이 macOS 에서 S9 포함 **23 passed, 1 skipped**.
|
||||||
|
|
||||||
|
**H3.** 래퍼 `multi-agent-mux-delegate-job:193` `[[ -s "$PROMPT_FILE" ]]` 아니면 exit 1, `is empty`. S1e 통과.
|
||||||
|
|
||||||
|
## (2) 빈 파일 거부
|
||||||
|
|
||||||
|
막힌 것은 내용이 없는 `--prompt-file` 뿐이다. `--prompt` 짧은 문자열과 loop 형 `prompt.md`(본문을 쓴 뒤 넘김)은 그대로다. 빈 파일을 등록해야 하는 호출자는 없다.
|
||||||
|
|
||||||
|
## (3) 1회차·락스텝
|
||||||
|
|
||||||
|
파일 전달, #23 brief 경로, 순응 잡 rc 1 은 이 커밋이 되돌리지 않는다. 4.7.3 bump 는 오케스트레이터 예정. 병합 차단 아님.
|
||||||
|
|
||||||
|
## 테스트
|
||||||
|
|
||||||
|
검증 경로, `AGENT_PYTHON_BIN` = 컨트롤 `.venv`, basetemp `/tmp`:
|
||||||
|
|
||||||
|
`tests/test_issue36_prompt_transport.py` → **23 passed, 1 skipped in 38.74s** (skip 은 Linux `E2BIG` S1b).
|
||||||
|
|
||||||
|
## 종합
|
||||||
|
|
||||||
|
1회차 N1 과 쓰기 실패·빈 파일 지적이 코드·macOS 실행과 맞다. 설계 재작업 불필요.
|
||||||
|
|
||||||
|
[VERDICT: PASS]
|
||||||
@@ -29,7 +29,7 @@ export WORKSPACE_ROOT
|
|||||||
# Framework semantic version. Single runtime source of truth; kept in lockstep
|
# Framework semantic version. Single runtime source of truth; kept in lockstep
|
||||||
# with VERSIONS.md and the 8 SKILL.md frontmatters by tests/test_version_consistency.py.
|
# with VERSIONS.md and the 8 SKILL.md frontmatters by tests/test_version_consistency.py.
|
||||||
# NOTE: unlike other MAM_* variables this one is intentionally NOT env-overridable.
|
# NOTE: unlike other MAM_* variables this one is intentionally NOT env-overridable.
|
||||||
MAM_VERSION="4.7.2"
|
MAM_VERSION="4.7.3"
|
||||||
export MAM_VERSION
|
export MAM_VERSION
|
||||||
AGENT_SESSIONS_YAML="${AGENT_SESSIONS_YAML:-$WORKSPACE_ROOT/.mam/agent-sessions.yaml}"
|
AGENT_SESSIONS_YAML="${AGENT_SESSIONS_YAML:-$WORKSPACE_ROOT/.mam/agent-sessions.yaml}"
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import sys
|
|||||||
from typing import List, Sequence
|
from typing import List, Sequence
|
||||||
|
|
||||||
# Framework version locked with framework constants
|
# Framework version locked with framework constants
|
||||||
FRAMEWORK_VERSION = "4.7.2"
|
FRAMEWORK_VERSION = "4.7.3"
|
||||||
|
|
||||||
CORE_MODULES: Sequence[str] = (
|
CORE_MODULES: Sequence[str] = (
|
||||||
# Standard library modules
|
# Standard library modules
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-create
|
name: multi-agent-mux-create
|
||||||
description: "Create a new agent session (claude, antigravity/agy) in a dedicated herdr session for context-preserving long-running work. Always creates a herdr session — never backgrounds with nohup/disown. Writes the new session to .mam/agent-sessions.yaml. Use when you want to start a fresh agent (no prior UUID) for a new project workspace."
|
description: "Create a new agent session (claude, antigravity/agy) in a dedicated herdr session for context-preserving long-running work. Always creates a herdr session — never backgrounds with nohup/disown. Writes the new session to .mam/agent-sessions.yaml. Use when you want to start a fresh agent (no prior UUID) for a new project workspace."
|
||||||
version: 4.7.2
|
version: 4.7.3
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-delegate-job
|
name: multi-agent-mux-delegate-job
|
||||||
description: "Delegate a unit of work to any autonomous agent (claude-code, hermes, agy, grok-build, codex, or a human) and observe it asynchronously over an MQTT event channel. Supported roles include orchestrator, worker, and reviewer."
|
description: "Delegate a unit of work to any autonomous agent (claude-code, hermes, agy, grok-build, codex, or a human) and observe it asynchronously over an MQTT event channel. Supported roles include orchestrator, worker, and reviewer."
|
||||||
version: 4.7.2
|
version: 4.7.3
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos, windows]
|
platforms: [linux, macos, windows]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-loop
|
name: multi-agent-mux-loop
|
||||||
description: "Run an autonomous planning-execution-review loop using multiple agents (Planner, Creator, Reviewers) in the workspace. Automatically orchestrates plan discussion, code changes, and peer reviews until a unanimous PASS is achieved or the maximum iteration limit is reached."
|
description: "Run an autonomous planning-execution-review loop using multiple agents (Planner, Creator, Reviewers) in the workspace. Automatically orchestrates plan discussion, code changes, and peer reviews until a unanimous PASS is achieved or the maximum iteration limit is reached."
|
||||||
version: 4.7.2
|
version: 4.7.3
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-monitor
|
name: multi-agent-mux-monitor
|
||||||
description: "Run a long-lived reconciler that watches .mam/agent-sessions.yaml against the actual herdr/agent runtime state and reconciles them. Use when you want live visibility into which agent sessions are running, which are dead, which have stale YAML entries, and which have new session ids that haven't been recorded yet. Runs as a persistent loop (`reconcile.sh --subscribe`) that keeps going until it times out, idles out, or is interrupted."
|
description: "Run a long-lived reconciler that watches .mam/agent-sessions.yaml against the actual herdr/agent runtime state and reconciles them. Use when you want live visibility into which agent sessions are running, which are dead, which have stale YAML entries, and which have new session ids that haven't been recorded yet. Runs as a persistent loop (`reconcile.sh --subscribe`) that keeps going until it times out, idles out, or is interrupted."
|
||||||
version: 4.7.2
|
version: 4.7.3
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-orc-onboard
|
name: multi-agent-mux-orc-onboard
|
||||||
description: "Register current or specified orchestrator session UUID into agent-sessions.yaml orchestrator_uuids list to prevent sub-agent discovery capture."
|
description: "Register current or specified orchestrator session UUID into agent-sessions.yaml orchestrator_uuids list to prevent sub-agent discovery capture."
|
||||||
version: 4.7.2
|
version: 4.7.3
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-resume
|
name: multi-agent-mux-resume
|
||||||
description: "Resume an existing agent (claude, antigravity/agy) conversation by UUID into a herdr session. Reads .mam/agent-sessions.yaml for the saved session/conversation id, spawns (or reuses) a herdr session of the matching name, and runs `claude -r <id>` or `agy --conversation <id>` inside. Use when you want to reattach to a previous session's context, or revive a session whose herdr died but the agent's conversation is still on disk."
|
description: "Resume an existing agent (claude, antigravity/agy) conversation by UUID into a herdr session. Reads .mam/agent-sessions.yaml for the saved session/conversation id, spawns (or reuses) a herdr session of the matching name, and runs `claude -r <id>` or `agy --conversation <id>` inside. Use when you want to reattach to a previous session's context, or revive a session whose herdr died but the agent's conversation is still on disk."
|
||||||
version: 4.7.2
|
version: 4.7.3
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-status
|
name: multi-agent-mux-status
|
||||||
description: "Read-only instant snapshot of all agent herdr sessions — name, YAML status, herdr alive, pane cmd/cwd, resume UUID on disk, and any drift. No mutation. Reuses reconcile.sh --dry-run for the diff logic. Use when you want to know 'what's running RIGHT NOW' without spinning up the monitor loop."
|
description: "Read-only instant snapshot of all agent herdr sessions — name, YAML status, herdr alive, pane cmd/cwd, resume UUID on disk, and any drift. No mutation. Reuses reconcile.sh --dry-run for the diff logic. Use when you want to know 'what's running RIGHT NOW' without spinning up the monitor loop."
|
||||||
version: 4.7.2
|
version: 4.7.3
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: multi-agent-mux-stop
|
name: multi-agent-mux-stop
|
||||||
description: "Stop an agent herdr session (claude, antigravity/agy) and update .mam/agent-sessions.yaml. Default stops gracefully and marks status=stopped with conversation preserved for resume. Does NOT delete on-disk conversation artifacts (jsonl/db) — those are preserved unless --purge-conversation is passed. Use when ending a work session, switching to a different one, or cleaning up before a fresh start."
|
description: "Stop an agent herdr session (claude, antigravity/agy) and update .mam/agent-sessions.yaml. Default stops gracefully and marks status=stopped with conversation preserved for resume. Does NOT delete on-disk conversation artifacts (jsonl/db) — those are preserved unless --purge-conversation is passed. Use when ending a work session, switching to a different one, or cleaning up before a fresh start."
|
||||||
version: 4.7.2
|
version: 4.7.3
|
||||||
author: godopu
|
author: godopu
|
||||||
license: MIT
|
license: MIT
|
||||||
platforms: [linux, macos]
|
platforms: [linux, macos]
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
본 문서는 `herdr_agent_orchestration` 오케스트레이션 및 메시징 백플레인 워크플로우를 새로운 프로젝트에 도입하여 이식하고, 새로운 개발자/에이전트가 초기 가동을 시작할 때 수행해야 하는 환경 설정 및 구축 절차를 안내합니다.
|
본 문서는 `herdr_agent_orchestration` 오케스트레이션 및 메시징 백플레인 워크플로우를 새로운 프로젝트에 도입하여 이식하고, 새로운 개발자/에이전트가 초기 가동을 시작할 때 수행해야 하는 환경 설정 및 구축 절차를 안내합니다.
|
||||||
|
|
||||||
[](VERSIONS.md)
|
[](VERSIONS.md)
|
||||||
|
|
||||||
새로운 에이전트는 이 안내서에 기술된 절차를 순차적으로 실행하여 초기 환경을 안정적으로 설정할 수 있습니다.
|
새로운 에이전트는 이 안내서에 기술된 절차를 순차적으로 실행하여 초기 환경을 안정적으로 설정할 수 있습니다.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
This document guides you through the setup and initialization procedures required to adopt the `herdr_agent_orchestration` orchestration and messaging backplane workflow in a new project, enabling a new developer or agent to get up and running quickly.
|
This document guides you through the setup and initialization procedures required to adopt the `herdr_agent_orchestration` orchestration and messaging backplane workflow in a new project, enabling a new developer or agent to get up and running quickly.
|
||||||
|
|
||||||
[](VERSIONS.md)
|
[](VERSIONS.md)
|
||||||
|
|
||||||
A new agent can follow the steps in this guide sequentially to establish a stable and reliable initial environment.
|
A new agent can follow the steps in this guide sequentially to establish a stable and reliable initial environment.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
# Messaging System Technical Analysis & Architecture Report
|
# Messaging System Technical Analysis & Architecture Report
|
||||||
|
|
||||||
Applies to: MAM v4.7.2
|
Applies to: MAM v4.7.3
|
||||||
|
|
||||||
This report provides a comprehensive, deep-dive analysis of the messaging system implemented in the `multi-agent-mux-delegate-job` skill. It covers the MQTT broker architecture, event protocols, job lifecycles, codebase internals, cross-system integration, and a list of known limitations along with production recommendations.
|
This report provides a comprehensive, deep-dive analysis of the messaging system implemented in the `multi-agent-mux-delegate-job` skill. It covers the MQTT broker architecture, event protocols, job lifecycles, codebase internals, cross-system integration, and a list of known limitations along with production recommendations.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Herdr와 MQTT 브로커를 기반으로 구축된 고신뢰성 **다중 에이전트 오케스트레이션 및 메시징 백플레인** 프레임워크입니다. `claude`, `agy`, `hermes`, `grok`, `opencode`, `codex` 등 다중 LLM 백엔드 에이전트 전반에 걸쳐 장시간 수행되는 작업(코드 생성, 리팩토링, 보안 검토 등)을 조정, 격리 및 감사할 수 있도록 설계되었습니다.
|
Herdr와 MQTT 브로커를 기반으로 구축된 고신뢰성 **다중 에이전트 오케스트레이션 및 메시징 백플레인** 프레임워크입니다. `claude`, `agy`, `hermes`, `grok`, `opencode`, `codex` 등 다중 LLM 백엔드 에이전트 전반에 걸쳐 장시간 수행되는 작업(코드 생성, 리팩토링, 보안 검토 등)을 조정, 격리 및 감사할 수 있도록 설계되었습니다.
|
||||||
|
|
||||||
[](VERSIONS.md)
|
[](VERSIONS.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
An advanced, high-reliability **Multi-Agent Orchestration & Messaging Backplane** framework built on Herdr and MQTT. It is designed to coordinate, isolate, and audit long-running agent tasks (such as code generation, refactoring, and security reviews) across multiple LLM backend clients (`claude`, `agy`, `hermes`, `grok`, `opencode`, `codex`).
|
An advanced, high-reliability **Multi-Agent Orchestration & Messaging Backplane** framework built on Herdr and MQTT. It is designed to coordinate, isolate, and audit long-running agent tasks (such as code generation, refactoring, and security reviews) across multiple LLM backend clients (`claude`, `agy`, `hermes`, `grok`, `opencode`, `codex`).
|
||||||
|
|
||||||
[](VERSIONS.md)
|
[](VERSIONS.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
+27
-11
@@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
## 📌 현재 버전 개요 (Current Release)
|
## 📌 현재 버전 개요 (Current Release)
|
||||||
|
|
||||||
- **프레임워크 버전**: `v4.7.2`
|
- **프레임워크 버전**: `v4.7.3`
|
||||||
- **최신 릴리스 일시**: 2026-09-23 (KST)
|
- **최신 릴리스 일시**: 2026-09-24 (KST)
|
||||||
- **기준 브랜치**: `main`
|
- **기준 브랜치**: `main`
|
||||||
- **핵심 아키텍처**:
|
- **핵심 아키텍처**:
|
||||||
- **Terminal 상태 단방향 불변성 (`engine.py`, Issue #6 Phase 1)**: `completed`, `error`, `cancelled` 터미널 상태 진입 후 후속 이벤트나 업데이트에 의한 덮어쓰기를 엄격히 차단(Terminal Immutability). `registry.py close` 서브커맨드 및 fail-closed 조기 종료 도입.
|
- **Terminal 상태 단방향 불변성 (`engine.py`, Issue #6 Phase 1)**: `completed`, `error`, `cancelled` 터미널 상태 진입 후 후속 이벤트나 업데이트에 의한 덮어쓰기를 엄격히 차단(Terminal Immutability). `registry.py close` 서브커맨드 및 fail-closed 조기 종료 도입.
|
||||||
@@ -22,23 +22,39 @@
|
|||||||
|
|
||||||
## 🧭 스킬 패키지 버전 매트릭스 (Skills Version Matrix)
|
## 🧭 스킬 패키지 버전 매트릭스 (Skills Version Matrix)
|
||||||
|
|
||||||
모든 8개 스킬은 YAML frontmatter 메타데이터(`author`, `version`, `platforms`, `environments`) 표준화를 통해 `v4.7.2`로 동기화되어 배포됩니다.
|
모든 8개 스킬은 YAML frontmatter 메타데이터(`author`, `version`, `platforms`, `environments`) 표준화를 통해 `v4.7.3`로 동기화되어 배포됩니다.
|
||||||
|
|
||||||
| 스킬명 | 버전 | 역할 및 주요 책임 | 상태 |
|
| 스킬명 | 버전 | 역할 및 주요 책임 | 상태 |
|
||||||
| :--- | :---: | :--- | :---: |
|
| :--- | :---: | :--- | :---: |
|
||||||
| **`multi-agent-mux-create`** | `4.7.2` | 에이전트 세션 신규 생성 및 Herdr 컨테이너 격리 스폰 | ✅ 배포 |
|
| **`multi-agent-mux-create`** | `4.7.3` | 에이전트 세션 신규 생성 및 Herdr 컨테이너 격리 스폰 | ✅ 배포 |
|
||||||
| **`multi-agent-mux-stop`** | `4.7.2` | 대화 UUID 원자적 캡처 및 세션 안전 종료 (Graceful Stop) | ✅ 배포 |
|
| **`multi-agent-mux-stop`** | `4.7.3` | 대화 UUID 원자적 캡처 및 세션 안전 종료 (Graceful Stop) | ✅ 배포 |
|
||||||
| **`multi-agent-mux-resume`** | `4.7.2` | 온디스크 대화 컨텍스트 기반 Tier-1 초고속 세션 복원 | ✅ 배포 |
|
| **`multi-agent-mux-resume`** | `4.7.3` | 온디스크 대화 컨텍스트 기반 Tier-1 초고속 세션 복원 | ✅ 배포 |
|
||||||
| **`multi-agent-mux-status`** | `4.7.2` | 실시간 Herdr 세션 및 레지스트리 드리프트 스냅샷 조회 | ✅ 배포 |
|
| **`multi-agent-mux-status`** | `4.7.3` | 실시간 Herdr 세션 및 레지스트리 드리프트 스냅샷 조회 | ✅ 배포 |
|
||||||
| **`multi-agent-mux-monitor`** | `4.7.2` | YAML ↔ 런타임 상태 간 자율 조정자 (Reconciler Loop) | ✅ 배포 |
|
| **`multi-agent-mux-monitor`** | `4.7.3` | YAML ↔ 런타임 상태 간 자율 조정자 (Reconciler Loop) | ✅ 배포 |
|
||||||
| **`multi-agent-mux-delegate-job`** | `4.7.2` | MQTT 이벤트 채널 기반 비동기 단위 작업 위임 | ✅ 배포 |
|
| **`multi-agent-mux-delegate-job`** | `4.7.3` | MQTT 이벤트 채널 기반 비동기 단위 작업 위임 | ✅ 배포 |
|
||||||
| **`multi-agent-mux-loop`** | `4.7.2` | Planner-Creator-Reviewer 3자 자율 계획·실행·피어리뷰 루프 | ✅ 배포 |
|
| **`multi-agent-mux-loop`** | `4.7.3` | Planner-Creator-Reviewer 3자 자율 계획·실행·피어리뷰 루프 | ✅ 배포 |
|
||||||
| **`multi-agent-mux-orc-onboard`** | `4.7.2` | 오케스트레이터 UUID 격리 등록 및 서브 세션 오염 방지 | ✅ 배포 |
|
| **`multi-agent-mux-orc-onboard`** | `4.7.3` | 오케스트레이터 UUID 격리 등록 및 서브 세션 오염 방지 | ✅ 배포 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 📋 버전별 상세 변경 내역 (Changelog)
|
## 📋 버전별 상세 변경 내역 (Changelog)
|
||||||
|
|
||||||
|
### 🚀 `v4.7.3` — Issue #36 위임 프롬프트를 argv 가 아닌 파일로 전달, 위임 실패는 종료 코드 1 (2026-09-24, PR #37)
|
||||||
|
|
||||||
|
> **패치 마일스톤 (PATCH Release)**: 리뷰 루프가 diff 를 리뷰 프롬프트에 넣어 bash 에 **단일 argv 원소**로 넘기던 탓에, 프롬프트가 Linux 의 단일 인자 상한(`MAX_ARG_STRLEN` 131,072 바이트)을 넘으면 `OSError: [Errno 7]` 로 죽고 **종료 코드 0** 으로 끝나던 문제(Issue #36)를 고쳤다. 수집기 상한 `MAM_DIFF_MAX_BYTES`(기본 200000)가 커널 상한보다 높아 그 사이 크기는 걸러지지 않았고, 커밋 10여 개 규모의 브랜치(PR #35 가 실제로 133 KB)는 루프로 리뷰할 수 없었다. 옵션 하나(`--prompt-file`)가 늘지만 잘못된 전달 경로를 고치는 수정이라 PATCH 로 올린다.
|
||||||
|
|
||||||
|
#### 🌟 주요 개선 사항
|
||||||
|
1. **프롬프트는 파일로 전달 (`lib_py/loop/runner.py`, delegate-job 래퍼, `lib_py/delegation/registry.py`·`cli.py`, `lib.sh`, `run_loop.sh`)**: Python 러너는 모든 위임 프롬프트를 제어 루트 `.mam/` 아래 임시 파일에 쓰고 래퍼에 `--prompt-file` 로 넘긴 뒤 래퍼가 끝나면 지운다. 래퍼와 등록부는 `--prompt`/`--prompt-file` 을 상호 배타로 받고, 래퍼는 받은 경로를 등록부에 그대로 넘기며 loop 형 반복의 `update` 도 `prompt.md` 파일로 넘긴다. `lib.sh` 의 제출 도우미(`delegate_submit_job` 계열)와 `cli.py` 사슬, 옛 셸 러너(`MAM_LOOP_PY=0`)의 위임 11곳이 거치는 `delegate_job_safe` 도 파일로 넘긴다(쓰기 실패 시 부분 파일을 지우고 stderr 로 진단). `create_session.sh` 는 주입 안내문에 과제 본문 대신 잡 디렉터리의 `brief.md` 절대경로만 넣는다(herdr 주입문도 argv 하나다). 기존 `--prompt` 는 그대로 받는다.
|
||||||
|
2. **위임 실패는 성공처럼 끝나지 않음 (`runner.py`)**: 래퍼를 실행할 수 없으면(OSError) rc 126 의 실패한 제출로 다루고 첫 시도·재시도 모두 원인을 남기며 루프는 rc 1 로 끝난다. 반론 이후의 재심·중재 잡이 등록되지 않은 경우와 순응 잡이 등록되지 않거나 끝나지 않은 경우도 판정이 아니라 실패(rc 1)다.
|
||||||
|
3. **수집기 상한의 의미 (`diff_collect.sh`)**: `MAM_DIFF_MAX_BYTES`·`MAM_DIFF_MAX_LINES` 는 리뷰어에게 보여줄 증거의 크기만 제한한다. 전달 자체에는 크기 한계가 없고, 잘린 diff 를 증거로 쓸 때의 기존 규칙(diff-only 원격 리뷰어 거부)은 그대로다.
|
||||||
|
4. **문서**: delegate-job SKILL.md(`--prompt-file`), loop SKILL.md.
|
||||||
|
|
||||||
|
#### ✅ 품질 검증
|
||||||
|
- **신규 테스트 22개** (`tests/test_issue36_prompt_transport.py`): 131,072 바이트를 넘는 실제 프롬프트가 실제 subprocess/exec 경로(러너 → 래퍼 → 등록부, `lib.sh` 도우미, 옛 셸 러너, `create_session`)를 지나는지, 임시 파일의 생성·정리·쓰기 실패 처리, 래퍼를 실행할 수 없을 때 rc 1 과 트레이스백 부재. 수정 전 코드에서는 13건 중 11건이 실패.
|
||||||
|
- **리뷰**: 작성 워크스페이스 5인 3라운드 전원 PASS(codex 1차: 반론 경로에서 잡 등록 실패 시 루프 계속 → 14d11bf; 오케스트레이터: 옛 셸 러너·`lib.sh` 도우미의 argv 잔존 → f26952c; codex 델타: strace 로 `create_session` 의 E2BIG 와 셸 러너의 부분 파일 잔존 재현 → 59be1c8). 병합 워크스페이스에서 PR head 를 detached 워크트리에 받아 재검토: 1회차 @59be1c8 claude PASS(비차단 H1–H5), agy·grok·codex NOT PASS — 새 테스트 S9 가 stderr 의 리터럴 `File too large` 를 요구해 macOS bash 3.2 에서 실패(agy·grok·codex R2), Python 러너의 프롬프트 파일 **쓰기** 실패가 정리·오류 처리 범위 밖이라 부분 파일이 남고 예외가 루프까지 올라옴(claude H1·codex R1), 래퍼가 빈 `--prompt-file` 을 받아들임(claude H3) → 708262a 로 정정(쓰기 실패는 exec 실패와 같은 rc 126 실패 제출, libc 문구는 Linux 에서만 단언, 빈 파일 거부; 회귀 S4c·S1e 는 정정을 되돌리면 실패). 델타 리뷰 @708262a claude·agy·grok·codex **4인 전원 PASS**(identity 4건 모두 base 59be1c8 → head 708262a, evidence review-tree). codex 는 1회차에서 이 호스트의 `SC_ARG_MAX` 를 넘는 1,114,112 바이트 프롬프트가 argv 로는 `OSError(errno=7)` 이고 파일로는 공백 경로·다른 `--workdir` 조합에서도 원문 그대로 등록됨을 실측. 리포트와 identity 사이드카는 `.agents/reports/<session>/`.
|
||||||
|
- **전체 스위트 (병합 워크스페이스, 자체 venv 가 있는 워크트리 @59be1c8)**: @59be1c8 1139 통과 / 2 skip / 실패 1(S9, 위 R2; 54분 7초) → @708262a 관련 스위트(transport·delegation 계약·loop CLI·delivery ack·version) 125 통과 / 1 skip(Linux 전용 S1b).
|
||||||
|
- **비차단 후속**: `MAM_DIFF_MAX_*` 의 "증거 상한, 전달 무제한" 의미가 loop SKILL.md 에 없음(H2); 래퍼의 `$(<file)` 이 후행 개행을 잃어 등록부 프롬프트와 브리프 본문이 그 점에서 다를 수 있음(H4); 순응 잡의 실행 실패도 rc 1 로 끝내는 강화에서 로그 문구가 "Failed to register" 로 등록 실패와 구분되지 않음(grok·codex).
|
||||||
|
|
||||||
### 🚀 `v4.7.2` — Issue #23·#24 세션 생성 경로: 교차 서버 herdr 스코프, 에이전트 무관 시작 다이얼로그, 실제 에이전트가 충족하는 온보딩 전달 판정 (2026-09-23, PR #35)
|
### 🚀 `v4.7.2` — Issue #23·#24 세션 생성 경로: 교차 서버 herdr 스코프, 에이전트 무관 시작 다이얼로그, 실제 에이전트가 충족하는 온보딩 전달 판정 (2026-09-23, PR #35)
|
||||||
|
|
||||||
> **패치 마일스톤 (PATCH Release)**: `create_session.sh` 로 새 세션을 만드는 첫 1분 안의 결함 둘을 고쳤다. 오케스트레이터 pane 이 물려주는 herdr 환경(`HERDR_WORKSPACE_ID`·`HERDR_PANE_ID`·`HERDR_TAB_ID`)이 격리 서버 조회를 오염시켜 온보딩 프롬프트가 전달되지 않고, 전달 판정이 화면 기준이라 살아 있는 에이전트를 실패로 판정한 뒤 롤백까지 실패하던 문제(#23)와, 시작 신뢰(trust) 다이얼로그를 claude 에게만 처리해 agy·grok·codex 가 준비 대기 초과로 온보딩을 건너뛰던 문제(#24)다. 옵션 하나(`--no-auto-trust`)와 종료 코드 둘(42·43)이 늘지만 잘못된 기본 동작을 고치는 수정이라 PATCH 로 올린다.
|
> **패치 마일스톤 (PATCH Release)**: `create_session.sh` 로 새 세션을 만드는 첫 1분 안의 결함 둘을 고쳤다. 오케스트레이터 pane 이 물려주는 herdr 환경(`HERDR_WORKSPACE_ID`·`HERDR_PANE_ID`·`HERDR_TAB_ID`)이 격리 서버 조회를 오염시켜 온보딩 프롬프트가 전달되지 않고, 전달 판정이 화면 기준이라 살아 있는 에이전트를 실패로 판정한 뒤 롤백까지 실패하던 문제(#23)와, 시작 신뢰(trust) 다이얼로그를 claude 에게만 처리해 agy·grok·codex 가 준비 대기 초과로 온보딩을 건너뛰던 문제(#24)다. 옵션 하나(`--no-auto-trust`)와 종료 코드 둘(42·43)이 늘지만 잘못된 기본 동작을 고치는 수정이라 PATCH 로 올린다.
|
||||||
|
|||||||
Reference in new issue
Block a user