# ๐Ÿ“‘ Issue #5 Phase 3 โ€” `run_loop.sh` Thin Wrapper ์ „ํ™˜ ๋ฐ `lib_py.loop` ์Šน๊ฒฉ ๋ช…์„ธ - **๋ฌธ์„œ ๋ฒ„์ „**: Rev.1 - **์ƒํƒœ**: Phase 3 ์„ค๊ณ„ ๋ฐ ์ด์‹ ์ฐฉ์ˆ˜ ๊ธฐ์ค€์„  (Implementation & Regression Specification) - **๋Œ€์ƒ ํŒŒ์ผ**: - `lib_py/loop/runner.py`, `lib_py/loop/__main__.py`, `lib_py/loop/parser.py` - `.agents/skills/multi-agent-mux-loop/scripts/run_loop.sh` - `tests/test_issue5_phase3_loop.py` --- ## 1. ๊ฐœ์š” ๋ฐ ํ•œ ๋ฌธ์žฅ ์š”์•ฝ **936์ค„์˜ ๋ณต์žกํ•œ Bash ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜(`run_loop.sh`)์„ ๋‹จ์ผ ์ œ์–ด ๊ตฌ์กฐ์˜ Python ๋ชจ๋“ˆ(`lib_py.loop`)๋กœ ์Šน๊ฒฉํ•˜๊ณ , `run_loop.sh`๋Š” CLI ๊ณ„์•ฝ๊ณผ B-13 Freeze๋ฅผ ๋ณด์กดํ•˜๋Š” ์–‡์€ ๋ž˜ํผ(Thin Wrapper)๋กœ ์ „ํ™˜ํ•œ๋‹ค.** Phase 2(CAS ์ „์ด ์—”์ง„, ์ด์ค‘ ๊ฐ์ง€ ์ž๋™์™„๋ฃŒ ๊ฒŒ์ดํŠธ)์—์„œ ๊ตฌ์ถ•๋œ ์ƒํƒœ ๋ฐ ํŒ์ • ๊ธฐ๋ฐ˜์„ ์‹ค์ œ ๋ฃจํ”„ ์ œ์–ด ํ๋ฆ„์— ์œ ๊ธฐ์ ์œผ๋กœ ๊ฒฐํ•ฉํ•˜๋ฉฐ, ๊ธฐ์กด 732๊ฐœ ํ…Œ์ŠคํŠธ์˜ 0-Fail ํ†ต๊ณผ์™€ ์…ธ ๋ ˆ๊ฑฐ์‹œ ๋กค๋ฐฑ(`MAM_LOOP_PY=0`)์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. --- ## 2. ๋ชจ๋“ˆ ์•„ํ‚คํ…์ฒ˜ (`lib_py/loop/`) Phase 1/2a์˜ ํ˜•์ œ ํŒจํ‚ค์ง€ ๊ฒฉ๋ฆฌ ์›์น™(D1(b), G9: `lib_py.loop`๋Š” `lib_py.delegation` ์™ธ์˜ ๋‹ค๋ฅธ ํ˜•์ œ ํŒจํ‚ค์ง€๋ฅผ ์ž„ํฌํŠธํ•˜์ง€ ์•Š์Œ)์„ ์—„๊ฒฉํžˆ ์ค€์ˆ˜ํ•ฉ๋‹ˆ๋‹ค. ``` lib_py/loop/ โ”œโ”€โ”€ __init__.py # ํŒจํ‚ค์ง€ ์„ ์–ธ ๋ฐ export โ”œโ”€โ”€ __main__.py # python3 -m lib_py.loop ์ง„์ž…์  โ”œโ”€โ”€ parser.py # ์ฝ”๋“œ ํŽœ์Šค ์ œ์™ธ Strict ํŒ์ •/ํƒœ๊ทธ ํŒŒ์„œ โ”œโ”€โ”€ runner.py # ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ ๋ณธ์ฒด (LoopRunner ํด๋ž˜์Šค) โ”œโ”€โ”€ checkpoint.py # ์ฒดํฌํฌ์ธํŠธ ๊ธฐ๋ก ๋ฐ ๋ณต๊ตฌ (.mam/loop-checkpoint.json) โ”œโ”€โ”€ attribution.py # [Phase 2a] ๊ด€์ธก ์ฆ๊ฑฐ ์ˆ˜์ง‘ โ”œโ”€โ”€ watcher.py # [Phase 2a] herdr agent wait ๋ž˜ํผ โ”œโ”€โ”€ policy.py # [Phase 2a] ์ƒํƒœ ๊ฒฐ์ • ์ •์ฑ… โ”œโ”€โ”€ shadow.py # [Phase 2a] Would-Complete shadow ์‹คํ–‰๊ธฐ โ”œโ”€โ”€ store.py # [Phase 2a] ์ฆ๊ฑฐ ์ €์žฅ์†Œ โ””โ”€โ”€ autocomplete.py # [Phase 2b] ์ž๋™์™„๋ฃŒ ํ‰๊ฐ€๊ธฐ ``` --- ## 3. ํ•ต์‹ฌ ์„ค๊ณ„ ์š”์†Œ ### 3-1. ์—„๊ฒฉํ•œ ํŒ์ • ํŒŒ์„œ (Strict Parser, `parser.py`) - **์ฝ”๋“œ ํŽœ์Šค(` ``` `) ๋ฌด์‹œ**: - ๊ธฐ์กด ์…ธ ์ •๊ทœ์‹์€ ๋ฆฌํฌํŠธ ๋ณธ๋ฌธ ๋‚ด ์ฝ”๋“œ ๋ธ”๋ก(์˜ˆ: ๋งˆํฌ๋‹ค์šด ์˜ˆ์‹œ ๋“ฑ)์— ์ ํžŒ `[VERDICT: PASS]`๋„ ํŒŒ์ผ ๋์— ์œ„์น˜ํ•˜๋ฉด ์œ ํšจํ•œ ํŒ์ •์œผ๋กœ ์˜ค์ธํ•˜๋Š” ๊ฒฐํ•จ(`baseline.md` Gap)์ด ์กด์žฌํ–ˆ์Šต๋‹ˆ๋‹ค. - Python ํŒŒ์„œ๋Š” ํ…์ŠคํŠธ๋ฅผ ํŒŒ์‹ฑํ•  ๋•Œ ๋งˆํฌ๋‹ค์šด ์ฝ”๋“œ ํŽœ์Šค(` ``` ` ๋˜๋Š” `~~~`) ๋‚ด๋ถ€ ๋ผ์ธ์„ ์ œ๊ฑฐํ•œ ๋’ค, **๊ฐ€์žฅ ๋งˆ์ง€๋ง‰ ๋น„๊ณต๋ฐฑ ๋ผ์ธ(last non-blank line)**๋งŒ์„ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค. - **Fail-Closed ๋ถˆ๋ณ€์‹**: - ํŒ์ • ๋ผ์ธ์ด ์—†๊ฑฐ๋‚˜, `[VERDICT: NOT PASS]`์™€ `[VERDICT: PASS]`๊ฐ€ ๋ชจํ˜ธํ•˜๊ฒŒ ์ถฉ๋Œํ•˜๊ฑฐ๋‚˜, ์•Œ ์ˆ˜ ์—†๋Š” ํƒœ๊ทธ์ธ ๊ฒฝ์šฐ ์˜ˆ์™ธ ์—†์ด `NOT PASS`๋กœ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. - **ํƒœ๊ทธ ์ถ”์ถœ**: - `[ESCALATE: PLANNER]`: ์ฝ”๋“œ ํŽœ์Šค ์™ธ๋ถ€ ์–ด๋””์„œ๋“  ๋‹จ๋… ํ–‰์œผ๋กœ ์กด์žฌํ•˜๋ฉด ๊ฐ์ง€ - `[REBUT: ]`: Creator ๋ฐ˜๋ก  ๋Œ€์ƒ ๋ฆฌ๋ทฐ์–ด ์„ธ์…˜๋ช… ์ถ”์ถœ (์ค‘๋ณต ์ œ๊ฑฐ ๋ฐ ์‹ค์ œ ๊ฑฐ๋ถ€ํ•œ ๋ฆฌ๋ทฐ์–ด๋กœ ํ•„ํ„ฐ๋ง) - `[ADJUDICATION: SUSTAINED|OVERRULED]`: ์žฌ์‹ฌ ํŒ์ • ์ถ”์ถœ (fail-closed: ๋ช…์‹œ์  SUSTAINED ์™ธ์—๋Š” ๋ชจ๋‘ OVERRULED) - `[ARBITRATION: CREATOR|REVIEWER]`: Planner ์ค‘์žฌ ํŒ์ • ์ถ”์ถœ ### 3-2. `run_loop.sh` Thin Wrapper ๋ฐ ํ•˜์œ„ ํ˜ธํ™˜์„ฑ - **ํ•˜์œ„ ํ˜ธํ™˜ ๋กค๋ฐฑ ํ”Œ๋ž˜๊ทธ**: - `MAM_LOOP_PY=1` (๊ธฐ๋ณธ๊ฐ’): Python `lib_py.loop` ์‹คํ–‰ - `MAM_LOOP_PY=0`: ๊ธฐ์กด ์ธ๋ผ์ธ ์…ธ ๋ฃจํ”„ ์‹คํ–‰ (์•ˆ์ „ ๋กค๋ฐฑ ๊ฒฝ๋กœ) - **B-13 Freeze ๋ฐ ์ธ์ž ๋ณด์กด**: - Phase 4์—์„œ Freeze๊ฐ€ ์ œ๊ฑฐ๋˜๊ธฐ ์ „๊นŒ์ง€, `run_loop.sh`๋Š” ๊ธฐ์กด์ฒ˜๋Ÿผ ๊ธฐ๋™ ์‹œ `/tmp/mam-loop-freeze.XXXXXX`๋กœ ํŠธ๋ฆฌ๋ฅผ ๋ณต์ œํ•˜์—ฌ ์‹คํ–‰ ๊ฒฉ๋ฆฌ๋ฅผ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. - `MAM_REAL_ROOT`, `WORKSPACE_ROOT`, `MAM_ENV_FILE` ํ™˜๊ฒฝ๋ณ€์ˆ˜ export๋ฅผ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•˜์—ฌ ๊ธฐ์กด ํ…Œ์ŠคํŠธ์˜ ๋ฌธ์ž์—ด ๊ฒ€์ฆ์„ ๋งŒ์กฑํ•ฉ๋‹ˆ๋‹ค. - **๊ธฐ์กด ํ…Œ์ŠคํŠธ ํ…์ŠคํŠธ ์•ต์ปค ๋ณด์กด**: - `test_o1_rebuttal.py` (`has_tag_line() {`, `extract_tag_values() {`, `find_report() {`) - `test_b7_diff_untracked.py` (`CHANGES_DIFF=$(mam_collect_changes_diff ...`) - `test_deploy_freshness.py` (`export MAM_ENV_FILE=`) - `test_o3_scoped_guard.py` (`mam_release_loop_lock`) - ์œ„ ์•ต์ปค๋“ค์€ `run_loop.sh` ๋‚ด์— ์…ธ ํ—ฌํผ ๋˜๋Š” ์ฃผ์„/shim ๋ธ”๋ก์œผ๋กœ ๋ณด์กด๋˜์–ด ๊ธฐ์กด ์ •์  ๊ฒ€์‚ฌ ํ…Œ์ŠคํŠธ๋“ค์„ 100% ํ†ต๊ณผ์‹œํ‚ต๋‹ˆ๋‹ค. ### 3-3. ์ฒดํฌํฌ์ธํŠธ ๋ฐ ์ •์ง€ ํ”„๋กœํ† ์ฝœ (`checkpoint.py`) - **์›์ž์  ์ฒดํฌํฌ์ธํŠธ (`.mam/loop-checkpoint.json`)**: - ๋ฃจํ”„ ํšŒ์ฐจ(`iteration`), ํ™œ์„ฑ Job ID ๋ชฉ๋ก(`created_jobs`), ํ˜„์žฌ ๊ณ„ํš(`current_plan`), ๋ฆฌ๋ทฐ ์ƒํƒœ(`review_status`), ๊ธฐ์ค€ ์ปค๋ฐ‹(`base_commit`), ํƒ€์ž„์Šคํƒฌํ”„ ๊ธฐ๋ก. - ์ž„์‹œ ํŒŒ์ผ ์“ฐ๊ธฐ ํ›„ ์›์ž์  rename(`replace`)์œผ๋กœ ์ปค๋ฐ‹. - **์šฐ์•„ํ•œ ์ •์ง€ (Quiesce on Signal)**: - SIGINT/SIGTERM ์ˆ˜์‹  ์‹œ ์ฆ‰์‹œ ๊ฐ•์ œ ์ข…๋ฃŒํ•˜์ง€ ์•Š๊ณ , ํ˜„์žฌ ์ง„ํ–‰ ์ค‘์ธ ์„œ๋ธŒํ”„๋กœ์„ธ์Šค๋ฅผ ์ •๋ฆฌํ•˜๊ณ  ์ฒดํฌํฌ์ธํŠธ๋ฅผ ๊ธฐ๋กํ•œ ๋’ค ๊ฐ€๋“œ๋ฅผ ํ•ด์ œํ•˜๊ณ  ์•ˆ์ „ํ•˜๊ฒŒ ์ข…๋ฃŒ(exit 130/143)ํ•ฉ๋‹ˆ๋‹ค. --- ## 4. ๋‹จ๊ณ„๋ณ„ ์„ธ๋ถ€ ๊ตฌํ˜„ ๋ฐ ๊ฒ€์ฆ ๊ฒŒ์ดํŠธ (P3-01 ~ P3-03) ### ๐Ÿ“Œ P3-01: `lib_py/loop/parser.py` ๋ฐ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ๋กœ์ง ์ด์‹ 1. `parser.py` ๊ตฌํ˜„: ์ฝ”๋“œ ํŽœ์Šค ๋ถ„๋ฆฌ ๋ฐ ์—„๊ฒฉํ•œ ํŒ์ •/ํƒœ๊ทธ ์ถ”์ถœ ๋กœ์ง ์ž‘์„ฑ. 2. `runner.py` ๊ตฌํ˜„: - CLI ์ธ์ž ํŒŒ์‹ฑ ๋ฐ ๊ฒ€์ฆ (`--creator`, `--task`, `--plan`, `--plan-talk`, `--reviewer`, `--all-reviewer`, `--max-loop`, `--max-rebut`, `--cleanup`, `--verbose`) - ์„ธ์…˜ ์กฐํšŒ ๋ฐ ์ƒํƒœ ๊ฒ€์ฆ (Planner, Target Agent, Reviewers) - Phase 1 ํ”Œ๋ž˜๋‹ ๋ฐ ํ† ๋ก  (Plan โž” Critique โž” Refine) - Phase 2 ๊ตฌํ˜„ ๋””์ŠคํŒจ์น˜ (Worker) - Phase 3 ๋ฆฌ๋ทฐ ๋ฃจํ”„ (๋ณ‘๋ ฌ ๋””์ŠคํŒจ์น˜, Strict ํŒ์ • ์ˆ˜๋ ด, Escalation ๋ถ„๊ธฐ, Rebuttal/Adjudication/Arbitration) - Phase 4 ์‚ฐ์ถœ๋ฌผ ์Šน๊ฒฉ (Plan ๋ฐ Report ์›์ž์  ๋ณต์‚ฌ/์ด๋™) ๋ฐ ์ž„์‹œ ๋””๋ ‰ํ„ฐ๋ฆฌ ์ •๋ฆฌ 3. `tests/test_issue5_phase3_loop.py` ์ž‘์„ฑ: - ์ฝ”๋“œ ํŽœ์Šค ๋‚ด๋ถ€ ํ—ˆ์œ„ ํŒ์ • ๋ฌด์‹œ ๊ฒ€์ฆ (Strict Parser) - Rebuttal ์˜ˆ์‚ฐ(iteration budget vs run-wide budget) ๋ฐ ์ค‘์žฌ ํ๋ฆ„ ๊ฒ€์ฆ - ๋งŒ์žฅ์ผ์น˜ PASS ์ˆ˜๋ ด ๊ฒ€์ฆ ### ๐Ÿ“Œ P3-02: `run_loop.sh` Thin Wrapper ์ „ํ™˜ ๋ฐ ๋Ÿฐํƒ€์ž„ ํ˜ธํ™˜์„ฑ 1. `run_loop.sh`์— `MAM_LOOP_PY` ๋ถ„๊ธฐ ๋„์ž…: - `MAM_LOOP_PY=1`์ผ ๋•Œ Python `lib_py.loop`๋กœ ์œ„์ž„ - ๊ธฐ์กด CLI ํŒŒ์‹ฑ, `--help` exit 1 ์œ ์ง€, B-13 freeze ๊ฒฉ๋ฆฌ ์œ ์ง€ - ๊ธฐ์กด ํ…Œ์ŠคํŠธ ํ…์ŠคํŠธ ์•ต์ปค ๋ณด์กด 2. CWD, ๊ณต๋ฐฑ ๊ฒฝ๋กœ, ์‹œ๊ทธ๋„ ํŠธ๋žฉ, ์ธํ„ฐํ”„๋ฆฌํ„ฐ ์•ต์ปค๋ง ๊ฒ€์ฆ: - Freeze ์‚ฌ๋ณธ ๋‚ด์—์„œ ์‹คํ–‰ ์‹œ `_delegate_py_bin`์ด `WORKSPACE_ROOT` ๊ธฐ์ค€ ์ธํ„ฐํ”„๋ฆฌํ„ฐ๋ฅผ ์ •ํ™•ํžˆ ํ•ด์„ํ•˜๋Š”์ง€ ๊ฒ€์ฆ (O-4 ์ธ๊ณ„ ํ•ด๊ฒฐ) ### ๐Ÿ“Œ P3-03: ์ „๋ฉด ํšŒ๊ท€ ๊ฒ€์ฆ ๋ฐ ์ฒดํฌํฌ์ธํŠธ ํ•ธ๋“œ์˜คํ”„ 1. ์ „์ฒด 732๊ฐœ ํ…Œ์ŠคํŠธ ํšŒ๊ท€ ๊ฒ€์ฆ: - `test_loop_cli.py`, `test_o1_rebuttal.py`, `test_tier4_e2e.py` (Scenario 5 E2E ๋ฃจํ”„ ํฌํ•จ) ์ „์ฒด 0-Fail ํ†ต๊ณผ 2. ์ฒดํฌํฌ์ธํŠธ ๊ธฐ๋ก ๋ฐ ์‹œ๊ทธ๋„ ์ธํ„ฐ๋ŸฝํŠธ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๊ฒ€์ฆ. 3. ๋กค๋ฐฑ ๋“œ๋ฆด: `MAM_LOOP_PY=0`์œผ๋กœ ๊ธฐ์กด ์…ธ ๋ฃจํ”„ ์ „ํ™˜ ์‹œ ์ •์ƒ ์ž‘๋™ ํ™•์ธ.