# ๐Ÿ” Review Report โ€” PR #38 ๋ณ‘ํ•ฉ ์ „ ๋…๋ฆฝ ๋ฆฌ๋ทฐ (job `107d435d`) - **Reviewer**: `planner-reviewer-claude-01` (role: planner,reviewer) ยท 2026-09-25 - **๋Œ€์ƒ**: PR #38 `fix/issue-34` **@ `ed293dc`**, base `main 7a4a0c2` (v4.7.3). 6์ปค๋ฐ‹, 11ํŒŒ์ผ `+924/โˆ’16`, `git diff --check` ํด๋ฆฐ. - **๊ฒ€์ฆ ๊ฒฝ๋กœ**: `/var/folders/.../T/mam-review/loop-20260925T055502Z-a37cf1/1`. **identity ํ™•์ธ**: `HEAD = ed293dceโ€ฆ67cf`, `HEAD^{tree} = 671b0e59โ€ฆ899c` โ€” ๋ธŒ๋ฆฌํ”„์™€ ์ผ์น˜. - **์•ˆ์ „ ๊ทœ์น™ ์ค€์ˆ˜**: ๊ฒ€์ฆ ๊ฒฝ๋กœยท์†Œ์Šค ์›ŒํฌํŠธ๋ฆฌ(`multi-agent-mux-wt/pr-38`)ยท๋ณธ ์ฒดํฌ์•„์›ƒ(`main 7a4a0c2`) ๋ชจ๋‘ ์ˆ˜์ •ยท์ปค๋ฐ‹ยทcheckout ์—†์Œ. ๋“ฑ๋ก๋ถ€ ์‹คํ—˜์€ scratchpad ์ž„์‹œ ๋“ฑ๋ก๋ถ€์—์„œ `registry.py` ๋ฅผ ์ง์ ‘ ํ˜ธ์ถœ(๋ž˜ํผ ๋ฏธ์‚ฌ์šฉ, ์‹ค์ œ herdr ์— ์žก ์ƒ์„ฑ ์—†์Œ), ๋Ÿฌ๋„ˆ ์‹คํ—˜์€ in-process(`env -u MAM_VERSION`, ์ž„์‹œ git ์ €์žฅ์†Œ), lib.sh ๋Š” ํ•จ์ˆ˜ ์ถ”์ถœ + `env -i`. ์‹ค์ œ `.mam`ยท๋ฐ๋ชฌยท์„ธ์…˜ยทherdr ์„œ๋ฒ„ ์กฐ์ž‘ ์—†์Œ, pushยทmergeยทPR ์ƒํƒœ ๋ณ€๊ฒฝ ์—†์Œ. ์ด pane ์˜ `HERDR_PANE_ID`/`HERDR_WORKSPACE_ID` ๋Š” **์ฝ๊ธฐ๋งŒ** ํ–ˆ๋‹ค. ## ๊ฒฐ๋ก  ์š”์•ฝ **๋ณ‘ํ•ฉ์„ ๋ง‰์„ ๊ฒฐํ•จ์€ ์—†๋‹ค.** ๋ถ€๋ชจ ๊ธฐ๋กยท์ž์† ๋Œ€๊ธฐยท๋ฆฌ๋ทฐ ๊ทผ๊ฑฐ ํ‘œยทstale ๊ฐ€๋“œยท๊ฐ™์€ ์„œ๋ฒ„ ๋‹ค๋ฅธ ์›Œํฌ์ŠคํŽ˜์ด์Šค ์กฐํšŒ ๊ทœ์น™์„ ๊ฐ๊ฐ ๋…๋ฆฝ ์‹คํ—˜์œผ๋กœ ํ™•์ธํ–ˆ๊ณ , ์ƒˆ ํ…Œ์ŠคํŠธ 17๊ฑด ์ค‘ **16๊ฑด์ด ์ˆ˜์ • ์ „ ์†Œ์Šค์—์„œ ์‹คํŒจ**ํ•œ๋‹ค. ๋น„์ฐจ๋‹จ ๊ด€์ฐฐ 4๊ฑด(K1โ€“K4). ๊ทธ์ค‘ K1(๊ท€์†๋˜์ง€ ์•Š์€ ์—ด๋ฆฐ ์žก๋งŒ ์žˆ์„ ๋•Œ ํŒ์ • ๊ทœ์น™ ๋ฌธ๊ตฌ๊ฐ€ ์—†์Œ)์€ ๋ธŒ๋ฆฌํ”„ ํ•ญ๋ชฉ 2ยท4 ์˜ "์ถฉ๋ถ„ํ•œ ๊ทผ๊ฑฐ" ์งˆ๋ฌธ์— ์ง์ ‘ ๋‹ฟ๋Š”๋‹ค. ## 1. ๋ถ€๋ชจ ๊ธฐ๋ก (ํ•ญ๋ชฉ 1) | ํ™•์ธ | ๊ฒฐ๊ณผ | |---|---| | `--parent-job` ๊ฒ€์ฆ | `registry.py register`(`:540-544`) ๊ฐ€ ๋“ฑ๋ก๋ถ€์— ์—†๋Š” ๋ถ€๋ชจ๋ฅผ `unknown parent job ` + rc 1 ๋กœ ๊ฑฐ๋ถ€. ๋ž˜ํผ(`:217-226`)๋Š” ๋ช…์‹œ `--parent-job` ์„ ๊ทธ๋Œ€๋กœ ๋„˜๊ฒจ **๊ฐ™์€ ๊ฒ€์ฆ**์— ๊ฑธ๋ฆฌ๊ฒŒ ํ•˜๊ณ , ๊ธฐ๋ณธ `MAM_JOB_ID` ๋Š” ์ด ๋“ฑ๋ก๋ถ€์— ๊ทธ ๋ ˆ์ฝ”๋“œ๊ฐ€ ์žˆ์„ ๋•Œ๋งŒ ์“ฐ๋ฉฐ ์—†์œผ๋ฉด `WARNING: โ€ฆ registering without a parent job` ํ›„ ๋ถ€๋ชจ ์—†์ด ๋“ฑ๋ก โ€” ๋‘ ์ธต์˜ ๊ทœ์น™์ด ์ผ์น˜ํ•œ๋‹ค | | ์ž๊ธฐ ์ฐธ์กฐ | `--job-id X --parent-job X` ๋Š” ๋“ฑ๋ก ์‹œ์ ์— X ๊ฐ€ ์—†์–ด ๊ฑฐ๋ถ€๋จ(์‹ค์ธก). `update` ์—๋Š” `--parent-job` ์ด ์—†์–ด **๋ถ€๋ชจ๋Š” ๋“ฑ๋ก ํ›„ ๋ถˆ๋ณ€** โ†’ ์ •์ƒ ๊ฒฝ๋กœ๋กœ๋Š” ์ˆœํ™˜์ด ๋งŒ๋“ค์–ด์งˆ ์ˆ˜ ์—†๋‹ค | | ์ˆœํ™˜ ๋ฐฉ์–ด | ๋ ˆ์ฝ”๋“œ ํŒŒ์ผ์„ ์ง์ ‘ ๊ณ ์ณ `root.parent = grandchild` ์ˆœํ™˜์„ ๋งŒ๋“  ๋’ค `descendants_of` ๋ฅผ ๋Œ๋ ค๋„ ์ข…๋ฃŒํ•œ๋‹ค(`seen` ์ง‘ํ•ฉ, ๋ฐฉ๋ฌธ 1ํšŒ): root ๊ธฐ์ค€ `[(child,1),(grandchild,2)]`, grandchild ๊ธฐ์ค€ `[(root,1),(child,2)]` | | `depth` | ์ €์žฅํ•˜์ง€ ์•Š๊ณ  BFS ์—์„œ ๊ณ„์‚ฐ(์ž์‹ 1, ์†์ž 2 โ€” ์‹ค์ธก) | | ๋น„์šฉ | `descendants_of` ๋Š” ํด๋ง(3 s)๋งˆ๋‹ค `list_jobs` **์ „์ฒด ์Šค์บ”** 1ํšŒ(๋ ˆ์ฝ”๋“œ ์ˆ˜ N ์˜ JSON ์ฝ๊ธฐ). `_subjobs_block` ์€ ํšŒ์ฐจ๋งˆ๋‹ค root ์ˆ˜๋งŒํผ ์Šค์บ” + `unattributed_open_jobs` 1ํšŒ. ์ˆ˜๋ฐฑ ๊ฑด์ด๋ฉด ๋ฌธ์ œ์—†๊ณ  ์ˆ˜์ฒœ ๊ฑด์ด๋ฉด 3 s ํด๋ง๋‹น ์ˆ˜์ฒœ ํŒŒ์ผ ์ฝ๊ธฐ โ€” ๋“ฑ๋ก๋ถ€ ์ •๋ฆฌ ์ •์ฑ…์ด ์—†๋Š” ์˜ค๋ž˜๋œ ์ œ์–ด ํ‰๋ฉด์—์„œ ์ฒด๊ฐ๋  ์ˆ˜ ์žˆ๋‹ค(K4) | | `MAM_JOB_TREE_MAX`(50) | ๋„˜์œผ๋ฉด `log_error` ํ›„ **๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ ** ๋ฆฌ๋ทฐ๋กœ ๊ฐ„๋‹ค. ์•ˆ์ „ํ•œ ์ชฝ์ธ๊ฐ€: ์—ด๋ฆฐ ์ž์†์€ `[ํ•˜์œ„ ์œ„์ž„ ์žก]` ํ‘œ์— OPEN ์œผ๋กœ ๋‚˜์˜ค๊ณ  SUBJOB_RULE ์ด NOT PASS ๋ฅผ ์ง€์‹œํ•˜๋ฏ€๋กœ "์กฐ์šฉํžˆ ํ†ต๊ณผ"๋Š” ์•„๋‹ˆ๋‹ค. ๋‹ค๋งŒ 51๊ฐœ ์ด์ƒ์˜ ํ–‰์ด ํ”„๋กฌํ”„ํŠธ์— ๋ถ™๋Š”๋‹ค | ## 2. ๋ถ€๋ชจ ID ์ „๋‹ฌ (ํ•ญ๋ชฉ 2) - ๋กœ์ปฌ ์—์ด์ „ํŠธ: ๋ž˜ํผ๊ฐ€ ์ฃผ์ž…๋ฌธ ๋(`_instructions`, test18 ๋ฌธ๊ตฌ ๋ณด์กด)๊ณผ `brief.md` ์˜ `Parent for sub-delegations` ์ค„(`:251`, `:427`), create ์˜ ์˜จ๋ณด๋”ฉ ์ฃผ์ž…๋ฌธ(`create_session.sh:646`)์œผ๋กœ ์•Œ๋ฆฐ๋‹ค. ์—์ด์ „ํŠธ๊ฐ€ ๋ถ™์ด์ง€ ์•Š์œผ๋ฉด ์ถ”์ ์ด ๋น ์ง€๋Š” ๊ฒƒ์€ ์‚ฌ์‹ค์ด๊ณ , ์•ˆ์ „๋ง์€ ๋‘ ๊ฐ€์ง€๋‹ค: (a) `[๊ท€์†๋˜์ง€ ์•Š์€ ์—ด๋ฆฐ ์žก]` ์ค„ โ€” `unattributed_open_jobs` ๊ฐ€ "๋ถ€๋ชจ ์—†์Œ + pending/running + ์ด ๋ฃจํ”„๊ฐ€ ๋งŒ๋“  ์žก ์ œ์™ธ + creator ์žก ๋“ฑ๋ก ์ดํ›„" ๋กœ ๊ณ ๋ฅธ๋‹ค(์‹ค์ธก: ๋ฃจํ”„ ์ž์‹ ์˜ ๋’ค์ด์€ ์žก์€ ์ œ์™ธ, ๋‚จ์˜ root ์žก์€ ํ‘œ์‹œ); (b) `--wait-open-jobs` ๋กœ ๊ทธ ์žก๋“ค๊นŒ์ง€ ๋Œ€๊ธฐ(๊ธฐ๋ณธ off โ€” ๊ฐ™์€ ์ œ์–ด ํ‰๋ฉด์˜ ๋ฌด๊ด€ํ•œ ์žก์ด ๋ฃจํ”„๋ฅผ ์žก์„ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ; ๋ฌธ์„œ์™€ ์ผ์น˜). - **K1 (๋น„์ฐจ๋‹จ, ๊ทผ๊ฑฐ ์ถฉ๋ถ„์„ฑ)**: ํ•˜์œ„ ์žก์ด ํ•˜๋‚˜๋„ ์—†๊ณ  ๊ท€์†๋˜์ง€ ์•Š์€ ์—ด๋ฆฐ ์žก๋งŒ ์žˆ์„ ๋•Œ ๋ธ”๋ก์€ `[ํ•˜์œ„ ์œ„์ž„ ์žก] ์—†์Œ โ€ฆ [๊ท€์†๋˜์ง€ ์•Š์€ ์—ด๋ฆฐ ์žก]: (โ€ฆ)` ๋งŒ ๋‹ด๊ณ  **SUBJOB_RULE ์„ ๋ถ™์ด์ง€ ์•Š๋Š”๋‹ค**(`_subjobs_block` ์˜ `if not rows:` ๋ถ„๊ธฐ โ€” ์‹ค์ธก: `has rule: False`). SUBJOB_RULE ์ž์ฒด๋„ "ํ•˜์œ„ ์žก"๋งŒ ๋งํ•˜๊ณ  ๊ท€์†๋˜์ง€ ์•Š์€ ์žก์„ ์–ด๋–ป๊ฒŒ ๋ณผ์ง€๋Š” ๋งํ•˜์ง€ ์•Š๋Š”๋‹ค. ์ฆ‰ "creator ๊ฐ€ `--parent-job` ์„ ๋น ๋œจ๋ฆฐ" ๋ฐ”๋กœ ๊ทธ ๊ฒฝ์šฐ์— ๋ฆฌ๋ทฐ์–ด๋Š” ์‚ฌ์‹ค์€ ๋ฐ›์ง€๋งŒ ์ง€์‹œ๋Š” ๋ฐ›์ง€ ๋ชปํ•œ๋‹ค. ํ•œ ๋ฌธ์žฅ("๊ท€์†๋˜์ง€ ์•Š์€ ์—ด๋ฆฐ ์žก์ด ์ด ์ž‘์—…์˜ ํ•˜์œ„ ์œ„์ž„์œผ๋กœ ๋ณด์ด๋ฉด OPEN ํ•˜์œ„ ์žก๊ณผ ๊ฐ™์ด ์ทจ๊ธ‰")์„ stray ์ค„ ๋’ค์— ๋ถ™์ด๋ฉด ๋‹ซํžŒ๋‹ค. - ์›๊ฒฉ ๋Ÿฌ๋„ˆ: `MAM_JOB_ID` ๊ฐ€ ์ž๋™ ๋ถ€๋ชจ๊ฐ€ ๋˜์ง€๋งŒ ์›๊ฒฉ ํ˜ธ์ŠคํŠธ์˜ ๋“ฑ๋ก๋ถ€์—๋Š” ๊ทธ ์žก์ด ์—†์„ ์ˆ˜ ์žˆ์–ด "์žˆ์„ ๋•Œ๋งŒ" ๊ทœ์น™์ด ๋งž๋‹ค(๋ž˜ํผ ์ฃผ์„๊ณผ ์ผ์น˜). ## 3. ์ž์† ๋Œ€๊ธฐ (ํ•ญ๋ชฉ 3) - `wait_for_job_tree` ๋Š” ์‹คํ–‰(`:1291`)ยท์ˆ˜์ •(`:1520`)ยท์ˆœ์‘(`:1591`) ์„ธ creator ์žก ๋ชจ๋‘์— ๊ฑธ๋ฆฌ๊ณ , `round_roots` ์— ๋ˆ„์ ๋œ๋‹ค. - `MAM_CHILD_WAIT_S`(3900) ์˜ ๊ธฐ์ค€์ ์€ `wait_for_descendants` ์ง„์ž… ์‹œ๊ฐ = **creator ์žก ์ข…๊ฒฐ ์‹œ์ **(๋ฌธ์„œ์™€ ์ผ์น˜). ์ดˆ๊ณผ ์‹œ `log_warn` ํ›„ False ๋ฅผ ๋Œ๋ ค์ฃผ์ง€๋งŒ `wait_for_job_tree` ๋Š” root ์˜ ๊ฒฐ๊ณผ๋งŒ ๋ฐ˜ํ™˜ํ•˜๋ฏ€๋กœ ๋ฃจํ”„๋Š” ๋ฆฌ๋ทฐ๋กœ ์ง„ํ–‰ํ•˜๊ณ  ์—ด๋ฆฐ ์žก์€ ํ‘œ์— OPEN ์œผ๋กœ ๋‚จ๋Š”๋‹ค(์„ค๊ณ„๋Œ€๋กœ). - `interrupted`: ์‹œ๊ทธ๋„ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์ฒดํฌํฌ์ธํŠธ ์ €์žฅ ํ›„ `sys.exit(128+signum)` ํ•˜๋ฏ€๋กœ(`_handle_signal`) ํด๋ง ๋ฃจํ”„์˜ `if self.interrupted: return False` ๋Š” ์‹ค์ œ๋กœ๋Š” ๋‹ฟ์ง€ ์•Š๋Š” ๋ฐฉ์–ด ์ฝ”๋“œ๋‹ค โ€” ๋ฌดํ•ด. - ๋ถ„๋ฅ˜(`_subjob_state`)์™€ `get_feedback` ์˜ ์‹ค์ œ ๊ฐ’: ๋ž˜ํผ๋Š” ๋งˆ๊ฐ ์‹œ `close --request deadline_timeout` ๋’ค `publish_event --event error --detail deadline_timeout`(inject ๋‹จ๊ณ„๋ฉด `agent_blocked`)์„ ๋‚จ๊ธฐ๊ณ (`:928-930`), preflight/idle ์‹คํŒจ๋Š” `cancelled --detail session_busy` ๋˜๋Š” ์ƒˆ `target_not_found`(`:896`, `:952`), create ๋กค๋ฐฑ์€ `cancelled "rolled back by create_session"` ์ด๋‹ค. `get_feedback` ์€ ๋งˆ์ง€๋ง‰ `completed/error`(๊ฐ์‚ฌ ๋กœ๊ทธ) ๋˜๋Š” events.log ์˜ detail ์„ ๋Œ๋ ค์ฃผ๋ฏ€๋กœ `ORCHESTRATOR_CLOSED` ํŠœํ”Œยท`startswith("rolled back")`ยท`session_busy|target_not_found` ๊ฐ€ ์ •ํ™•ํžˆ ๊ทธ ๋ฌธ์ž์—ด๋“ค๊ณผ ๋งž๋Š”๋‹ค. ์‚ฌ์œ  ์—†๋Š” `error` ๋ฅผ CLOSED-UNCONFIRMED ๋กœ ๋ณด๋Š” ๊ทœ์น™์€ "์—์ด์ „ํŠธ ์ž์‹ ์˜ ๋ณด๊ณ ๊ฐ€ ์•„๋‹ˆ๋‹ค" ๋Š” ๋œป์—์„œ ๋ณด์ˆ˜์ ์ด๋ฉฐ ์˜ณ๋‹ค. ## 4. ๋ฆฌ๋ทฐ์–ด ๊ทผ๊ฑฐ (ํ•ญ๋ชฉ 4) in-process ์‹ค์ธก(`_subjobs_block`): | ์ƒํ™ฉ | ๊ฒฐ๊ณผ | |---|---| | ํ•˜์œ„ ์žก ์—†์Œยทstray ์—†์Œ | **`''`** โ€” ํ”„๋กฌํ”„ํŠธ ๋ฐ”์ดํŠธ ๋™์ผ | | ์ด ๋ฃจํ”„๊ฐ€ ๋’ค์— ๋งŒ๋“  ์žก(`created_jobs`)๋งŒ ์กด์žฌ | `''` โ€” ๋ฆฌ๋ทฐ์–ด ์ž์‹ ์˜ ์žกยท์•ž ํšŒ์ฐจ ๋ฆฌ๋ทฐ ์žก์€ stray ๋กœ ์˜ค์ธ๋˜์ง€ ์•Š๋Š”๋‹ค(ํšŒ์ฐจ์˜ ๋ธ”๋ก์€ ๋ฆฌ๋ทฐ ์žก ์œ„์ž„ **์ „์—** ๊ณ„์‚ฐ๋œ๋‹ค) | | ๋‚จ์˜ root ์žก(๋ถ€๋ชจ ์—†์Œ, creator ์žก ์ดํ›„ ๋“ฑ๋ก) | ๋ธ”๋ก ์ƒ์„ฑ, stray ์ค„์— id ํ‘œ์‹œ, **๊ทœ์น™ ๋ฌธ๊ตฌ ์—†์Œ**(K1) | | pending ์ž์‹ 1๊ฐœ | ํ‘œ(`\| OPEN \| - \| 1 \|`) + ์ง‘๊ณ„ + SUBJOB_RULE | `descendants_of` ๊ฐ€ `depth` ๋ฅผ ์ฃผ๊ณ  `_subjob_state` ๊ฐ€ ์ƒํƒœยท์‚ฌ์œ ๋ฅผ ์ฃผ๋ฏ€๋กœ ํ‘œ์˜ ์—ด์€ ์ •ํ™•ํ•˜๋‹ค. ํ‘œ๋Š” ์•ž ํšŒ์ฐจ creator ์žก์˜ ์ž์†๊นŒ์ง€ ๋งค ํšŒ์ฐจ ๋‹ค์‹œ ์ฝ๋Š”๋‹ค(`round_roots` ๋ˆ„์ , T9). ## 5. stale ๊ฐ€๋“œ โ€” ์˜ต์…˜ ์—†๋Š” ๋ฃจํ”„ (ํ•ญ๋ชฉ 5) `_fingerprint`(`runner.py:954-972`)๋Š” `head` ์™€, `.mam`(`mam_dir`)ยท`jobs_dir`ยท๊ฐ์‚ฌ ๋กœ๊ทธ ๋””๋ ‰ํ„ฐ๋ฆฌ(`DELEGATE_JOB_LOGS_DIR`, ์ƒ๋Œ€๋ฉด `workspace_root` ๊ธฐ์ค€์œผ๋กœ ๊ฒฐํ•ฉ)๋ฅผ `git add -A -- . :(exclude)` ๋กœ ๋บ€ tree ๋‹ค. in-process ์‹ค์ธก(์ž„์‹œ git ์ €์žฅ์†Œ, **`.gitignore` ์—†์Œ** โ€” ์ฆ‰ `.mam` ์ด ๋ฌด์‹œ๋˜์ง€ ์•Š๋Š” ์ตœ์•… ์กฐ๊ฑด): | ์ƒํ™ฉ | ๊ฒฐ๊ณผ | |---|---| | `DELEGATE_JOB_LOGS_DIR=.mam/delegate_job_logs`(์ƒ๋Œ€) ๋กœ ๊ฐ์‚ฌ ๋กœ๊ทธยท`jobs/*.json` ์„ ์ƒˆ๋กœ ์“ด ๋’ค | ์ง€๋ฌธ **๋ถˆ๋ณ€** โ€” ์ œ์–ด ํ‰๋ฉด ์“ฐ๊ธฐ๋Š” ์ œ์™ธ๋œ๋‹ค | | ์†Œ์Šค์— untracked ํŒŒ์ผ 1๊ฐœ ์ถ”๊ฐ€ | ์ง€๋ฌธ **๋ณ€๊ฒฝ** | | ๊ฐ์‚ฌ ๋กœ๊ทธ ๋””๋ ‰ํ„ฐ๋ฆฌ๊ฐ€ ์†Œ์Šค **๋ฐ–**(์ ˆ๋Œ€๊ฒฝ๋กœ) | `relative_to` ๊ฐ€ `ValueError` โ†’ ์ œ์™ธ ๋ชฉ๋ก์—์„œ ๋น ์ง€๊ณ  ์ง€๋ฌธ ๊ณ„์‚ฐ์€ ์ •์ƒ | - **์ผœ์ง€๋Š” ์กฐ๊ฑด**: `light = self._fingerprint()` ๋Š” `not identity and subjobs`(`:1350-1352`) โ€” ํ•˜์œ„ ์žก ํ‘œ๋‚˜ stray ์ค„์ด ์žˆ์„ ๋•Œ๋งŒ์ด๋‹ค(1d4f338). ํ•˜์œ„ ์œ„์ž„ ์—†๋Š” ๋ฃจํ”„๋Š” ์˜› ๋™์ž‘ ๊ทธ๋Œ€๋กœ๋ผ `test_tier4_e2e.py::test_e2e_scenario5` ๋ฅ˜(์†Œ์Šค์— ๋งค ์žก ๋ฐ”๋€Œ๋Š” untracked ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ)๊ฐ€ ๋‹ค์‹œ ์‚ด์•„๋‚œ๋‹ค โ€” ์ด๋ฒˆ ์‹คํ–‰์—์„œ tier4 ํฌํ•จ ํ†ต๊ณผ(ยง7). - **์ ์šฉ ์ง€์ **: ์…€ํ”„ ๋ฆฌ๋ทฐ ๋’ค(`:1368`), ๋ฆฌ๋ทฐ ํŒ์ • ์ˆ˜์ง‘ ๋’ค(`:1463`, identity ๋ชจ๋“œ์˜ B7 ๊ณผ `elif` ๋กœ ๋ฐฐํƒ€), ์Šน๊ฒฉ ์ง์ „(`:1595`, identity ๋ชจ๋“œ์˜ B10 ์€ `:1613` ๋กœ ๋ณ„๋„). identity ๋ชจ๋“œ์—์„œ๋Š” `light` ๊ฐ€ `None` ์œผ๋กœ ๋‚จ์•„ **์ด์ค‘ ์ ์šฉ์ด ์—†๋‹ค**. - ์ง€๋ฌธ์„ ๋งŒ๋“ค ์ˆ˜ ์—†์œผ๋ฉด(์˜ˆ: dirty ์„œ๋ธŒ๋ชจ๋“ˆ) ๊ฒฝ๊ณ  1ํšŒ ํ›„ ๊ฐ€๋“œ off โ€” ์˜ต์…˜ ์—†๋Š” ๋ชจ๋“œ์˜ ๊ธฐ์กด ๋™์ž‘์œผ๋กœ ๋˜๋Œ์•„๊ฐ€๋Š” ๊ฒƒ์ด๋ผ ์ˆ˜์šฉ ๊ฐ€๋Šฅํ•˜๋‹ค. ## 6. ๊ฐ™์€ ์„œ๋ฒ„ ๋‹ค๋ฅธ ์›Œํฌ์ŠคํŽ˜์ด์Šค ์œ„์ž„ (ํ•ญ๋ชฉ 6) `_herdr_scope_is_pane_inherited`(`lib.sh:1464-1473`)์™€ `mam_herdr` ์˜ `elif`(`:1485-1486`)๋ฅผ ํ•จ์ˆ˜ ์ถ”์ถœ + `env -i` ๋กœ 10๊ฐ€์ง€ ์กฐํ•ฉ์— ๋Œ๋ ธ๋‹ค(์ „๋ถ€ ์„ค๊ณ„์™€ ์ผ์น˜): | ํ˜ธ์ถœ | ํ™˜๊ฒฝ | ๊ฒฐ๊ณผ | |---|---|---| | `agent`/`has-session`/`kill-session`(`-L` ์ ‘๋‘ ํฌํ•จ) | `HERDR_WORKSPACE_ID == HERDR_PANE_ID` ์˜ ์›Œํฌ์ŠคํŽ˜์ด์Šค, ๊ฐ™์€ ์„œ๋ฒ„ | **WS id ๋งŒ ์ œ๊ฑฐ**(paneยทtab id ์œ ์ง€) | | `new-session`, `pane split` | ๋™์ผ | **์œ ์ง€** โ€” pane ๋ฐฐ์น˜ยท์›Œํฌ์ŠคํŽ˜์ด์Šค ํŒจํ‚น(#7) ๋ถˆ๋ณ€ | | `agent` | WS id โ‰  pane ์˜ ์›Œํฌ์ŠคํŽ˜์ด์Šค(์–ด๊ธ‹๋‚œ ์Šค์ฝ”ํ”„) | ์œ ์ง€(์˜๋„๋œ ์Šค์ฝ”ํ”„) | | `agent` | pane id ์—†์Œ(pane ๋ฐ– ๋ช…์‹œ ์Šค์ฝ”ํ”„) | ์œ ์ง€ | | `agent` | ๊ธฐ๋ณธ ์„œ๋ฒ„ pane โ†’ ์ด๋ฆ„ ์žˆ๋Š” ์„œ๋ฒ„ | **CROSS-all3** โ€” #23 ๊ทœ์น™์ด `if` ๋กœ ๋จผ์ € ์žก์•„ ์„ธ id ๋ชจ๋‘ ์ œ๊ฑฐ, ์ด ๊ทœ์น™์€ `elif` ๋ผ ๋‹ฟ์ง€ ์•Š๋Š”๋‹ค(์ถฉ๋Œ ์—†์Œ) | | `agent` | ๋Œ€์ƒ ๋ฏธ์ง€์ •(์•ฐ๋น„์–ธํŠธ) | WS id ๋งŒ ์ œ๊ฑฐ(๊ฐ™์€ ์„œ๋ฒ„์ด๋ฏ€๋กœ ๊ฐ™์€ ์ทจ๊ธ‰) | - ์ด pane ์˜ ์‹ค์ œ ๊ฐ’๋„ ๊ทœ์น™์˜ ์ „์ œ(`HERDR_PANE_ID = ":"`, `HERDR_WORKSPACE_ID = `)์™€ ๋งž์•˜๋‹ค(๊ฐ’์€ ์ฝ๊ธฐ๋งŒ). - ์ด๋ฆ„ ์œ ์ผ์„ฑ ๊ทผ๊ฑฐ(herdr `--skill` ๋ฌธ๊ตฌ)๋Š” ์ฝ”๋“œ ์ฃผ์„์— ์ธ์šฉ๋ผ ์žˆ๊ณ , T10/T11 ์ด ๋‘ ๋ฐฉํ–ฅ์„ ๊ณ ์ •ํ•œ๋‹ค. - `target_not_found`: preflight ์˜ `has-session` ์‹คํŒจ๊ฐ€ `PREFLIGHT_REASON` ์„ ์„ธ์šฐ๊ณ (`:952`) `finalize_unfinished` ๊ฐ€ `cancelled --detail target_not_found` ๋กœ ๋‹ซ๋Š”๋‹ค(`:896`). **rc ๋Š” ์ข…์ „๊ณผ ๊ฐ™์€ 5** ๋‹ค(T12 ๊ฐ€ `returncode == 5` ๋ฅผ ๊ณ ์ •) โ€” ์ฆ‰ ์ด๋ฒคํŠธ detail ์€ ์ •ํ™•ํ•ด์กŒ์ง€๋งŒ ์ข…๋ฃŒ ์ฝ”๋“œ๋Š” "busy" ์™€ ๊ฐ™์•„์„œ, ๋ฃจํ”„์˜ busy 1ํšŒ ์žฌ์‹œ๋„(`MAM_SESSION_BUSY_RETRY_S`)๊ฐ€ ์—†๋Š” ๋Œ€์ƒ์—๋„ ํ•œ ๋ฒˆ ๋” ๋Œ์•„๊ฐ„๋‹ค(K3). ## 7. ์ƒˆ ํ…Œ์ŠคํŠธ (ํ•ญ๋ชฉ 7) - **์‹ค์ œ ๊ฒฝ๋กœ**: T1ยทT12 ๋Š” ์‹ค์ œ ๋ž˜ํผ(`--agent human`, ๋„๋‹ฌ ๋ถˆ๊ฐ€ ๋ธŒ๋กœ์ปค)์™€ `registry.py` ๋กœ ๋“ฑ๋กยท์ข…๊ฒฐํ•œ๋‹ค. T3~T9 ๋Š” `python -m lib_py.loop` ๋ฅผ ํ•˜์œ„ ํ”„๋กœ์„ธ์Šค๋กœ ๋„์šฐ๊ณ  ๊ฐ€์งœ ๋ž˜ํผ๊ฐ€ **์‹ค์ œ ๋ž˜ํผ**(`FAKE_REAL_WRAPPER`)๋กœ ํ•˜์œ„ ์žก์„ ๋“ฑ๋กํ•ด ์ž์† ๋Œ€๊ธฐยทํ‘œยท๊ฐ€๋“œ๋ฅผ ํ†ต๊ณผ์‹œํ‚จ๋‹ค. T10~T12 ๋Š” mock herdr ์œ„์˜ ์ƒŒ๋“œ๋ฐ•์Šค. `HOME`ยท`WORKSPACE_ROOT`ยท`DELEGATE_JOB_LOGS_DIR` ๋Š” `tmp_path`. - **์ˆ˜์ • ์—†์ด๋Š” ์‹คํŒจํ•˜๋Š”๊ฐ€ โ€” ํ™•์ธํ•จ**: ๊ฒ€์ฆ ๊ฒฝ๋กœ ์‚ฌ๋ณธ์—์„œ PR ์ด ๋ฐ”๊พผ ์†Œ์Šค 6ํŒŒ์ผ์„ `7a4a0c2` ํŒ์œผ๋กœ ๋˜๋Œ๋ฆฌ๊ณ  ์ƒˆ ํŒŒ์ผ์„ ๋Œ๋ฆฐ ๊ฒฐ๊ณผ **16 failed / 1 passed**(16.7 s). ํ†ต๊ณผ 1๊ฑด์€ T11("ํ˜ธ์ถœ์ž๊ฐ€ ๊ณ ๋ฅธ ์›Œํฌ์ŠคํŽ˜์ด์Šค id ๋Š” ์—ฌ์ „ํžˆ ์Šค์ฝ”ํ”„") โ€” ์ •์ • ์ „์—๋„ ์„ฑ๋ฆฝํ•ด์•ผ ํ•˜๋Š” ๋ถˆ๋ณ€ ์กฐ๊ฑด์ด๋‹ค. - **๊ฒ€์ฆ ๊ฒฝ๋กœ์—์„œ์˜ ์‹คํ–‰** (9ํŒŒ์ผ: ์ƒˆ ํŒŒ์ผ + issue5 phase1ยทtier4 e2eยทissue21 loop B/acceptanceยทissue23/24ยทloop_cliยทissue25ยทherdr shim contract): ``` โ†’ 1 failed, 243 passed in 1460.19s (0:24:20) FAILED tests/test_issue23_24_startup_path.py::test_s7c_a_started_that_was_followed_by_a_terminal_status_still_counts ``` - ์ƒˆ ํŒŒ์ผ 17๊ฑด **์ „๋ถ€ ํ†ต๊ณผ**, `test_tier4_e2e.py::test_e2e_scenario5`(๋ธŒ๋ฆฌํ”„ ํ•ญ๋ชฉ 5 ์˜ ํšŒ๊ท€) **ํ†ต๊ณผ**(873 s โ€” ์ด ํ˜ธ์ŠคํŠธ์—์„œ ๋‹ค๋ฅธ ์Šค์œ„ํŠธ๊ฐ€ ๋™์‹œ์— ๋Œ์•„ load avg 2.5 ์˜€๋‹ค), registry CLI ์Šค๋ƒ…์ƒทยทhelp ์บก์ฒ˜(issue5 phase1) ํ†ต๊ณผ โ†’ `--parent-job` ์ถ”๊ฐ€๊ฐ€ ๊ธฐ์กด CLI ๊ณ„์•ฝ์„ ๊นจ์ง€ ์•Š๋Š”๋‹ค. - **์‹คํŒจ 1๊ฑด์€ ๋ถ€ํ•˜ flake** ๋กœ ํŒ์ •ํ•œ๋‹ค: s7c ๋Š” create/registry ์˜ `started` ์ฆ๊ฑฐ ์‹œํ—˜์ด๋ฉฐ PR ์€ ๊ทธ ๊ฒฝ๋กœ๋ฅผ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์•˜๋‹ค(create ๋Š” ์ฃผ์ž…๋ฌธ์— ๋ฌธ์žฅ ํ•˜๋‚˜ ์ถ”๊ฐ€๋ฟ). ํ•ฉ๋™ ์‹คํ–‰์—์„œ 40 s ๊ฑธ๋ ค `_seed_job` ๋‹จ๊ณ„(`--ack-timeout 2`)์—์„œ ์‹คํŒจํ–ˆ๊ณ , **๋‹จ๋… ์žฌ์‹คํ–‰์—์„œ๋Š” s7b ์™€ ํ•จ๊ป˜ 2 passed in 10.8 s** ๋กœ ํ†ต๊ณผํ–ˆ๋‹ค. - **๊ฒฉ๋ฆฌ**: ์‹ค์ œ `.mam/jobs`ยท`.mam/delegate_job_logs` ์ „ํ›„ **์ฐจ์ด 0 / ์ฐจ์ด 0**. ๊ฒ€์ฆ ๊ฒฝ๋กœ tracked ํŒŒ์ผ ๋ณ€ํ™” ์—†์Œ(HEAD `ed293dc`), ๋ณธ ์ฒดํฌ์•„์›ƒ `main 7a4a0c2`. - **์‹คํ–‰ ์‹œ๊ฐ„**: ์ƒˆ ํŒŒ์ผ ์ž์ฒด๋Š” ๋˜๋Œ๋ฆฐ ์‚ฌ๋ณธ ๊ธฐ์ค€ 17 s ์•ˆํŒŽ์ด๊ณ , ํ•ฉ๋™ ์‹คํ–‰์˜ 24 ๋ถ„์€ tier4 e2e(์•ฝ 17 ๋ถ„)์™€ ํ˜ธ์ŠคํŠธ ๋ถ€ํ•˜ ๋ชซ์ด๋‹ค. ## 8. ๋ฌธ์„œ (ํ•ญ๋ชฉ 8) - loop SKILL.md `:214`(`--wait-open-jobs` ํ‘œ), `:221`(ํ•˜์œ„ ์œ„์ž„ ์žก: ๋Œ€๊ธฐ ๋ฒ”์œ„ยท`MAM_CHILD_WAIT_S` ๊ธฐ์ค€์ ยท`MAM_JOB_TREE_MAX`ยทํ‘œ/๊ทœ์น™ยทํšŒ์ฐจ ๋ˆ„์ ), `:223`(stale ๊ฐ€๋“œ) โ€” ์ฝ”๋“œ์™€ ์ผ์น˜ํ•œ๋‹ค. - delegate-job SKILL.md `:49-53`ยทBest Practices, MULTI_AGENT_RULES(ko/en) `:154` โ€” `--parent-job` ์˜๋ฌด์™€ ๊ธฐ๋ณธ๊ฐ’ยท๊ฑฐ๋ถ€ ๊ทœ์น™์ด ์„œ์ˆ ๋ผ ์žˆ๋‹ค. - ๋ž˜ํผ usage ์™€ `brief.md` ์˜ `Parent for sub-delegations` ์ค„, create ์˜จ๋ณด๋”ฉ ์ฃผ์ž…๋ฌธ โ€” ๋ชจ๋‘ ๊ฐ™์€ ๋ฌธ์žฅ์ด๋‹ค. ## 9. ๋ณ‘ํ•ฉ ์ฐจ๋‹จ ์—ฌ๋ถ€ (ํ•ญ๋ชฉ 9) ๋ฒ„์ „ ๋ฝ์Šคํ… bump ๋ถ€์žฌ(`lib.sh:32` `4.7.3`, ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ๊ฐ€ 4.7.4 ๋กœ ์ฒ˜๋ฆฌ ์˜ˆ์ •) ์™ธ์— **๋ณ‘ํ•ฉ์„ ๋ง‰์„ ๊ฒฐํ•จ์€ ์—†๋‹ค**. | # | ๋น„์ฐจ๋‹จ ๊ด€์ฐฐ | ๊ทผ๊ฑฐ / ๊ถŒ๊ณ  | |---|---|---| | K1 | ๊ท€์†๋˜์ง€ ์•Š์€ ์—ด๋ฆฐ ์žก**๋งŒ** ์žˆ์„ ๋•Œ ๋ธ”๋ก์— SUBJOB_RULE ์ด ์—†๊ณ , ๊ทœ์น™ ๋ฌธ๊ตฌ๋„ ๊ทธ ์žก์„ ์–ธ๊ธ‰ํ•˜์ง€ ์•Š๋Š”๋‹ค โ€” `--parent-job` ์„ ๋น ๋œจ๋ฆฐ ๋ฐ”๋กœ ๊ทธ ๊ฒฝ์šฐ์— ๋ฆฌ๋ทฐ์–ด๊ฐ€ ์ง€์‹œ ์—†์ด ์‚ฌ์‹ค๋งŒ ๋ฐ›๋Š”๋‹ค | `runner.py:697-724` ์˜ `if not rows:` ๋ถ„๊ธฐ, ์‹ค์ธก `has rule: False`. stray ์ค„ ๋’ค์— ํ•œ ๋ฌธ์žฅ ๊ถŒ๊ณ  | | K2 | `MAM_JOB_TREE_MAX` ์ดˆ๊ณผ ์‹œ ๋Œ€๊ธฐ๋ฅผ ๊ฑด๋„ˆ๋›ฐ๊ณ  ๋ฆฌ๋ทฐ๋กœ ๊ฐ„๋‹ค โ€” ํ‘œ์˜ OPEN ํ–‰๊ณผ ๊ทœ์น™์ด NOT PASS ๋ฅผ ์œ ๋„ํ•˜๋ฏ€๋กœ ์กฐ์šฉํ•œ ํ†ต๊ณผ๋Š” ์•„๋‹ˆ์ง€๋งŒ, 50+ ํ–‰์ด ํ”„๋กฌํ”„ํŠธ์— ๋ถ™๋Š”๋‹ค | `:646-654`. ์ •๋ณด | | K3 | `target_not_found` ๋Š” ์ด๋ฒคํŠธ detail ๋งŒ ๋ฐ”๋€Œ๊ณ  ๋ž˜ํผ rc ๋Š” `session_busy` ์™€ ๊ฐ™์€ **5** ๋ผ ๋ฃจํ”„์˜ busy 1ํšŒ ์žฌ์‹œ๋„๊ฐ€ ์—†๋Š” ๋Œ€์ƒ์—๋„ ๋ˆ๋‹ค | `:896`, `:952`, T12 `returncode == 5`. rc ๋ถ„๋ฆฌ ๊ถŒ๊ณ (ํ›„์†) | | K4 | `descendants_of`/`unattributed_open_jobs` ๋Š” ํด๋ง(3 s)๋งˆ๋‹ค ๋“ฑ๋ก๋ถ€ ์ „์ฒด JSON ์„ ์ฝ๋Š”๋‹ค โ€” ์ˆ˜์ฒœ ๊ฑด ๋ˆ„์ ๋œ ์ œ์–ด ํ‰๋ฉด์—์„œ ์ฒด๊ฐ ๊ฐ€๋Šฅ | `registry.py:259-282`. ๋“ฑ๋ก๋ถ€ ์ •๋ฆฌ ์ •์ฑ… ๋˜๋Š” ์ธ๋ฑ์Šค๊ฐ€ ์ƒ๊ธฐ๊ธฐ ์ „๊นŒ์ง€๋Š” ์ •๋ณด | ## 10. ๊ต์ฐจ๊ฒ€์ฆ ๊ฐ€์ด๋“œ ```bash RP=/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20260925T055502Z-a37cf1/1 PY=/Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python git -C "$RP" rev-parse HEAD 'HEAD^{tree}' # ed293dcโ€ฆ / 671b0e5โ€ฆ # ๋“ฑ๋ก๋ถ€: ๋ถ€๋ชจ ๊ธฐ๋กยทlist --parentยท๋ฏธ์ง€ ๋ถ€๋ชจ/์ž๊ธฐ ์ฐธ์กฐ ๊ฑฐ๋ถ€ (์ž„์‹œ ๋“ฑ๋ก๋ถ€, registry.py ์ง์ ‘) # descendants_of: ์†์ž depth 2, ๋ ˆ์ฝ”๋“œ ํŒŒ์ผ๋กœ ์ˆœํ™˜์„ ๋งŒ๋“ค์–ด๋„ ์ข…๋ฃŒ # ๋Ÿฌ๋„ˆ in-process (env -u MAM_VERSION): _subjobs_block '' / stray / child ํ‘œ, _fingerprint ์˜ .mam ์ œ์™ธ # lib.sh: _herdr_scope_is_pane_inherited 10์ผ€์ด์Šค (ยง6) # ๋˜๋Œ๋ฆฐ ์‚ฌ๋ณธ: ์†Œ์Šค 6ํŒŒ์ผ์„ 7a4a0c2 ํŒ์œผ๋กœ โ†’ tests/test_issue34_job_tree.py โ†’ 16 failed / 1 passed (cd "$RP" && AGENT_PYTHON_BIN="$PY" PYTHONDONTWRITEBYTECODE=1 "$PY" -m pytest -p no:cacheprovider -q --basetemp=/tmp/bt \ tests/test_issue23_24_startup_path.py::test_s7c_a_started_that_was_followed_by_a_terminal_status_still_counts) # ๋‹จ๋… ํ†ต๊ณผ ``` [VERDICT: PASS]