# ๐Ÿ” Review Report โ€” Job 47a845e0 (Branch Review Round 2.2 โ€” fix/issue-9-14, HEAD @ ebe58f6) - **Reviewer**: `reviewer-agy-01` (role: reviewer) ยท 2026-09-13 - **๋Œ€์ƒ ๋ธŒ๋žœ์น˜**: `fix/issue-9-14` (base: `main` @ `4d444ce`) - **๊ฒ€ํ†  ์ปค๋ฐ‹ ๋ฒ”์œ„**: `497633f` ~ `ebe58f6` (์ด 9๊ฐœ ์ปค๋ฐ‹: `497633f`, `16376c5`, `4b31345`, `eaa49aa`, `0918917`, `7c40bf4`, `d5a191c`, `952657e`, `ebe58f6`) - **์‚ฐ์ถœ๋ฌผ ๊ฒฝ๋กœ**: `.mam/jobs/47a845e0/agy-reports/report-final.md` - **๊ฒ€ํ†  ์›์น™**: ๋ธŒ๋žœ์น˜ ์ „์ฒด ๊ธฐ๋Šฅ ์ •ํ•ฉ์„ฑ, ์ตœ์‹  ์ˆ˜์ •(`ebe58f6`) ๋ฌด๊ฒฐ์„ฑ ๊ฒ€์ฆ, 60์ดˆ ์ฃผ๊ธฐ heartbeat ๊ทœ์น™ ์ค€์ˆ˜, ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ ์ „์ˆ˜ ์‹คํ–‰, ์ฝ”๋“œ ์ˆ˜์ • ๊ธˆ์ง€. --- ## 1. ์ตœ์‹  ์ปค๋ฐ‹ ์ด๋ ฅ ๋ฐ ์„ธ๋ถ€ ๊ฐœ์„  ๋‚ด์—ญ ๊ฒ€ํ†  1ํšŒ์ฐจ ์ดํ›„ ๋ˆ„์ ๋œ 4๊ฐœ ์ปค๋ฐ‹(`7c40bf4`, `d5a191c`, `952657e`, `ebe58f6`)์˜ ์„ธ๋ถ€ ์ฝ”๋“œ ๋ณ€๊ฒฝ์„ ์ง‘์ค‘ ๊ฒ€ํ† ํ–ˆ์Šต๋‹ˆ๋‹ค: 1. `7c40bf4`: `fix(issue-9): translate only a structured server_not_running error in the shim's ls` - ์‰ผ `ls` ๋ฒˆ์—ญ ์‹œ `_ls_rc != 0`์ธ ์‹คํŒจ ์ƒํƒœ์—์„œ๋งŒ JSON ๊ตฌ์กฐํ™” ์—๋Ÿฌ(`error.code == "server_not_running"`)๋ฅผ ํŒŒ์‹ฑํ•˜๋„๋ก ์ˆ˜์ •. ์„ฑ๊ณต ์‘๋‹ต ๋ฐ์ดํ„ฐ ๋‚ด `server_not_running` ๋ฌธ์ž์—ด๋กœ ์ธํ•œ ํ—ˆ์œ„ ๋‹ค์šด ํŒ์ • ๋ฐฉ์ง€. 2. `d5a191c`: `fix(issue-14): keep a job open on fresh disk heartbeats after the subscriber idles out` - `_recent_disk_heartbeat` ๋„์ž…: herdr RPC์— ์žกํžˆ์ง€ ์•Š๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์—์ด์ „ํŠธ๋ผ๋„ ๋กœ์ปฌ `$REGISTRY_DIR/$job_id.events.log`์— ์‹ ๊ทœ `progress` heartbeat๊ฐ€ ๋„๋‹ฌํ•˜๋ฉด `IDLE_TIMEOUT` ์œ ์˜ˆ ์ฐฝ์„ ๋™์ ์œผ๋กœ ์—ฐ์žฅ. 3. `952657e`: `fix(issue-9,14): bound the R1 grace by the wall clock and keep other ls failures unknown` - ์‰ผ `ls`์˜ ๋น„-`server_not_running` ์‹คํŒจ๋ฅผ `herdr agent list returned rc=...; state unknown`์œผ๋กœ ๋น ์ ธ๋‚˜๊ฐ€๊ฒŒ ํ•˜์—ฌ `reconcile.sh`์˜ `herdr_confirmed=False` ์œ ์ง€(drift A ์˜คํŒ ๋ฐฉ์ง€). - `_within_deadline` ๋ž˜ํผ ๋ฐ ๋งค ๋ฃจํ”„ ์‹ค์‹œ๊ฐ„ clock(`date +%s`) ๋น„๊ต๋ฅผ ํ†ตํ•ด R1 ์œ ์˜ˆ ๋ฐ broker_unavailable์˜ ์ „์ฒด ๋ฒฝ์‹œ๊ณ„ ๋ฐ๋“œ๋ผ์ธ(`START_TIME + TIMEOUT`) ์—„๊ฒฉ ์ค€์ˆ˜. 4. `ebe58f6`: `fix(issue-14): record every event on disk before the wire publish` - [.agents/skills/lib_py/delegation/publish_event.py:165-176, 358](file:///Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.agents/skills/lib_py/delegation/publish_event.py#L165-L176) ๋ฐ [lib_py/delegation/service.py:134-146](file:///Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.agents/skills/lib_py/delegation/service.py#L134-L146): - `record_local_event` ํ•จ์ˆ˜๋ฅผ ๋„์ž…ํ•˜์—ฌ ๋„คํŠธ์›Œํฌ(์™€์ด์–ด) ๋ฐœํ–‰ ์‹œ๋„ **์ด์ „์—** ๋กœ์ปฌ ๋””์Šคํฌ `$REGISTRY_DIR/$job_id.events.log`์— ์ด๋ฒคํŠธ๋ฅผ ์„ ํ–‰ ๊ธฐ๋ก. - ๋ธŒ๋กœ์ปค ์—ฐ๊ฒฐ ์ง€์—ฐ์ด๋‚˜ ACK ์žฌ์‹œ๋„ ๋Œ€๊ธฐ(์ˆ˜ ์ดˆ ์ด์ƒ ์†Œ์š”)๋กœ ์ธํ•ด ๋””์Šคํฌ ๊ธฐ๋ก์ด ๋Šฆ์–ด์ ธ ์œ ์˜ˆ ์ฐฝ์ด ์กฐ๊ธฐ ๋งŒ๋ฃŒ๋˜๋˜ ๊ฒฝ์Ÿ ์ƒํƒœ๋ฅผ ์›์ฒœ ์ฐจ๋‹จ. - ๋ฐœํ–‰ ํ›„ `record_outcome(..., local_event_recorded=True)` ํ”Œ๋ž˜๊ทธ๋ฅผ ํ†ตํ•ด ์ด๋ฒคํŠธ ์ค‘๋ณต ์ถ”๊ฐ€ ์—†์ด ๊ฐ์‚ฌ ๋กœ๊ทธ ๋ฐ ์ƒํƒœ ๋™๊ธฐํ™”๋งŒ ์™„๊ฒฐ. --- ## 2. 7๋Œ€ ๊ฒ€์ฆ ํ•ญ๋ชฉ ์ƒ์„ธ ๊ต์ฐจ ๊ฒ€์ฆ ### (1) `lib.sh` ์‰ผ ์„œ๋ฒ„ ๊ธฐ๋™ env denylist - [.agents/skills/lib.sh:226-240](file:///Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.agents/skills/lib.sh#L226-L240): - ๋Ÿฐํƒ€์ž„ ์„ธ์…˜ ๋งˆ์ปค ํ•œ์ • denylist: `("CLAUDECODE", "CLAUDE_CODE_CHILD_SESSION", "CLAUDE_CODE_SESSION_ID", "CLAUDE_CODE_SESSION_ATTENDED", "CLAUDE_CODE_ENTRYPOINT", "CLAUDE_PID", "CLAUDE_CODE_MESSAGING_TOKEN", "CLAUDE_CODE_MESSAGING_SOCKET", "CLAUDE_CODE_BRIDGE_SESSION_ID", "ANTIGRAVITY_CONVERSATION_ID", "HERMES_SESSION_ID", "GROK_SESSION_ID", "OPENCODE_SESSION_ID")` - ์„ธ์…˜ ์‹๋ณ„์ž๋งŒ ์„ ๋ณ„ ์ œ๊ฑฐํ•˜์—ฌ ์„œ๋ธŒ์—์ด์ „ํŠธ Claude์˜ transcript ์ €์žฅ ๋น„ํ™œ์„ฑํ™” ๊ฒฐํ•จ(Issue #9)์„ ์›์ฒœ ์ฐจ๋‹จ. - `CLAUDE_PROJECT_DIR`, `CLAUDE_CONFIG_DIR`, `PATH`, `HOME`, `WORKSPACE_ROOT` ๋“ฑ ์ผ๋ฐ˜ ์„ค์ •์€ ์˜จ์ „ํžˆ ๋ณด์กด๋˜์–ด ์ƒ์†๋จ. ### (2) `MAM_NO_AUTOSTART` ๋ฐ ๊ตฌ์กฐํ™” ์˜ค๋ฅ˜ ๊ธฐ๋ฐ˜ ์‰ผ `ls` ๋ฒˆ์—ญ (`7c40bf4`, `952657e`) - [multi-agent-mux-status/scripts/status.sh](file:///Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.agents/skills/multi-agent-mux-status/scripts/status.sh#L12) ๋ฐ [multi-agent-mux-monitor/scripts/reconcile.sh](file:///Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh#L28)์—์„œ `export MAM_NO_AUTOSTART=1`์„ ์ •์ ์œผ๋กœ ์„ ์–ธํ•˜์—ฌ ์กฐํšŒ ๊ฒฝ๋กœ์—์„œ์˜ ๋ฐ๋ชฌ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์„ ์™„์ „ํžˆ ์ฐจ๋‹จ. - [.agents/skills/lib.sh:1245-1288](file:///Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.agents/skills/lib.sh#L1245-L1288): - `_ls_rc != 0`์ธ ์‹คํŒจ ์ƒํƒœ์—์„œ๋งŒ JSON ๋ด‰ํˆฌ(stderrยทstdout ๋ฌด๊ด€) ๋‚ด `error.code == "server_not_running"`์„ ํ™•์ธํ•˜์—ฌ ํ‘œ์ค€ `"no server running (herdr session ...)"`์œผ๋กœ ๋ฒˆ์—ญ. - ๊ทธ ๋ฐ–์˜ ๋ชจ๋“  ์‹คํŒจ๋Š” `herdr agent list returned rc=...; state unknown`์œผ๋กœ ๋น ์ ธ๋‚˜๊ฐ€ `reconcile.sh`์˜ ๋นˆ ์„œ๋ฒ„ ํ‚ค์›Œ๋“œ(`no server running`, `no sessions`, `failed to connect`)์™€ ๋งค์นญ๋˜์ง€ ์•Š์Œ. ์ด๋กœ์จ `herdr_confirmed=False`๊ฐ€ ์œ ์ง€๋˜์–ด ์‚ด์•„์žˆ๋Š” ์„ธ์…˜์ด drift A๋กœ ์˜คํŒ๋˜์–ด ๊ฐ•์ œ ์ข…๋ฃŒ๋˜๋Š” ๋ฌธ์ œ๋ฅผ ์™„๋ฒฝํžˆ ์ฐจ๋‹จํ•จ. ### (3) `send_keys_safe`: accepted ์ œ์ถœ ์ „๋‹ฌ ํ™•์ธ ๋ฐ fail-closed ๊ณ„์•ฝ - [.agents/skills/lib.sh:2485-2518](file:///Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.agents/skills/lib.sh#L2485-L2518): - RPC ์‘๋‹ต ์—๋Ÿฌ๊ฐ€ `"timeout"` ๋˜๋Š” `"agent_prompt_stalled"`์ธ ๊ฒฝ์šฐ๋งŒ `SKS_DELIVERY_GRACE_S`(15s) ๊ด€์ธก ๋ฃจํ”„ ์ง„์ž…. - ๊ด€์ธก ์ค‘ `agent_status == working`, pane์— ์ƒˆ busy ํ† ํฐ(`esc to interrupt`) ์ถœํ˜„, ํ˜น์€ ์‚ฌ์ „ ์Šค๋ƒ…์ƒท ๋Œ€๋น„ pane ๋ณ€ํ™”๊ฐ€ ๊ฐ์ง€๋˜๋ฉด ์„ฑ๊ณต(rc 0). - ์œ ์˜ˆ ๋งŒ๋ฃŒ ์‹œ๊นŒ์ง€ ์–ด๋– ํ•œ ์‹ ํ˜ธ๋„ ์—†์œผ๋ฉด ์ฆ‰์‹œ rc 4 fail-closed. `transport_error`๋Š” accepted๋กœ ๋ถ„๋ฅ˜๋˜์ง€ ์•Š๊ณ  ์ฆ‰์‹œ rc 4 ๋ฐ˜ํ™˜. ๋‹จ์ผ ์ฃผ์ž… ๊ณ„์•ฝ(paste-buffer ํด๋ฐฑ ๋ฐฐ์ œ) ์—„๊ฒฉ ์ค€์ˆ˜. ### (4) ์„ ํ–‰ ๋กœ์ปฌ ๊ธฐ๋ก ๊ธฐ๋ฐ˜ ๋ž˜ํผ ack ์œ ์˜ˆ ๋ฐ ๋ฒฝ์‹œ๊ณ„ ๋ฐ”์šด๋”ฉ๋œ R1 ์œ ์˜ˆ ์ฐฝ (`d5a191c`, `952657e`, `ebe58f6`) - `publish_event` ์„ ํ–‰ ๊ธฐ๋ก ([lib_py/delegation/publish_event.py:165-176, 358](file:///Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.agents/skills/lib_py/delegation/publish_event.py#L165-L176)): - ๋„คํŠธ์›Œํฌ ์ „์†ก ์ „ `record_local_event`๊ฐ€ ์ฆ‰์‹œ `$REGISTRY_DIR/$job_id.events.log`์— `progress` ๋ผ์ธ์„ ์ถ”๊ฐ€ํ•˜๋ฏ€๋กœ, ๋ธŒ๋กœ์ปค ์‘๋‹ต ์ง€์—ฐ์— ๊ด€๊ณ„์—†์ด `_recent_disk_heartbeat`๊ฐ€ 0ms ์ง€์—ฐ์œผ๋กœ ์ตœ์‹  ํƒ€์ž„์Šคํƒฌํ”„๋ฅผ ๊ฐ์ง€ํ•จ. - `_within_deadline "$deadline" ...`: herdr RPC ํ”„๋กœ๋ธŒ๊ฐ€ ์ง€์—ฐ๋˜๊ฑฐ๋‚˜ ๋ธ”๋กœํ‚น๋  ๊ฒฝ์šฐ ์ง€์ •๋œ ์‹œ๊ฐ์— ์ž์‹ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ •๋ฆฌํ•˜๊ณ  124(no evidence)๋ฅผ ๋ฐ˜ํ™˜. - `finalize_unfinished` ([multi-agent-mux-delegate-job:725-760](file:///Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job#L725-L760)): - `deadline=$(( ${START_TIME:-$(date +%s)} + TIMEOUT ))` - `while [ "$(date +%s)" -lt "$deadline" ]; do` ๋กœ ๋งค pass๋งˆ๋‹ค ์‹ค์ œ ์‹œ๊ณ„๋ฅผ ํ™•์ธํ•˜์—ฌ ๋ฒฝ์‹œ๊ณ„ ํƒ€์ž„์•„์›ƒ ์ƒํ•œ ์—„๊ฒฉ ์ค€์ˆ˜. ### (5) T1-7 ํ…Œ์ŠคํŠธ ํŠธ๋ฆฌ๊ฑฐ ๋ณ€๊ฒฝ์˜ ํƒ€๋‹น์„ฑ - [tests/test_issue6_fail_closed.py:262-270](file:///Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/tests/test_issue6_fail_closed.py#L262-L270): - `timeout`์ด accepted ์ œ์ถœ๋กœ ์Šน๊ฒฉ๋จ์— ๋”ฐ๋ผ, ์ฃผ์ž… ๋ถˆ๋Šฅ ์ƒํ™ฉ์˜ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด ํ™•์ •์  ์ „์†ก ์‹คํŒจ ์ฝ”๋“œ์ธ `transport_error`๋กœ ๋ณ€๊ฒฝํ•œ ๊ฒƒ์€ ์ •ํ™•ํ•˜๊ณ  ํƒ€๋‹นํ•จ. ### (6) ๋ฒ„์ „ ๋ฝ์Šคํ… ์™„์ „์„ฑ (v4.4.2 PATCH) ๋ฐ ์ปค๋ฐ‹ ํ˜•์‹ - `lib.sh` (`MAM_VERSION="4.4.2"`), `eager.py` (`FRAMEWORK_VERSION = "4.4.2"`), 8๊ฐœ `SKILL.md` frontmatter, `VERSIONS.md` (๊ฐœ์š”, ๋งคํŠธ๋ฆญ์Šค, ์ƒ์„ธ ๋ณ€๊ฒฝ ๋‚ด์—ญ), ๊ด€๋ จ ๋ฌธ์„œ ๋ฐฐ์ง€(`README.md`, `README.ko.md`, `BOOTSTRAP.md`, `BOOTSTRAP.ko.md`, `MESSAGING.md`) ์ „์› 4.4.2 ์ผ์น˜. - `VERSIONS.md`์— `7c40bf4`, `d5a191c`, `952657e`, `ebe58f6`์˜ ๊ฐœ์„  ๋‚ด์—ญ๊ณผ 21๊ฐœ ์‹ ๊ทœ ํ…Œ์ŠคํŠธ ํ˜„ํ™ฉ์ด ์ •ํ™•ํžˆ ๊ธฐ๋ก๋จ. - `tests/test_version_consistency.py`: **3 passed**. ### (7) 8๋Œ€ ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ ์ „์ˆ˜ ์‹คํ–‰ ๊ฒฐ๊ณผ ์ง€์ •๋œ ํ•ต์‹ฌ ํ…Œ์ŠคํŠธ ์Šค์œ„ํŠธ 8์ข…์„ ์ „์ˆ˜ ์‹คํ–‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค: 1. `tests/test_issue9_server_env.py` (9 passed) 2. `tests/test_issue14_delivery_ack.py` (12 passed โ€” ์‹ ๊ทœ ์„ ํ–‰ ๊ธฐ๋ก ํ…Œ์ŠคํŠธ 2๊ฑด ํฌํ•จ) 3. `tests/test_issue6_fail_closed.py` (29 passed) 4. `tests/test_issue6_turn_handshake.py` (17 passed) 5. `tests/test_a4_adapter_contract.py` (19 passed) 6. `tests/test_loop_cli.py` (15 passed) 7. `tests/test_issue5_phase4_runtime.py` (13 passed) 8. `tests/test_version_consistency.py` (3 passed) **์‹คํ–‰ ๊ฒฐ๊ณผ**: ```text ============================== 119 passed in 349.97s ============================== ``` ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์ค‘ 35-40์ดˆ ์ฃผ๊ธฐ heartbeat ์ด๋ฒคํŠธ๋ฅผ 7ํšŒ ์—ฐ์† ๋ฐœํ–‰ํ•˜์—ฌ ๋ฐฑํ”Œ๋ ˆ์ธ ๊ทœ์น™์„ ์ฒ ์ €ํžˆ ์ค€์ˆ˜ํ•˜์˜€์œผ๋ฉฐ, 119๊ฐœ ํ…Œ์ŠคํŠธ ์ „ ํ•ญ๋ชฉ์ด 100% ํ†ต๊ณผํ–ˆ์Šต๋‹ˆ๋‹ค. --- ## 3. ์ข…ํ•ฉ ํ‰๊ฐ€ - ๋„คํŠธ์›Œํฌ ์ „์†ก ์ง€์—ฐ๊ณผ ๋ฌด๊ด€ํ•˜๊ฒŒ ๋กœ์ปฌ ๋””์Šคํฌ์— heartbeat๋ฅผ ์ฆ‰์‹œ ๊ธฐ๋กํ•˜๋Š” `record_local_event` ๋„์ž…(`ebe58f6`)์„ ํ†ตํ•ด, ๋ถ„์‚ฐ ํ™˜๊ฒฝ์—์„œ์˜ ๋น„๋™๊ธฐ ์ƒ์กด ์‹ ํ˜ธ ์ „๋‹ฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด ์™„๋ฒฝํ•œ ๋ฌด๊ฒฐ์„ฑ์„ ํ™•๋ณดํ–ˆ์Šต๋‹ˆ๋‹ค. - ๋ชจ๋“  ๊ธฐ๋Šฅ ๋ฐ ๋น„๊ธฐ๋Šฅ ๊ณ„์•ฝ(๋‹จ์ผ ์ฃผ์ž…, fail-closed, ๋ฒฝ์‹œ๊ณ„ ์ƒํ•œ ๋ณด์žฅ, read-only ๋ฌด๋ณ€์ด์„ฑ, drift ์˜คํŒ ๋ฐฉ์ง€, ๋น„๋™๊ธฐ ๋””์Šคํฌ ํ•˜ํŠธ๋น„ํŠธ)์ด ๊ฒ€์ฆ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. - ํ”Œ๋ž˜๋„ˆ ์—์Šค์ปฌ๋ ˆ์ด์…˜ ์—†์ด `main` ๋ธŒ๋žœ์น˜ ๋ณ‘ํ•ฉ์„ ์ตœ์ข… ์Šน์ธํ•ฉ๋‹ˆ๋‹ค. --- [VERDICT: PASS]