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, base
59be1c8 -> head 708262a, 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:
GodopuandClaude Fable 5.1 committed 2026-09-24 18:10:28 +09:00
1 parent 708262a598
commit c3e6a7e96d
28 files changed
+740 -26

No files matched your search

@@ -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]