diff --git a/.agents/reports/planner-reviewer-claude-01/report-1a9fbbc4.identity.json b/.agents/reports/planner-reviewer-claude-01/report-1a9fbbc4.identity.json new file mode 100644 index 0000000..d9c59c4 --- /dev/null +++ b/.agents/reports/planner-reviewer-claude-01/report-1a9fbbc4.identity.json @@ -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" +} \ No newline at end of file diff --git a/.agents/reports/planner-reviewer-claude-01/report-1a9fbbc4.md b/.agents/reports/planner-reviewer-claude-01/report-1a9fbbc4.md new file mode 100644 index 0000000..c615593 --- /dev/null +++ b/.agents/reports/planner-reviewer-claude-01/report-1a9fbbc4.md @@ -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 : (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] diff --git a/.agents/reports/planner-reviewer-claude-01/report-4ab12ae2.md b/.agents/reports/planner-reviewer-claude-01/report-4ab12ae2.md new file mode 100644 index 0000000..6bad7c1 --- /dev/null +++ b/.agents/reports/planner-reviewer-claude-01/report-4ab12ae2.md @@ -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 ` argv | `.mam/prompt--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 ""` (ํ•จ์ˆ˜ ์ธ์ž) | ํ•จ์ˆ˜ ์•ˆ์—์„œ `--prompt` ๋ฅผ `.mam/prompt-shloop-XXXXXX` + `--prompt-file` ๋กœ ๋ฐ”๊ฟ” ๋ž˜ํผ exec | `run_loop.sh:196-221`. `run_loop.sh` ์˜ ์ž”์กด `--prompt "โ€ฆ"` 11๊ณณ์€ ์ด **ํ•จ์ˆ˜** ํ˜ธ์ถœ์ด๋ผ exec ์„ ๊ฑฐ์น˜์ง€ ์•Š๋Š”๋‹ค | +| `create_session.sh --submit-job` | ์ฃผ์ž…๋ฌธ์— ๊ณผ์ œ ๋ณธ๋ฌธ ํฌํ•จ | ๊ณผ์ œ๋Š” `//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` ๋ณ€์ˆ˜(`$(` ๊ฑฐ๋ถ€. +- ์ฝ๋Š” ์‹œ์ : 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 | `//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`(ํŒŒ์ผ ๊ฒฝ์œ )์ด๊ณ , ๊ทธ ๋’ค `//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="$(&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] diff --git a/.agents/reports/reviewer-agy-01/report-db46a0da.md b/.agents/reports/reviewer-agy-01/report-db46a0da.md new file mode 100644 index 0000000..3b00267 --- /dev/null +++ b/.agents/reports/reviewer-agy-01/report-db46a0da.md @@ -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 `. 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//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//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] diff --git a/.agents/reports/reviewer-grok-01/report-93408cd8.md b/.agents/reports/reviewer-grok-01/report-93408cd8.md new file mode 100644 index 0000000..693272d --- /dev/null +++ b/.agents/reports/reviewer-grok-01/report-93408cd8.md @@ -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] diff --git a/.agents/reports/reviewer-grok-01/report-e44ac99c.identity.json b/.agents/reports/reviewer-grok-01/report-e44ac99c.identity.json new file mode 100644 index 0000000..5ee854b --- /dev/null +++ b/.agents/reports/reviewer-grok-01/report-e44ac99c.identity.json @@ -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" +} \ No newline at end of file diff --git a/.agents/reports/reviewer-grok-01/report-e44ac99c.md b/.agents/reports/reviewer-grok-01/report-e44ac99c.md new file mode 100644 index 0000000..fa3b5b3 --- /dev/null +++ b/.agents/reports/reviewer-grok-01/report-e44ac99c.md @@ -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] diff --git a/.agents/skills/lib.sh b/.agents/skills/lib.sh index c595e21..a150395 100644 --- a/.agents/skills/lib.sh +++ b/.agents/skills/lib.sh @@ -29,7 +29,7 @@ export WORKSPACE_ROOT # 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. # 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 AGENT_SESSIONS_YAML="${AGENT_SESSIONS_YAML:-$WORKSPACE_ROOT/.mam/agent-sessions.yaml}" @@ -2032,22 +2032,36 @@ _delegate_script() { printf '%s\n' "$(find "$skill_dir" -name "$name" 2>/dev/null | head -n 1 || true)" } +# _delegate_prompt_file โ€” write the prompt to a new file under the +# control root's .mam/ and echo its path. Issue #36: a prompt handed to an exec +# as one argv string is capped at 131,072 bytes; the caller removes the file. +_delegate_prompt_file() { + local dir="${WORKSPACE_ROOT:-$PWD}/.mam" f + mkdir -p "$dir" || return 1 + f="$(mktemp "$dir/prompt-submit-XXXXXX")" || return 1 + printf '%s' "$1" > "$f" || { rm -f "$f"; return 1; } + printf '%s\n' "$f" +} + # delegate_submit_job # # Register a job in the multi-agent-mux-delegate-job registry. Prints the new JID on stdout. _delegate_submit_job_legacy() { local prompt="$1" agent="$2" session="$3" - local py_bin registry_py + local py_bin registry_py prompt_file rc=0 py_bin="$(_delegate_py_bin)" registry_py="$(_delegate_script registry.py)" if [ -z "$registry_py" ] || [ ! -f "$registry_py" ]; then echo "ERROR: multi-agent-mux-delegate-job registry.py not found under .agents/skills/" >&2 return 1 fi + prompt_file="$(_delegate_prompt_file "$prompt")" || { echo "ERROR: cannot write the prompt file" >&2; return 1; } "$py_bin" "$registry_py" register \ - --prompt "$prompt" \ + --prompt-file "$prompt_file" \ --agent "$agent" \ - --agent-session "$session" + --agent-session "$session" || rc=$? + rm -f "$prompt_file" + return $rc } # delegate_publish_event [detail] @@ -2152,7 +2166,11 @@ delegate_submit_job() { echo "ERROR: multi-agent-mux-delegate-job registry.py not found under .agents/skills/" >&2 return 1 fi - _delegation_py submit --prompt "$prompt" --agent "$agent" --agent-session "$session" + local prompt_file rc=0 + prompt_file="$(_delegate_prompt_file "$prompt")" || { echo "ERROR: cannot write the prompt file" >&2; return 1; } + _delegation_py submit --prompt-file "$prompt_file" --agent "$agent" --agent-session "$session" || rc=$? + rm -f "$prompt_file" + return $rc } # delegate_publish_event [detail] diff --git a/.agents/skills/lib_py/delegation/cli.py b/.agents/skills/lib_py/delegation/cli.py index 0a9a634..27f860b 100644 --- a/.agents/skills/lib_py/delegation/cli.py +++ b/.agents/skills/lib_py/delegation/cli.py @@ -26,7 +26,11 @@ def _build_parser() -> argparse.ArgumentParser: sub = parser.add_subparsers(dest="command", required=True) p_submit = sub.add_parser("submit", help="register a job; prints the job id") - p_submit.add_argument("--prompt", required=True) + # Issue #36: lib.sh passes the prompt by file (one argv string is capped + # at 131,072 bytes); --prompt keeps working. + submit_prompt = p_submit.add_mutually_exclusive_group(required=True) + submit_prompt.add_argument("--prompt") + submit_prompt.add_argument("--prompt-file") p_submit.add_argument("--agent", default="claude-code") p_submit.add_argument("--agent-session", default="herdr:claude") p_submit.add_argument("--registry-dir", default=None) @@ -57,7 +61,9 @@ def main(argv: Optional[List[str]] = None) -> int: args = _build_parser().parse_args(argv) if args.command == "submit": - forward = ["register", "--prompt", args.prompt, + prompt_arg = (["--prompt-file", args.prompt_file] if args.prompt_file is not None + else ["--prompt", args.prompt]) + forward = ["register", *prompt_arg, "--agent", args.agent, "--agent-session", args.agent_session] if args.registry_dir: forward = ["--registry-dir", args.registry_dir] + forward diff --git a/.agents/skills/lib_py/delegation/registry.py b/.agents/skills/lib_py/delegation/registry.py index e5e4be3..72975ef 100755 --- a/.agents/skills/lib_py/delegation/registry.py +++ b/.agents/skills/lib_py/delegation/registry.py @@ -323,7 +323,11 @@ def _build_parser() -> argparse.ArgumentParser: sub = parser.add_subparsers(dest="command", required=True) p_reg = sub.add_parser("register", help="create a pending job; prints the job id") - p_reg.add_argument("--prompt", required=True) + # Issue #36: a long prompt goes by file - one argv string is capped at + # MAX_ARG_STRLEN (131,072 bytes on Linux). + reg_prompt = p_reg.add_mutually_exclusive_group(required=True) + reg_prompt.add_argument("--prompt") + reg_prompt.add_argument("--prompt-file", help="read the prompt from this file (utf-8)") p_reg.add_argument("--agent", default="claude-code") p_reg.add_argument("--agent-session", default="herdr:claude") p_reg.add_argument("--role", default="Worker", help="logical role for the delegated agent (e.g. Worker, Planner, Reviewer)") @@ -365,7 +369,9 @@ def _build_parser() -> argparse.ArgumentParser: p_update.add_argument("--job", required=True) p_update.add_argument("--status", default=None) p_update.add_argument("--agent-session", default=None) - p_update.add_argument("--prompt", default=None) + upd_prompt = p_update.add_mutually_exclusive_group() + upd_prompt.add_argument("--prompt", default=None) + upd_prompt.add_argument("--prompt-file", default=None, help="read the prompt from this file (utf-8)") p_update.add_argument("--iteration", type=int, default=None) p_update.add_argument("--attempt-id", type=int, default=None) p_update.add_argument("--role", default=None) @@ -473,15 +479,26 @@ def _operator_write(job_id: str, registry_dir: str, **fields) -> int: return 4 +def _prompt_from_args(parser: argparse.ArgumentParser, args: argparse.Namespace) -> Optional[str]: + """The prompt text: from --prompt-file (read verbatim) or from --prompt.""" + if getattr(args, "prompt_file", None) is None: + return args.prompt + try: + return Path(args.prompt_file).read_text(encoding="utf-8") + except (OSError, UnicodeDecodeError) as exc: + parser.error(f"cannot read --prompt-file {args.prompt_file}: {exc}") + + def main(argv: Optional[List[str]] = None) -> int: mqtt_common.ensure_env_loaded() mqtt_common.setup_logging(logging.INFO) - args = _build_parser().parse_args(argv) + parser = _build_parser() + args = parser.parse_args(argv) rd = args.registry_dir if args.command == "register": job_id = register_job( - prompt=args.prompt, + prompt=_prompt_from_args(parser, args), agent=args.agent, agent_session=args.agent_session, role=args.role, @@ -540,8 +557,9 @@ def main(argv: Optional[List[str]] = None) -> int: fields["status"] = args.status if args.agent_session is not None: fields["agent_session"] = args.agent_session - if args.prompt is not None: - fields["prompt"] = args.prompt + prompt = _prompt_from_args(parser, args) + if prompt is not None: + fields["prompt"] = prompt if args.iteration is not None: fields["iteration"] = args.iteration if args.attempt_id is not None: diff --git a/.agents/skills/lib_py/loop/eager.py b/.agents/skills/lib_py/loop/eager.py index 4c852c8..64702aa 100644 --- a/.agents/skills/lib_py/loop/eager.py +++ b/.agents/skills/lib_py/loop/eager.py @@ -13,7 +13,7 @@ import sys from typing import List, Sequence # Framework version locked with framework constants -FRAMEWORK_VERSION = "4.7.2" +FRAMEWORK_VERSION = "4.7.3" CORE_MODULES: Sequence[str] = ( # Standard library modules diff --git a/.agents/skills/lib_py/loop/runner.py b/.agents/skills/lib_py/loop/runner.py index c5d7726..2300e5d 100644 --- a/.agents/skills/lib_py/loop/runner.py +++ b/.agents/skills/lib_py/loop/runner.py @@ -464,7 +464,31 @@ class LoopRunner: if not wrapper_script.exists(): wrapper_script = Path(__file__).resolve().parent.parent / "multi-agent-mux-delegate-job" / "multi-agent-mux-delegate-job" - def _run_cmd(p_text: str): + def _run_cmd(p_text: str) -> subprocess.CompletedProcess: + # Issue #36: the prompt goes by file. One argv string is capped at + # MAX_ARG_STRLEN (131,072 bytes on Linux), and a review prompt + # carries the whole diff. The wrapper blocks until the job ends, + # so nothing reads the file after it returns. + prompt_file = "" + try: + self.mam_dir.mkdir(parents=True, exist_ok=True) + with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=self.mam_dir, + prefix=f"prompt-{self.loop_id}-", suffix=".md", + delete=False) as fh: + prompt_file = fh.name + fh.write(p_text) + except OSError as e: + # The same class the shell paths handle (merge review H1): a + # partial file is removed and the submit fails like an exec + # error would, instead of the exception reaching the loop. + if prompt_file: + try: + os.unlink(prompt_file) + except OSError: + pass + msg = (f"cannot write the prompt file under {self.mam_dir}: {e} " + f"(prompt {len(p_text.encode('utf-8'))} bytes)") + return subprocess.CompletedProcess(["bash", str(wrapper_script), "submit"], 126, "", msg) cmd = [ "bash", str(wrapper_script), "submit", "--agent-session", self.session_label(agent_session), @@ -472,12 +496,24 @@ class LoopRunner: "--type", "direct", "--role", role, "--registry-dir", str(self.jobs_dir), - "--prompt", p_text, + "--prompt-file", prompt_file, ] + self.contract_args() env = dict(os.environ) if "DELEGATE_JOB_PYTHON" not in env: env["DELEGATE_JOB_PYTHON"] = sys.executable - return subprocess.run(cmd, capture_output=True, text=True, cwd=str(self.workspace_root), env=env) + try: + return subprocess.run(cmd, capture_output=True, text=True, cwd=str(self.workspace_root), env=env) + except OSError as e: + # A wrapper that cannot start is a failed submit (126, the shell's + # "cannot execute"), on the first try and on the retry alike. + 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) + finally: + try: + os.unlink(prompt_file) + except OSError: + pass res = _run_cmd(prompt) match = re.search(r"registered job:\s*([A-Za-z0-9_-]+)", res.stdout) @@ -500,6 +536,7 @@ class LoopRunner: self.failed_submit_jobs.add(job_id_retry) log_error(f"delegate_job retry failed (exit {res_retry.returncode}):\n{res_retry.stderr or res_retry.stdout}") return job_id_retry + log_error(f"delegate_job retry failed (exit {res_retry.returncode}):\n{res_retry.stderr or res_retry.stdout}") if job_id: self.created_jobs.append(job_id) self.failed_submit_jobs.add(job_id) @@ -1327,7 +1364,11 @@ class LoopRunner: adj_prompt = f"์ž‘์—…์ž(Creator)๊ฐ€ ๊ท€ํ•˜์˜ ๋ฆฌ๋ทฐ ์ง€์  ์ค‘ ์ผ๋ถ€๋ฅผ ํƒ€๋‹นํ•˜์ง€ ์•Š๋‹ค๊ณ  ํŒ๋‹จํ•˜์—ฌ ๋ฐ˜๋ก ์„ ์ œ๊ธฐํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋ก ์„ ๊ฒ€ํ† ํ•˜๊ณ  ์žฌ์‹ฌ(re-adjudication)ํ•˜์‹ญ์‹œ์˜ค. ๋ฐ˜๋ก ์ด ํƒ€๋‹นํ•˜์—ฌ ๊ท€ํ•˜์˜ ์ง€์ ์„ ์ฒ ํšŒํ•œ๋‹ค๋ฉด ๋ฆฌํฌํŠธ ๋งˆ์ง€๋ง‰์— ๋‹จ๋… ํ–‰์œผ๋กœ '[ADJUDICATION: SUSTAINED]' ๋ฅผ, ๋ฐ˜๋ก ์ด ํƒ€๋‹นํ•˜์ง€ ์•Š์•„ ์ง€์ ์„ ์œ ์ง€ํ•œ๋‹ค๋ฉด '[ADJUDICATION: OVERRULED]' ๋ฅผ ๋ช…์‹œํ•˜์‹ญ์‹œ์˜ค. ๋ฐ˜๋ก  ๋‚ด์šฉ:\n{rebuttal_text}" adj_jid = self._dispatch(rt, rt_type, "Reviewer", adj_prompt, remote=rt in self._remote_recipients, purpose="adjudication") - if not adj_jid or not self.wait_for_job(adj_jid): + if not adj_jid: + # a job that was never registered is a failure, not a verdict (Issue #36) + log_error(f"Failed to register re-adjudication job for '{rt}'.") + return 1 + if not self.wait_for_job(adj_jid): log_warn(f"Re-adjudication job for '{rt}' failed; objection stands.") overruled_revs.append(rt) continue @@ -1346,7 +1387,10 @@ class LoopRunner: p_type = self.resolve_agent_type(planner_session, state) arb_prompt = f"์ž‘์—…์ž(Creator)์™€ ๋ฆฌ๋ทฐ์–ด์˜ ์˜๊ฒฌ์ด ์ถฉ๋Œํ•˜์—ฌ ๊ต์ฐฉ ์ƒํƒœ์ž…๋‹ˆ๋‹ค. ๊ณ„ํš์„œ ๊ด€์ ์—์„œ ์–ด๋А ์ชฝ์ด ํƒ€๋‹นํ•œ์ง€ ์žฌ์ •(arbitration)ํ•˜์‹ญ์‹œ์˜ค. ๋ฆฌํฌํŠธ ๋งˆ์ง€๋ง‰์— ๋‹จ๋… ํ–‰์œผ๋กœ '[ARBITRATION: CREATOR]' ๋˜๋Š” '[ARBITRATION: REVIEWER]' ๋ฅผ ๋ช…์‹œํ•˜์‹ญ์‹œ์˜ค. ๋ฆฌ๋ทฐ์–ด ์ง€์ :\n{feedback_aggregate}\n์ž‘์—…์ž ๋ฐ˜๋ก :\n{rebuttal_text}\n๊ธฐ์กด ๊ณ„ํš์„œ:\n{self.current_plan}" arb_jid = self._dispatch(planner_session, p_type, "Planner", arb_prompt) - if arb_jid and self.wait_for_job(arb_jid): + if not arb_jid: + log_error("Failed to register Planner arbitration job.") + return 1 + if self.wait_for_job(arb_jid): arb_file = self.find_report(arb_jid) if arb_file and parser.has_tag_line(arb_file, "ARBITRATION: CREATOR"): arbitration = "CREATOR" @@ -1360,8 +1404,9 @@ class LoopRunner: rebut_budget = 0 comply_prompt = f"๊ท€ํ•˜์˜ ๋ฐ˜๋ก ์€ ์žฌ์‹ฌ ๊ฒฐ๊ณผ ๊ธฐ๊ฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋” ์ด์ƒ ๋ฐ˜๋ก ํ•˜์ง€ ๋ง๊ณ  ๋‹ค์Œ ๋ฆฌ๋ทฐ ํ”ผ๋“œ๋ฐฑ์„ ๊ทธ๋Œ€๋กœ ๋ฐ˜์˜ํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•˜์‹ญ์‹œ์˜ค. ํ”ผ๋“œ๋ฐฑ:\n{feedback_aggregate}" comp_jid = self._dispatch(self.creator, creator_type, "Worker", comply_prompt) - if comp_jid: - self.wait_for_job(comp_jid) + if not comp_jid or not self.wait_for_job(comp_jid): + log_error("Failed to register Creator compliance job.") + return 1 # Promotion if self.plan_mode and self.current_plan and self.plan_job_id: diff --git a/.agents/skills/multi-agent-mux-create/SKILL.md b/.agents/skills/multi-agent-mux-create/SKILL.md index 427a343..17dcd6e 100644 --- a/.agents/skills/multi-agent-mux-create/SKILL.md +++ b/.agents/skills/multi-agent-mux-create/SKILL.md @@ -1,7 +1,7 @@ --- 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." -version: 4.7.2 +version: 4.7.3 author: godopu license: MIT platforms: [linux, macos] diff --git a/.agents/skills/multi-agent-mux-create/scripts/create_session.sh b/.agents/skills/multi-agent-mux-create/scripts/create_session.sh index 1494ce1..0547594 100755 --- a/.agents/skills/multi-agent-mux-create/scripts/create_session.sh +++ b/.agents/skills/multi-agent-mux-create/scripts/create_session.sh @@ -622,6 +622,19 @@ if [ -n "$DELEGATE_JOB_ID" ]; then # dispatch (trial-2 finding: without them the token-checking ack below could # never be satisfied by a real agent, and every onboarding sat out 180 s). pub="$(delegate_publish_cmd "$DELEGATE_JOB_ID" "$DELEGATE_JOB_TOKEN" "$DELEGATE_JOB_ATTEMPT")" + # Issue #36: the task goes into a brief in the job's directory and the pane + # gets only its path, as the delegate-job wrapper does. The injected text is + # one argv string to herdr (capped at 131,072 bytes), so the whole task + # plus this preamble could not reach the agent. + task_reg_dir="${MAM_JOBS_DIR:-${WORKSPACE_ROOT:-$PWD}/.mam/jobs}" + [[ "$task_reg_dir" == /* ]] || task_reg_dir="$PWD/$task_reg_dir" + task_brief="$task_reg_dir/$DELEGATE_JOB_ID/brief.md" + if ! { mkdir -p "${task_brief%/*}" && printf '%s' "$SUBMIT_JOB_PROMPT" > "$task_brief"; }; then + echo "ERROR: cannot write the task brief $task_brief" >&2 + delegate_publish_event "$DELEGATE_JOB_ID" error "task brief could not be written" "$DELEGATE_JOB_TOKEN" "$DELEGATE_JOB_ATTEMPT" + JOB_TERMINALIZED=1 + exit 1 + fi instructions="Your job_id is \"$DELEGATE_JOB_ID\" (the one just registered for THIS delegation). On start run: $pub --event started. @@ -629,7 +642,7 @@ On progress (optional): $pub --event progress --detail ''. On success run: $pub --event completed --detail ''. On failure run: $pub --event error --detail ''. -Task: $SUBMIT_JOB_PROMPT" +Task: read the complete task in $task_brief and follow it." # Inject instructions into the herdr pane rc=0 diff --git a/.agents/skills/multi-agent-mux-delegate-job/SKILL.md b/.agents/skills/multi-agent-mux-delegate-job/SKILL.md index fa32a9c..96dc942 100644 --- a/.agents/skills/multi-agent-mux-delegate-job/SKILL.md +++ b/.agents/skills/multi-agent-mux-delegate-job/SKILL.md @@ -1,7 +1,7 @@ --- 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." -version: 4.7.2 +version: 4.7.3 author: godopu license: MIT platforms: [linux, macos, windows] @@ -44,6 +44,8 @@ multi-agent-mux-delegate-job submit \ # Liveness contract (Issue #18): the agent heartbeats every N s (automatic), # the job is unresponsive after K x N s (--idle-timeout overrides), `started` # must arrive within T s of the injection or the inject fails closed. + # --prompt-file replaces --prompt for long prompts (Issue #36): one + # argv string is capped at 131,072 bytes on Linux. The loop always uses it. # 1b) Same, for an agent on another host (no herdr pane, no shared disk): # the brief travels over the broker as a signed retained dispatch and a diff --git a/.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job b/.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job index 529be41..d67ffc3 100755 --- a/.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job +++ b/.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job @@ -109,13 +109,15 @@ usage() { cat <<'EOF' multi-agent-mux-delegate-job [options] - submit --agent --prompt [--workdir ] [--agent-session