diff --git a/docs/contracts/issue-5/baseline.md b/docs/contracts/issue-5/baseline.md new file mode 100644 index 0000000..4d192f4 --- /dev/null +++ b/docs/contracts/issue-5/baseline.md @@ -0,0 +1,141 @@ +# ๐Ÿ“‘ Issue #5 Baseline Contract Specification + +- **Document Version**: Rev.3 (Post Cross-Review Corrections) +- **Issue**: [Gitea Issue #5 โ€” `[Refactor] lib.sh ์ž‘์—… ์œ„์ž„ ๋กœ์ง ๋ฐ run_loop.sh ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ์˜ Python ๋ชจ๋“ˆํ™” (lib_py ์Šน๊ฒฉ)`](https://git.godopu.com/tmpl/multi-agent-mux/issues/5) +- **Baseline Commit**: `47640e2fd7b108be29446a9384a343727e87edf8` +- **Environment Baseline**: Python 3.9.6, Herdr 0.8.2, macOS Darwin 24.3.0 +- **Test Suite Baseline**: 469 tests collected initially; 484 tests collected with Phase 0 contract suite (+15 contract & mutation tests) +- **Status**: Phase 0 Baseline Contract (Observed vs. Proposed vs. Gap) + +--- + +## 1. ๊ฐœ์š” ๋ฐ ๊ณ„์•ฝ ์ •์˜ ์›์น™ + +๋ณธ ๋ฌธ์„œ๋Š” Issue #5(Python ๋ชจ๋“ˆํ™” ๋ฐ `lib_py` ์Šน๊ฒฉ)์˜ ์ฐฉ์ˆ˜ ๊ธฐ์ค€์„ (Baseline)์œผ๋กœ์„œ, ํ˜„์žฌ ์ฝ”๋“œ๋ฒ ์ด์Šค(`run_loop.sh`, `lib.sh`, `delegate-job` ๋„๊ตฌ๊ตฐ)์— ๊ตฌํ˜„๋˜์–ด ๋™์ž‘ ์ค‘์ธ CLI, ์ด๋ฒคํŠธ, ๋ฆฌํฌํŠธ, ๋ฝ(Lock) ๋ฐ ์ƒํƒœ ์ „์ด ๊ณ„์•ฝ์„ ์—„๋ฐ€ํ•˜๊ฒŒ ๋ถ„๋ฅ˜ํ•˜์—ฌ ๋ช…๋ฌธํ™”ํ•ฉ๋‹ˆ๋‹ค. + +๊ฐ ํ•ญ๋ชฉ์€ ๋‹ค์Œ ์„ธ ๋ฒ”์ฃผ๋กœ ๊ตฌ๋ถ„ํ•˜์—ฌ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค: +- **`[Observed]`**: ํ˜„์žฌ ์†Œ์Šค ์ฝ”๋“œ ๋ฐ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์—์„œ ์‹ค์ œ๋กœ ํ™•์ธ๋œ ๋™์ž‘. +- **`[Proposed]`**: ํ–ฅํ›„ Phase 1โ€“4์—์„œ ๊ตฌํ˜„ํ•˜๊ณ ์ž ํ•˜๋Š” ๋ชฉํ‘œ ๋™์ž‘. +- **`[Gap]`**: ํ˜„ํ–‰ ๋™์ž‘๊ณผ ๋ชฉํ‘œ ๋™์ž‘ ์‚ฌ์ด์˜ ๋ถˆ์ผ์น˜, ๊ฒฐํ•จ ๋˜๋Š” ๋ฏธ๊ตฌํ˜„ ์‚ฌํ•ญ. + +--- + +## 2. CLI ์ธํ„ฐํŽ˜์ด์Šค ๊ณ„์•ฝ (Orchestrator & Delegation) + +### 2-1. `run_loop.sh` CLI ์˜ต์…˜ ๋ฐ ์ธ์ˆ˜ ํŒŒ์‹ฑ ๊ณ„์•ฝ + +| ์˜ต์…˜ | ๊ธฐ๋ณธ๊ฐ’ | ์ œ์•ฝ ๋ฐ ๊ฒ€์ฆ ๊ทœ์น™ | ํ˜„ํ–‰ ์†Œ์Šค ์œ„์น˜ | ๋Œ€์‘ ํ…Œ์ŠคํŠธ | ๋ฒ”์ฃผ | +|---|---|---|---|---|---| +| `--creator ` | ์—†์Œ (ํ•„์ˆ˜) | ๋น„์–ด์žˆ์ง€ ์•Š์€ ๋ฌธ์ž์—ด ํ•„์ˆ˜(L95). ์„ธ์…˜ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ(`.mam/agent-sessions.yaml` / `.db`) ๋“ฑ๋ก ๋ฐ ์‹คํ–‰ ์ค‘ ๊ฒ€์ฆ์€ L160โ€“185์—์„œ ์ˆ˜ํ–‰ | `run_loop.sh:79, 95-98, 160-185` | `test_loop_cli.py:test_creator_parses_and_reaches_session_check` | `[Observed]` | +| `--task ` | ์—†์Œ (ํ•„์ˆ˜) | ์ž‘์—… ๋ชฉํ‘œ ๋ฌธ์ž์—ด. ๋ˆ„๋ฝ ์‹œ fast-fail ๋ฐ `usage` ์ถœ๋ ฅ ํ›„ exit 1 | `run_loop.sh:85, 95-98` | `test_loop_cli.py:test_missing_creator_and_task_fail_fast` | `[Observed]` | +| `--target-agent ` | ์ œ๊ฑฐ๋จ | ์‚ฌ์šฉ ์ฆ‰์‹œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ ํ›„ exit 1 ๋ฐ˜ํ™˜ (ํ•˜์œ„ ํ˜ธํ™˜ ํ๊ธฐ) | `run_loop.sh:80-83` | `test_loop_cli.py:test_target_agent_is_rejected` | `[Observed]` | +| `--plan` | `false` | ํ”Œ๋ž˜๋„ˆ ๊ฐœ์ž… ๋ฐ ์„ค๊ณ„ ๋‹จ๊ณ„ ํ™œ์„ฑํ™” ํ”Œ๋ž˜๊ทธ | `run_loop.sh:56` | `test_loop_cli.py:test_planner_without_plan_fail_fast` | `[Observed]` | +| `--planner ` | ์ž๋™ ์กฐํšŒ | `--plan` ํ”Œ๋ž˜๊ทธ ์—†์ด ์ง€์ • ์‹œ ์ฆ‰์‹œ exit 1 ๋ฐ˜ํ™˜. ๋ฏธ์ง€์ • ์‹œ ๋“ฑ๋ก๋œ planner ์—ญํ•  ์„ธ์…˜ ์ž๋™ ์„ ํƒ | `run_loop.sh:84, 100-105` | `test_loop_cli.py:test_explicit_planner_unregistered`, `test_explicit_planner_skips_auto_resolve` | `[Observed]` | +| `--plan-talk ` | `1` | ์ˆซ์ž regex(`^[0-9]+$`) ๊ฒ€์ฆ. ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋Š” ์–‘์ˆ˜๋ฅผ ์š”๊ตฌํ•˜์ง€๋งŒ regex๋Š” **`0`์„ ํ—ˆ์šฉ**ํ•จ. 0 ์ง€์ • ์‹œ ํ† ๋ก  ๋ฃจํ”„(`$turn -le $PLAN_TALK_TURNS`)๋ฅผ ์™„์ „ํžˆ ๊ฑด๋„ˆ๋œ€ | `run_loop.sh:57-62, 442` | `test_loop_cli.py` (regex ํŒŒ์‹ฑ) | `[Observed]` | +| `--reviewer ` | ์—†์Œ | ์‰ผํ‘œ ๊ตฌ๋ถ„ ๋ฆฌ๋ทฐ์–ด ์„ธ์…˜ ๋ชฉ๋ก | `run_loop.sh:63` | `test_loop_cli.py` | `[Observed]` | +| `--all-reviewer` | `false` | ๋ชจ๋“  ํ™œ์„ฑ ๋ฆฌ๋ทฐ์–ด ์„ธ์…˜ ๊ฐ•์ œ. `--reviewer`์™€ ๋™์‹œ ์ง€์ • ์‹œ **`--all-reviewer`๊ฐ€ ์šฐ์„ **ํ•˜๋ฉฐ `--reviewer` ๋ชฉ๋ก์€ ๋ฌด์‹œ๋˜๊ณ  ๊ฒฝ๊ณ  ๋กœ๊ทธ ์ถœ๋ ฅ | `run_loop.sh:64, 192-196` | ํ˜„ํ–‰ ์ „์šฉ CLI ํ…Œ์ŠคํŠธ ๋ถ€์žฌ (`[Gap]`) | `[Observed]` | +| `--max-loop ` | `3` | ์–‘์ˆ˜ ์ •์ˆ˜ ๊ฒ€์ฆ(`^[0-9]+$` and `N > 0`). 0 ์ดํ•˜ ์‹œ ์—๋Ÿฌ ๋ฐ˜ํ™˜ | `run_loop.sh:65-70` | `test_loop_cli.py` | `[Observed]` | +| `--max-rebut ` | `1` | ๋น„์Œ์ˆ˜ ์ •์ˆ˜ ๊ฒ€์ฆ(`^[0-9]+$`). 0 ์ง€์ • ์‹œ ๋ฐ˜๋ก (Rebuttal) ์™„์ „ ๋น„ํ™œ์„ฑํ™” | `run_loop.sh:71-76` | `test_o1_rebuttal.py:test_v3_rebut_option_parsing_and_budget` | `[Observed]` | +| `REBUT_TOTAL_BUDGET` | `MAX_REBUT * MAX_LOOP` | ์ „์ฒด ์‹คํ–‰ ๊ธฐ๊ฐ„ ๋™์•ˆ์˜ ์ด ๋ฐ˜๋ก  ํ—ˆ์šฉ ์ƒํ•œ(Run-wide budget). ์กฐ๊ธฐ ์†Œ์ง„ ์‹œ ์ถ”๊ฐ€ ๋ฐ˜๋ก  ๊ธˆ์ง€ | `run_loop.sh:93, 762-766` | `test_o1_rebuttal.py:test_v3_rebut_option_parsing_and_budget` | `[Observed]` | +| `--verbose` | `false` | ์ƒ์„ธ ์‹คํ–‰ ํƒ€์ž„๋ผ์ธ ์ถ”์  ์ถœ๋ ฅ ํ™œ์„ฑํ™” ํ”Œ๋ž˜๊ทธ | `run_loop.sh:77` | N/A | `[Observed]` | +| `--cleanup` | `false` | ๋ฃจํ”„ ์„ฑ๊ณต ์ข…๋ฃŒ ์‹œ ์ƒ์„ฑ๋œ ์ž„์‹œ ์ž‘์—… ๋””๋ ‰ํ„ฐ๋ฆฌ(`.mam/jobs/`) ๋ฐ ๊ฐ€์ž…์ž ์ถœ๋ ฅ ํŒŒ์ผ ์‚ญ์ œ | `run_loop.sh:78, 921-930` | N/A | `[Observed]` | +| `-h`, `--help` | N/A | ๋„์›€๋ง ์ถœ๋ ฅ ํ›„ **exit 1**๋กœ ๋น„์ •์ƒ ์ข…๋ฃŒ (ํ˜„์žฌ ์ฝ”๋“œ์˜ ๊ด€ํ–‰์  ๋™์ž‘) | `run_loop.sh:50, 86` | `test_loop_cli.py:test_help_lists_creator_not_target_agent` | `[Observed]` | + +> โš ๏ธ **Help Exit Code Gap (`[Gap]`)**: +> ํ˜„์žฌ `run_loop.sh:50`์€ `usage()` ๋‚ด๋ถ€์—์„œ `exit 1`์„ ํ˜ธ์ถœํ•˜์—ฌ `--help` ์š”์ฒญ ์‹œ์—๋„ exit 1์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ํ‘œ์ค€ CLI ๊ด€๋ก€(exit 0)์™€ ๋‹ค๋ฅด๋‚˜, ๊ธฐ์กด ์„œ๋ธŒํ”„๋กœ์„ธ์Šค ํ…Œ์ŠคํŠธ๋“ค์ด `exit 1`์— ์˜์กดํ•˜๋ฏ€๋กœ Phase 0์—์„œ๋Š” ํ˜„ํ–‰ ๋™์ž‘์„ ์œ ์ง€ํ•˜๋ฉฐ ์˜๋„์  ๋ณ€๊ฒฝ์€ Phase 3 wrapper ์Šน๊ฒฉ ์‹œ ๋ณ„๋„ Decision์œผ๋กœ ๋‹ค๋ฃน๋‹ˆ๋‹ค. + +### 2-2. ํ™˜๊ฒฝ, ๊ฒฝ๋กœ ๋ฐ ์ˆ˜๋ช…์ฃผ๊ธฐ ๊ณ„์•ฝ + +1. **ํŒŒ์ด์ฌ ์ธํ„ฐํ”„๋ฆฌํ„ฐ ํƒ์ƒ‰ (`_delegate_py_bin`)**: + - `AGENT_PYTHON_BIN` โž” `VIRTUAL_ENV/bin/python` โž” ์ƒ์œ„ ๋””๋ ‰ํ„ฐ๋ฆฌ `.venv/bin/python` ํƒ์ƒ‰ โž” ์‹œ์Šคํ…œ `python3` ์ˆœ์œผ๋กœ ์บ์‹ฑ ๋ฐ ๊ฒฐ์ • (`lib.sh:1841-1861`). `[Observed]` +2. **๋Ÿฐํƒ€์ž„ ๊ณ ์ • ๋ณต์‚ฌ๋ณธ (B-13 Freeze Snapshot)**: + - ๋ฃจํ”„ ๊ธฐ๋™ ์‹œ `.agents/skills/` ํŠธ๋ฆฌ๋ฅผ `/tmp/mam-loop-freeze.XXXXXX`๋กœ ๋ณต์‚ฌ ํ›„ ํ•ด๋‹น ์œ„์น˜์˜ `run_loop.sh`๋กœ `exec` ์žฌ์‹คํ–‰ (`run_loop.sh:112-127`). `[Observed]` + - ๋‹จ, `multi-agent-mux-create`, `resume`, `stop` ๋“ฑ 1ํšŒ์„ฑ ์Šคํฌ๋ฆฝํŠธ๋Š” Live Tree๋ฅผ ์ฐธ์กฐ (`[Observed]`). +3. **๋ฃจํ”„ ๊ฐ€๋“œ ๋ฐ ์‹œ๊ทธ๋„ ์ •๋ฆฌ**: + - `MAM_LOOP_MARKER`(`.mam/loop-guard-active`)์— `pid`, `lstart`, `started` ํƒ€์ž„์Šคํƒฌํ”„๋ฅผ ์›์ž์  ํ•˜๋“œ๋งํฌ๋กœ ๊ธฐ๋ก (`loop_lock.sh:69-75`). `[Observed]` + - `trap _mam_release_guard EXIT INT TERM HUP`์„ ํ†ตํ•ด ๋น„์ •์ƒ ์ข…๋ฃŒ ์‹œ์—๋„ ์•ˆ์ „ํ•˜๊ฒŒ ๊ฐ€๋“œ ํŒŒ์ผ ๋ฐ Freeze ์ž„์‹œ ๋””๋ ‰ํ„ฐ๋ฆฌ ์ •๋ฆฌ (`run_loop.sh:189`). `[Observed]` +4. **์ž‘์—… ์œ„์ž„ ํ•จ์ˆ˜ ๊ณ„์•ฝ**: + - `delegate_submit_job`: `registry.py register`๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์ƒ์„ฑ๋œ 32-bit hex JID๋ฅผ stdout์œผ๋กœ๋งŒ ์ถœ๋ ฅ (`lib.sh:1876-1889`). `[Observed]` + - `delegate_publish_event`: Non-fatal ๊ณ„์•ฝ (`|| true`). ๋ˆ„๋ฝ๋œ JID, ์Šคํฌ๋ฆฝํŠธ ๋ถ€์žฌ, ๋ธŒ๋กœ์ปค ์ „์†ก ์‹คํŒจ ์‹œ์—๋„ 0์„ ๋ฐ˜ํ™˜ํ•˜์—ฌ ํ˜ธ์ถœ์ž๋ฅผ ์ค‘๋‹จ์‹œํ‚ค์ง€ ์•Š์Œ (`lib.sh:1897-1905`). `[Observed]` + - `start_watchdog`: ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ `reconcile.sh --subscribe --idle-timeout 0`๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ํ•ด๋‹น ๋ฐ๋ชฌ PID๋ฅผ stdout์œผ๋กœ ๋ฐ˜ํ™˜ (`lib.sh:1908-1938`). `[Observed]` + +--- + +## 3. ์ด๋ฒคํŠธ ์Šคํ‚ค๋งˆ ๋ฐ ์ˆ˜๋ช…์ฃผ๊ธฐ ๊ณ„์•ฝ (MQTT & Registry) + +### 3-1. ์ด๋ฒคํŠธ ์Šคํ‚ค๋งˆ v1 ๋ช…์„ธ +- ํ•„์ˆ˜ ํ•„๋“œ: `schema_version` (1), `seq` (๋‹จ์กฐ ์ฆ๊ฐ€ ์–‘์˜ ์ •์ˆ˜), `job_id` (JID), `event` (์ด๋ฒคํŠธ๋ช…), `timestamp` (ISO-8601 UTC `YYYY-MM-DDTHH:MM:SSZ`), `detail` (๋ฌธ์ž์—ด ์„ค๋ช…). +- ํ™•์žฅ ํ•„๋“œ: `data` (๋”•์…”๋„ˆ๋ฆฌ), `data.hmac_sig` (HMAC-SHA256 16์ง„์ˆ˜ ์„œ๋ช…). + +### 3-2. ์ด๋ฒคํŠธ์™€ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ์ƒํƒœ(Status) ๋งคํ•‘ ํ˜„ํ™ฉ + +```mermaid +stateDiagram-v2 + [*] --> pending: register_job + pending --> running: pick_pending / event 'started' + running --> running: event 'progress' (์ƒํƒœ ๋ณ€๊ฒฝ ์—†์Œ) + running --> running: event 'permission_required' (์ƒํƒœ ๋ณ€๊ฒฝ ์—†์Œ) + running --> completed: event 'completed' (Terminal) + running --> error: event 'error' (Terminal) + running --> cancelled: event 'cancelled' / stop_session (Terminal) +``` + +| ์ด๋ฒคํŠธ ์ด๋ฆ„ | ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ์ƒํƒœ ์ „์ด | ์ง€์†์„ฑ(Retain) | ์„ค๋ช… | ๋ฒ”์ฃผ | +|---|---|---|---|---| +| `started` | `running` | `false` | ์ž‘์—…์ž๊ฐ€ ์ž‘์—…์„ ์ˆ˜๋ฝํ•˜๊ณ  ์‹คํ–‰์„ ์‹œ์ž‘ํ•จ | `[Observed]` | +| `progress` | ์ „์ด ์—†์Œ (`running` ์œ ์ง€) | `false` | ์ค‘๊ฐ„ ์ง„ํ–‰ ๊ฒฝ๊ณผ ๋ณด๊ณ . ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ status๋Š” ๋ฏธ๋ณ€๊ฒฝ | `[Observed]` | +| `permission_required` | ์ „์ด ์—†์Œ (`running` ์œ ์ง€) | `false` | ์‚ฌ์šฉ์ž ๊ถŒํ•œ ์Šน์ธ ๋Œ€๊ธฐ. ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ status๋Š” ๋ฏธ๋ณ€๊ฒฝ | `[Observed]` | +| `completed` | `completed` | `true` | ์ž‘์—… ์„ฑ๊ณต ์™„๋ฃŒ. Terminal ์ƒํƒœ | `[Observed]` | +| `error` | `error` | `true` | ์ž‘์—… ์‹คํŒจ ์ข…๋ฃŒ. Terminal ์ƒํƒœ | `[Observed]` | +| `cancelled` | `cancelled` | `true` | ์„ธ์…˜ ์ค‘๋‹จ ๋“ฑ์— ์˜ํ•œ ์ทจ์†Œ. Terminal ์ƒํƒœ | `[Observed]` | + +> โš ๏ธ **์ด๋ฒคํŠธ-์ƒํƒœ ๊ฐ„๊ทน Gap (`[Gap]`)**: +> 1. `progress`์™€ `permission_required`๋Š” ์ด๋ฒคํŠธ๋กœ ๋ฐœํ–‰๋˜์ง€๋งŒ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ `status` enum(`VALID_STATUSES = ('pending', 'running', 'completed', 'error', 'cancelled')`)์— ํฌํ•จ๋˜์ง€ ์•Š์•„, ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ํŒŒ์ผ๋งŒ์œผ๋กœ๋Š” ๊ถŒํ•œ ์Šน์ธ ๋Œ€๊ธฐ ์—ฌ๋ถ€๋ฅผ ์ง์ ‘ ์•Œ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค (`mqtt_common.py:44-48`). +> 2. `publish_event.py`์˜ Terminal ์ด๋ฒคํŠธ๋Š” `("completed", "error", "cancelled")`์ด์ง€๋งŒ, `job_subscriber.py:46`์˜ Terminal ์ด๋ฒคํŠธ๋Š” `("completed", "error")`๋กœ ์ •์˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ฐ€์ž…์ž๊ฐ€ ๋””์Šคํฌ ํด๋ฐฑ(`_check_disk_fallback`)์„ ์ˆ˜ํ–‰ํ•  ๋•Œ ๋น„๋กœ์†Œ `cancelled` ์ƒํƒœ๋ฅผ ๊ฐ์ง€ํ•˜์—ฌ ๊ฐ€์ƒ `error` ์ด๋ฒคํŠธ(`synth_event="error"`)๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค (`job_subscriber.py:80`). +> 3. **Terminal Non-reentry (`[Proposed]` vs `[Gap]`)**: ํ•œ ๋ฒˆ Terminal ์ƒํƒœ์— ๋„๋‹ฌํ•œ ํ›„ ์ค‘๋ณต/์ƒ์ถฉ ์ด๋ฒคํŠธ์— ์˜ํ•ด ์ƒํƒœ๊ฐ€ ์—ญํ–‰ํ•˜์ง€ ์•Š๋Š” ๋ถˆ๋ณ€์‹์€ Phase 2a์˜ ๋ชฉํ‘œ ๊ณ„์•ฝ(`[Proposed]`)์ด๋ฉฐ, ํ˜„ํ–‰ `update_job_status`๋Š” ๋ฝ ๋‚ด์—์„œ ๋‹จ์ˆœ ํ•„๋“œ ๋ฎ์–ด์“ฐ๊ธฐ(`record.update(fields)`)๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ฏ€๋กœ ์™„๋ฒฝํ•œ ๋น„์—ญํ–‰์ด ๋ณด์žฅ๋˜์ง€ ์•Š๋Š” ๊ฐ„๊ทน(`[Gap]`)์ด ์žˆ์Šต๋‹ˆ๋‹ค. + +### 3-3. ๋ณด์•ˆ ๋ฐ ์žฌ์ƒ ๊ณต๊ฒฉ ๋ฐฉ์–ด +1. **์ธ์ฆ ๋ชจ๋“œ ๋ถ„๋ฆฌ**: `auth_token == null`์ผ ๋•Œ๋Š” HMAC ๊ฒ€์ฆ ํ†ต๊ณผ (PoC ๋ชจ๋“œ). `auth_token != null`์ผ ๋•Œ๋Š” `data.hmac_sig` ๊ฒ€์ฆ ์‹คํŒจ ์‹œ ์ฆ‰์‹œ ํ๊ธฐ (`job_subscriber.py:122-126`). `[Observed]` +2. **๋‹จ์กฐ ์‹œํ€€์Šค ๊ฒ€์ฆ**: ๊ฐ€์ž…์ž๋Š” ์ˆ˜์‹ ๋œ ํŽ˜์ด๋กœ๋“œ์˜ `seq`๊ฐ€ ์ด์ „ `last_seq`๋ณด๋‹ค ํฐ ๊ฒฝ์šฐ์—๋งŒ ์ฒ˜๋ฆฌํ•˜๊ณ , ๊ฐ™๊ฑฐ๋‚˜ ์ž‘์€ ํŒจํ‚ท์€ replay ๊ณต๊ฒฉ์œผ๋กœ ๊ฐ„์ฃผํ•˜์—ฌ drop (`job_subscriber.py:127-136`). `[Observed]` +3. **๋กœ์ปฌ ๊ฐ์‚ฌ ์ถ”์ (Audit Log)**: MQTT ์ „์†ก ์„ฑ๊ณต/์‹คํŒจ์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ `append_event`๋ฅผ ํ†ตํ•ด `.mam/delegate_job_logs//{meta,events,status}`์— ๋ชจ๋“  ์‹œ๋„ ๊ธฐ๋ก (`mqtt_common.py:122, publish_event.py:208-219`). `[Observed]` +4. **MQTT ์ „์†ก ์‹คํŒจ ์‹œ ๋กœ์ปฌ ์ƒํƒœ ๊ธฐ๋ก**: ๋ธŒ๋กœ์ปค ์ ‘์† ๋ถˆ๊ฐ€ ์‹œ์—๋„ `publish_event.py`๋Š” ๋กœ์ปฌ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ์˜ ์ƒํƒœ๋ฅผ `new_status`๋กœ ๋™๊ธฐํ™”ํ•œ ๋’ค exit code 2๋กœ ์ข…๋ฃŒ (`publish_event.py:223-233`). `[Observed]` + +--- + +## 4. ๋ฆฌํฌํŠธ ํŒŒ์‹ฑ ๋ฐ ์Šน๊ฒฉ ๊ณ„์•ฝ (Report & Promotion) + +### 4-1. ํŒ์ • ํ† ํฐ(Verdict) ์ถ”์ถœ ๋กœ์ง +- `run_loop.sh:204-210`: `has_verdict "$file" "$verdict"`๋Š” ํŒŒ์ผ์˜ **๋งˆ์ง€๋ง‰ ๋น„๊ณต๋ฐฑ ํ–‰(last non-blank line)**์„ ์ถ”์ถœํ•˜์—ฌ `^\[VERDICT: ${verdict}\][[:space:]]*\r?$` ํŒจํ„ด๊ณผ ์ผ์น˜ํ•˜๋Š”์ง€ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค. +- `[Gap]`: ๋งˆ์ง€๋ง‰ ํ–‰์ด ๋งˆํฌ๋‹ค์šด ์ฝ”๋“œ ๋ธ”๋ก(```) ๋‚ด๋ถ€์— ์žˆ๋Š” ์˜ˆ์ œ ๋ฌธ์ž์—ด์ธ ๊ฒฝ์šฐ์—๋„ ํ˜„์žฌ ์…ธ ์ •๊ทœ์‹์€ ์ด๋ฅผ ์œ ํšจํ•œ ํŒ์ • ํ† ํฐ์œผ๋กœ ์˜ค์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Phase 2์˜ ์—„๊ฒฉํ•œ ํŒŒ์„œ(Strict Parser)๋Š” ์ฝ”๋“œ ํŽœ์Šค ๋‚ด๋ถ€๋ฅผ ์ œ์™ธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +- ๋ฆฌํฌํŠธ ํŒŒ์ผ ์œ„์น˜ ํƒ์ƒ‰(`find_report`): `find ".mam/jobs/$1" -maxdepth 2 -name "report-final.md" | head -n 1`๋กœ ์ฒซ ๋ฒˆ์งธ ์ผ์น˜ ํŒŒ์ผ์„ ์ทจํ•˜๋ฉฐ, ์ •ํ™•ํ•œ ๋‹จ์ผ ๊ฒฝ๋กœ ๊ฐ•์ œ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค (`run_loop.sh:234`). `[Observed]` + +### 4-2. ์Šน๊ฒฉ(Promotion) ๋ฐ ์ •๋ฆฌ(Cleanup) ๊ณ„์•ฝ +1. **๊ณ„ํš์„œ ์Šน๊ฒฉ**: `CURRENT_PLAN`์„ `.agents/reports/$PLANNER_SESSION/plan-${PLAN_JOB_ID}.md.tmp`์— ๊ธฐ๋ก ํ›„ `mv -f`๋กœ ์›์ž์  ๊ต์ฒด (`run_loop.sh:895-901`). `[Observed]` +2. **๋ฆฌ๋ทฐ ๋ฆฌํฌํŠธ ์Šน๊ฒฉ**: ๊ฐ ๋ฆฌ๋ทฐ์–ด์˜ `report-final.md`๋ฅผ `.agents/reports/$rev/report-${job_id}.md`๋กœ ๋ณต์‚ฌ ๋ฐ ์ด๋™ (`run_loop.sh:903-916`). `[Observed]` +3. **๋ฐฐ์น˜ ๋น„์›์ž์„ฑ (`[Gap]`)**: ๋‹ค์ˆ˜์˜ ๋ฆฌ๋ทฐ ๋ฆฌํฌํŠธ๋ฅผ ์Šน๊ฒฉํ•  ๋•Œ ๊ฐœ๋ณ„ ํŒŒ์ผ ๋‹จ์œ„๋กœ `cp && mv`๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ฏ€๋กœ, ์ค‘๊ฐ„ ํฌ๋ž˜์‹œ ๋ฐœ์ƒ ์‹œ ์ผ๋ถ€ ๋ฆฌํฌํŠธ๋งŒ ์Šน๊ฒฉ๋˜๋Š” ๋ถ€๋ถ„ ์„ฑ๊ณต ์œ„ํ—˜์ด ์กด์žฌํ•ฉ๋‹ˆ๋‹ค. + +--- + +## 5. ์ž ๊ธˆ(Lock) ๋ฐ ๋™์‹œ์„ฑ ์ œ์–ด ๊ณ„์•ฝ + +### 5-1. ๋ฝ ์ฒด๊ณ„ ๋ฐ ํš๋“ ์ˆœ์„œ +1. **Workspace Loop Guard**: `.mam/loop-guard-active` (์›์ž์  ํ•˜๋“œ๋งํฌ + `pid`/`lstart` ์Šคํ‹ธ ์žฌ์‹œ๋„). +2. **Session Registry Lock**: `.mam/agent-sessions.db` (SQLite WAL ๋™์‹œ์„ฑ ๋ฐ ํŠธ๋žœ์žญ์…˜). +3. **Job Registry Lock**: `.mam/jobs/.lock` ๋˜๋Š” ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๋””๋ ‰ํ„ฐ๋ฆฌ ๋ฝ (`fcntl.flock(LOCK_EX)`). + +### 5-2. `update_job_status`์˜ ๋น„-CAS ํŠน์„ฑ (`[Gap]`) +- ํ˜„์žฌ `mqtt_common.py:377-403`์˜ `update_job_status`๋Š” ๋ฝ์„ ํš๋“ํ•œ ํ›„ `record.update(fields)`๋กœ ๋‹จ์ˆœํžˆ ํ•„๋“œ๋ฅผ ๋ฎ์–ด์“ฐ๋ฉฐ, ํ˜ธ์ถœ์ž๊ฐ€ ๊ธฐ๋Œ€ํ•œ ์ด์ „ ์ƒํƒœ(`expected_status`)๋‚˜ attempt ๋ฒ„์ „์„ ๋น„๊ตํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +- ๋˜ํ•œ `registry.py:129-131`์˜ `pick_pending`์€ `update_job_status`๋ฅผ ๊ฑฐ์น˜์ง€ ์•Š๊ณ  ์ง์ ‘ ํŒŒ์ผ์— `chosen["status"] = "running"`์„ ์”๋‹ˆ๋‹ค. +- **`[Proposed]`**: Phase 2a์—์„œ๋Š” ๋ชจ๋“  ์ƒํƒœ ๋ณ€๊ฒฝ์ด ๋‹จ์ผ ์—”์ง„์˜ ์›์ž์  CAS(Compare-And-Swap) ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ†ตํ•ด์„œ๋งŒ ์ˆ˜ํ–‰๋˜๋„๋ก ํ†ต์ผํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +--- + +## 6. ์†Œ์Šค ๋ฐ ํ…Œ์ŠคํŠธ ๋งคํ•‘ ์š”์•ฝ + +| ๊ตฌ์„ฑ์š”์†Œ | ํ•ต์‹ฌ ์†Œ์Šค ํŒŒ์ผ | ํ•ต์‹ฌ ํ…Œ์ŠคํŠธ ํŒŒ์ผ | ๊ธฐ์ค€์„  ์ƒํƒœ | +|---|---|---|---| +| ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ CLI | `run_loop.sh` | `tests/test_loop_cli.py` | 9๊ฐœ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ | +| ๋ฃจํ”„ ๊ฐ€๋“œ ๋ฝ | `loop_lock.sh` | `tests/test_o2_race_free_lock.py` | 22๊ฐœ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ | +| ๋ฐ˜๋ก  ์ƒํ•œ์„  | `run_loop.sh` | `tests/test_o1_rebuttal.py` | 10๊ฐœ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ | +| ์œ„์ž„ ํ—ฌํผ | `lib.sh` | `tests/test_tier1_unit.py` | ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ | +| ์ž‘์—… ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ | `registry.py`, `mqtt_common.py` | `tests/test_tier2_component.py` | fcntl ๊ธฐ๋ฐ˜ ๋ฝํ‚น | diff --git a/docs/contracts/issue-5/herdr-runtime.md b/docs/contracts/issue-5/herdr-runtime.md new file mode 100644 index 0000000..0c3adaa --- /dev/null +++ b/docs/contracts/issue-5/herdr-runtime.md @@ -0,0 +1,111 @@ +# ๐Ÿ“‘ Herdr Runtime Contract and Backend Capability Matrix + +- **Document Version**: Rev.3 (Post Cross-Review Corrections) +- **Issue**: [Gitea Issue #5](https://git.godopu.com/tmpl/multi-agent-mux/issues/5) +- **Target Herdr Version**: `herdr 0.8.2` +- **Scope**: Native Herdr API Contract, External Collector Specification, and Backend Matrix + +--- + +## 1. Herdr 0.8.2 ๋„ค์ดํ‹ฐ๋ธŒ API ๊ณ„์•ฝ (Native API Contract) + +### 1-1. ํ•ต์‹ฌ CLI ๋ช…๋ น์–ด ๋ฐ ๋ฐ˜ํ™˜ ํŽ˜์ด๋กœ๋“œ +`herdr`๋Š” AI ์ฝ”๋”ฉ ์—์ด์ „ํŠธ ์ „์šฉ ํ„ฐ๋ฏธ๋„ ๋ฉ€ํ‹ฐํ”Œ๋ ‰์„œ๋กœ์„œ, ์†Œ์ผ“ API๋ฅผ ํ†ตํ•ด ํ„ฐ๋ฏธ๋„ ์Šคํฌ๋ฆฐ ๊ฐ์‹œ ์ƒํƒœ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค: + +1. **`herdr agent list`**: ํ˜„์žฌ ํ™œ์„ฑํ™”๋œ ๋ชจ๋“  ์—์ด์ „ํŠธ ๋ชฉ๋ก ๋ฐ ์ƒํƒœ ๋ฐ˜ํ™˜ (JSON stdout, exit 0). +2. **`herdr agent get `**: ํŠน์ • ์—์ด์ „ํŠธ์˜ ๋‹จ์ผ ์‹œ์  ์Šค๋ƒ…์ƒท ๋ฐ˜ํ™˜ (`agent_status`, `interactive_ready`, `pane_id`, `revision`, `state_change_seq` ๋“ฑ). +3. **`herdr agent wait [OPTIONS]`**: ์—์ด์ „ํŠธ๊ฐ€ ์ง€์ •๋œ ์ƒํƒœ์— ๋„๋‹ฌํ•  ๋•Œ๊นŒ์ง€ ๋ธ”๋กœํ‚น ๋Œ€๊ธฐ. +4. **`herdr agent explain [OPTIONS] [target]`**: ๋‚ด๋ถ€ ์ •๊ทœ์‹ ๋งค์นญ ๊ทœ์น™(`matched_rule`, `evaluated_rules`) ํ‰๊ฐ€ ์„ธ๋ถ€์ •๋ณด ๋ฐ˜ํ™˜. + +### 1-2. ๋„ค์ดํ‹ฐ๋ธŒ ์ƒํƒœ ์ง‘ํ•ฉ (`agent_status`) +- **์ง€์› Enum**: `idle`, `working`, `blocked`, `done`, `unknown` (์ด 5์ข…). +- **`interactive_ready` (boolean)**: + - ํ„ฐ๋ฏธ๋„์ด ํ‚ค ์ž…๋ ฅ์„ ์ˆ˜์‹ ํ•  ์ค€๋น„๊ฐ€ ๋˜์—ˆ๋Š”์ง€๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ํ”Œ๋ž˜๊ทธ. + - **์‹ค์ธก ๋ถˆ๋ณ€์‹**: ์—์ด์ „ํŠธ๊ฐ€ ์ฝ”๋”ฉ ์ž‘์—… ์ค‘(`agent_status: "working"`)์ผ ๋•Œ๋„ `interactive_ready: true`์ž„์ด ์ž…์ฆ๋จ. ๋”ฐ๋ผ์„œ `interactive_ready` ๋‹จ๋…์œผ๋กœ๋Š” ์ž‘์—… ์™„๋ฃŒ์˜ ์ถฉ๋ถ„์กฐ๊ฑด์ด ๋  ์ˆ˜ ์—†์œผ๋ฉฐ, ์˜ค์ง `agent_status in {"idle", "done"}` ์ƒํƒœ์™€ ๊ฒฐํ•ฉ๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +### 1-3. `herdr agent wait` ํ˜ธ์ถœ ๊ทœ๊ฒฉ ๋ฐ ์ฑ„๋„ ๋ถ„๋ฆฌ ์‹ค์ธก +์‹ค์ œ `herdr 0.8.2` ์‹คํ–‰(`wait-contract.json` ๋ฐ `evidence/timeout_stderr.json`)์„ ํ†ตํ•ด ๊ฒ€์ฆ๋œ ์ž…์ถœ๋ ฅ ์ฑ„๋„ ๋ฐ ์ข…๋ฃŒ ์ฝ”๋“œ ๊ณ„์•ฝ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค: + +| ํ˜ธ์ถœ ์กฐ๊ฑด | ๋ช…๋ น ์˜ˆ์‹œ | ์ข…๋ฃŒ ์ฝ”๋“œ | ์ถœ๋ ฅ ์ฑ„๋„ | ๋ฐ˜ํ™˜ JSON ํŽ˜์ด๋กœ๋“œ ๊ตฌ์กฐ | +|---|---|---|---|---| +| **์ƒํƒœ ๋„๋‹ฌ (์„ฑ๊ณต)** | `herdr agent wait creator-agy-01 --until working --timeout 1000` | `exit 0` | `stdout` | `{"id":"cli:agent:wait","result":{"agent":{...},"type":"agent_info"}}` | +| **ํƒ€์ž„์•„์›ƒ (Timeout)** | `herdr agent wait creator-agy-01 --until done --timeout 50` | `exit 1` | **`stderr`** | `{"error":{"code":"timeout","message":"timed out waiting for agent status"},"id":"cli:agent:wait"}` | +| **๋Œ€์ƒ ๋ถ€์žฌ (Unknown)** | `herdr agent wait non-existent --until idle --timeout 50` | `exit 1` | **`stderr`** | `{"error":{"code":"agent_not_found","message":"agent target non-existent not found"},"id":"cli:agent:wait"}` | +| **๊ธฐ๋ณธ ๋Œ€๊ธฐ ์˜ต์…˜ ์ƒ๋žต** | `herdr agent wait ` | N/A | N/A | `--until` ์ƒ๋žต ์‹œ ๊ธฐ๋ณธ ๋งค์นญ ๋Œ€์ƒ์€ `idle, done, blocked`์ž„. `--timeout` ์ƒ๋žต ์‹œ ๋ฌด๊ธฐํ•œ ๋Œ€๊ธฐํ•จ | + +> ๐Ÿšจ **Critical Empirical Finding (์ˆ˜์ • ๋ฐ˜์˜)**: +> `herdr agent wait`์—์„œ ์„ฑ๊ณต(์ƒํƒœ ์ผ์น˜) ์‹œ์—๋Š” ์ •์ƒ JSON์ด `stdout`์œผ๋กœ ์ถœ๋ ฅ๋˜์ง€๋งŒ, **ํƒ€์ž„์•„์›ƒ(`timeout`) ๋ฐ ๋Œ€์ƒ ๋ถ€์žฌ(`agent_not_found`) ๋“ฑ ๋ชจ๋“  ์—๋Ÿฌ ์‘๋‹ต์€ `stderr`๋กœ ์—๋Ÿฌ JSON์ด ์ถœ๋ ฅ๋˜๋ฉฐ ์ข…๋ฃŒ ์ฝ”๋“œ๋Š” `exit 1`**์ž…๋‹ˆ๋‹ค (`stdout`์€ ๋น„์–ด์žˆ์Œ). ๋”ฐ๋ผ์„œ Watcher ์—”์ง„์€ `exit 1` ์‹œ ๋ฐ˜๋“œ์‹œ `stderr` ์ŠคํŠธ๋ฆผ์—์„œ JSON ์—๋Ÿฌ ํŽ˜์ด๋กœ๋“œ๋ฅผ ํŒŒ์‹ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +--- + +## 2. ์™ธ๋ถ€ ์‹œํ€€์Šค ์ˆ˜์ง‘๊ธฐ ๊ณ„์•ฝ (External Sequence Collector) + +### 2-1. ๋‹จ์ผ `wait` ํ˜ธ์ถœ์˜ ๋ณธ์งˆ์  ํ•œ๊ณ„ +- `herdr agent wait`๋Š” ํƒ€๊ฒŸ์ด ํŠน์ • ์ƒํƒœ์— ๋„๋‹ฌํ•œ ์‹œ์ ์— **๋‹จ 1๊ฐœ์˜ ์ตœ์ข… ์Šค๋ƒ…์ƒท JSON**๋งŒ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋™๊ธฐ์  ์žฅ๋ฒฝ(Barrier)์ž…๋‹ˆ๋‹ค. +- ํ˜ธ์ถœ ๋„์ค‘ ๊ฑฐ์ณ๊ฐ„ ์ค‘๊ฐ„ ์ „์ด ๊ณผ์ •(Transition Stream)์„ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +- ๋”ฐ๋ผ์„œ `working โž” idle`๊ณผ ๊ฐ™์€ ์ƒํƒœ ์ „์ด๋ฅผ ์ž…์ฆํ•˜๋ ค๋ฉด, ์™ธ๋ถ€ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ/์ˆ˜์ง‘๊ธฐ๊ฐ€ ์‚ฌ์ „ ์Šค๋ƒ…์ƒท(`before`)๊ณผ ์‚ฌํ›„ ์Šค๋ƒ…์ƒท(`after`)์„ ๋…๋ฆฝ์ ์œผ๋กœ ํš๋“ํ•˜์—ฌ ํƒ€์ž„์Šคํƒฌํ”„์™€ ํ•จ๊ป˜ ์—ฐ๊ฒฐํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +### 2-2. ์ˆ˜์ง‘๊ธฐ(Collector) ๊ถŒ์žฅ ๊ทœ๊ฒฉ +- **ํด๋ง ์ฃผ๊ธฐ**: 1~2์ดˆ ์ˆ˜์ค€์˜ ์œ ๊ณ„(Bounded) ํด๋ง (`agent get`). ๊ณ ์ •๋ฐ€ 10ms ์Šคํ•€ ํด๋ง์€ ๋ถˆํ•„์š”ํ•˜๋ฉฐ CPU ๋‚ญ๋น„๋ฅผ ์ดˆ๋ž˜ํ•ฉ๋‹ˆ๋‹ค. +- **Envelope ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ**: ๋‹จ์กฐ ์‹œ๊ณ„ ์˜คํ”„์…‹(`monotonic_start`, `monotonic_end`), `collector_clock_id`, `capture_id`, `source_command`๋ฅผ ํ•„์ˆ˜๋กœ ๋ณด์กดํ•˜์—ฌ ์‹œ์  ๊ฐ„ ์™œ๊ณก์„ ๋ฐฉ์ง€ํ•ฉ๋‹ˆ๋‹ค. + +--- + +## 3. ์ด๋ฒคํŠธ, ํŒŒ์ผ์‹œ์Šคํ…œ ๋ฐ ๋””์ŠคํŒจ์น˜ ์—ฐ๊ณ„ ๊ฒฝ๊ณ„ + +Herdr ๋„ค์ดํ‹ฐ๋ธŒ ํŽ˜์ด๋กœ๋“œ์™€ Multi-Agent-Mux ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์‚ฌ์ด์—๋Š” ์—„๊ฒฉํ•œ ์ฑ…์ž„ ๊ฒฉ๋ฆฌ๊ฐ€ ์œ ์ง€๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค: + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Herdr Native API Boundary โ”‚ +โ”‚ โ€ข agent_status (idle/working/blocked/done/unknown) โ”‚ +โ”‚ โ€ข interactive_ready (true/false) โ”‚ +โ”‚ โ€ข pane_id, state_change_seq, terminal_title โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ + โ”‚ ๊ด€์ธก ์ฆ๊ฑฐ ์ œ๊ณต (์ฑ„๋„ 2) + โ–ผ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ Multi-Agent-Mux Watcher Boundary โ”‚ +โ”‚ โ€ข 4-Fold Attribution: โ”‚ +โ”‚ (job_id, attempt_id, session_uuid, โ”‚ +โ”‚ pane_process_generation) โ”‚ +โ”‚ โ€ข File Evidence (report mtime debounce >=15s, hash) โ”‚ +โ”‚ โ€ข Backplane Events (seq, HMAC signature, started/progress)โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +- **๋ถˆ๋ณ€์‹**: Herdr ์›๋ณธ ์‘๋‹ต ํŽ˜์ด๋กœ๋“œ์—๋Š” `job_id`, `attempt_id`, `report phase`, `expected_decision` ๋“ฑ์˜ MAM ๋‚ด๋ถ€ ํ•„๋“œ๋ฅผ ์ ˆ๋Œ€ ์ฃผ์ž…ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +--- + +## 4. 6๊ฐœ ๋ฐฑ์—”๋“œ ๊ธฐ๋Šฅ ๋ฐ ์‹ค์ธก ์ฆ๊ฑฐ ํ–‰๋ ฌ (Capability Matrix) + +๋ณธ ํ”„๋กœ์ ํŠธ๊ฐ€ ์ง€์›ํ•˜๋Š” 6๊ฐœ ์—์ด์ „ํŠธ ๋ฐฑ์—”๋“œ(`claude`, `agy`, `hermes`, `grok`, `opencode`, `codex`)์˜ ํ˜„ ํ™˜๊ฒฝ ์‹ค์ธก ์ƒํƒœ ๋ฐ Gap ํ˜„ํ™ฉ์ž…๋‹ˆ๋‹ค (๋ชจ๋“  ์Šฌ๋กฏ์€ `tests/fixtures/issue_5/herdr/manifest.json`๊ณผ 100% ์ผ์น˜): + +| ๋ฐฑ์—”๋“œ | ์„ค์น˜ ๋ฐ”์ด๋„ˆ๋ฆฌ ๊ฒฝ๋กœ | ํ™œ์„ฑ ์„ธ์…˜๋ช… | ์‹ค์ธก ์บก์ฒ˜ ์Šฌ๋กฏ | ๋ฏธ๊ด€์ธก ์Šฌ๋กฏ (Gaps) ๋ฐ ์‚ฌ์œ  | Provenance | +|---|---|---|---|---|---| +| **`claude`** | `~/.local/bin/claude` | `planner-reviewer-claude-01` | `idle` (`claude/idle.json`) | `working`, `blocked`, `done`, `unknown` (์บก์ฒ˜ ์‹œ์ ์— idle ์ƒํƒœ์˜€์Œ) | `observed` | +| **`agy`** | `~/.local/bin/agy` | `creator-agy-01` | `working` (`agy/working.json`), `wait_timeout` (`agy/wait_timeout.json`) | `idle`, `blocked`, `done`, `unknown` (์ž‘์—… ์ˆ˜ํ–‰ ์ค‘์ด๋ฉฐ ๊ถŒํ•œ ๊ฑด๋„ˆ๋›ฐ๊ธฐ ์ ์šฉ) | `observed` | +| **`grok`** | `~/.grok/bin/grok` | `reviewer-creator-grok-01` | `idle` (`grok/idle.json`) | `working`, `blocked`, `done`, `unknown` (๋ฆฌ๋ทฐ ๋Œ€๊ธฐ ์ค‘ ์œ ํœด ์ƒํƒœ ์บก์ฒ˜) | `observed` | +| **`codex`** | `~/.local/bin/codex` | `planner-reviewer-codex-01` | `done` (`codex/done.json`) | `idle`, `working`, `blocked`, `unknown` (์ด์ „ ํ”Œ๋ž˜๋„ˆ ์ž‘์—… ์™„๋ฃŒ ์งํ›„ ์บก์ฒ˜) | `observed` | +| **`hermes`** | `~/.local/bin/hermes` | N/A (๋ฏธ๊ฐ€๋™) | ์—†์Œ | `idle`, `working`, `blocked`, `done`, `unknown` (Herdr ์„œ๋ฒ„์— ํ™œ์„ฑ ์„ธ์…˜ ๋ถ€์žฌ) | `unobserved` | +| **`opencode`**| `~/.opencode/bin/opencode` | N/A (๋ฏธ๊ฐ€๋™) | ์—†์Œ | `idle`, `working`, `blocked`, `done`, `unknown` (Herdr ์„œ๋ฒ„์— ํ™œ์„ฑ ์„ธ์…˜ ๋ถ€์žฌ) | `unobserved` | + +> ๐Ÿ“Œ **์ •์งํ•œ ์ฆ๊ฑฐ ๋ณด๊ณ  ์›์น™ (D3, D6)**: +> 1. ์˜ค์ง ์‹ค์ œ๋กœ ์‹คํ–‰ํ•˜์—ฌ ์บก์ฒ˜ํ•œ ์Šฌ๋กฏ๋งŒ `observed`๋กœ ์ธ์ •ํ•˜๋ฉฐ, ๋ชจ์˜ ์‹คํ–‰์ด๋‚˜ ๊ฐ€์ƒ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์„ ๋„ค์ดํ‹ฐ๋ธŒ ์บก์ฒ˜๋กœ ๋‘”๊ฐ‘์‹œํ‚ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +> 2. `hermes`์™€ `opencode`์˜ ๋ฏธ๊ฐ€๋™ ์Šฌ๋กฏ ๋ฐ ๊ธฐ์กด 4๊ฐœ ๋ฐฑ์—”๋“œ์˜ ๋ฏธ๊ด€์ธก ์ƒํƒœ๋Š” ๋ชจ๋‘ ์ •์งํ•˜๊ฒŒ `unobserved gap`์œผ๋กœ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. +> 3. Grok ์„ธ์…˜ ๋“ฑ์€ ์บก์ฒ˜ ์‹œ์ ์— `idle`์ด์—ˆ์œผ๋‚˜ ๋ฆฌ๋ทฐ ์‹คํ–‰ ์‹œ `working`์œผ๋กœ ์ „์ด๋˜๋ฏ€๋กœ, ์ •์  ์บก์ฒ˜๊ฐ€ ์—์ด์ „ํŠธ์˜ ์˜๊ตฌ ๋ถˆ๋ณ€ ๋Šฅ๋ ฅ์„ ๋œปํ•˜์ง€ ์•Š์Œ์„ ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค. + +--- + +## 5. ๋ณตํ•ฉ ์‹œ๋‚˜๋ฆฌ์˜ค ๊ธฐ๋Œ€ ์ •์ฑ… (Composite Expected Policy) + +Watcher๊ฐ€ ๋‹ค์ค‘ ์ฑ„๋„ ์ฆ๊ฑฐ๋ฅผ ๊ฒฐํ•ฉํ•˜์—ฌ ๋‚ด๋ฆฌ๋Š” ์ตœ์ข… ์™„๋ฃŒ ํŒ์ • ์ •์ฑ…: + +1. **`initial_idle`**: ๋””์ŠคํŒจ์น˜ ๊ธฐ๋ก์€ ์žˆ์œผ๋‚˜ ํ•ด๋‹น ์‹œ๋„์˜ ์‹คํ–‰ ์•ต์ปค๊ฐ€ ์—†์œผ๋ฉด โž” **`hold`** (์™„๋ฃŒ ๊ฑฐ๋ถ€). +2. **`tool_running`**: Herdr๋Š” `working`์ด๋‚˜ ํ•˜๋„ค์Šค์—์„œ ๋„๊ตฌ ์‹คํ–‰ ์ค‘์ด๋ฉด โž” **`hold`** (๋Œ€๊ธฐ). +3. **`report_pause_15s`**: ํŒŒ์ผ์ด 15์ดˆ๊ฐ„ ๋ฉˆ์ถ”์—ˆ์œผ๋‚˜ ํ™•์ • ๋งค๋‹ˆํŽ˜์ŠคํŠธ๊ฐ€ ์—†์œผ๋ฉด โž” **`hold`** (๋Œ€๊ธฐ). +4. **`finalized_candidate`**: 4์ค‘ ๊ท€์† ์ผ์น˜ + Fresh `idle/done` + ์›์ž์  ๋งค๋‹ˆํŽ˜์ŠคํŠธ/ํ•ด์‹œ ์ผ์น˜ โž” **`candidate_approved`** (CAS ์ปค๋ฐ‹ ํ—ˆ์šฉ). +5. **`blocked_permission`**: Herdr๊ฐ€ `blocked`์ด๋ฉด โž” **`hold_blocked`** (์™„๋ฃŒ ๊ธˆ์ง€). +6. **`transport_disconnected`**: ์†Œ์ผ“ ํ†ต์‹  ์‹คํŒจ ์‹œ โž” **`hold_reconnect`** (์Šค๋ƒ…์ƒท ์žฌ๋™๊ธฐํ™”). +7. **`restarted_generation`**: PID/์„ธ์…˜ ์‹๋ณ„์ž๊ฐ€ ๋ณ€๊ฒฝ๋œ ๊ณผ๊ฑฐ ์‹œ๋„์˜ ์‹ ํ˜ธ โž” **`reject_stale`** (์ „์ด ๊ฑฐ๋ถ€). diff --git a/docs/contracts/issue-5/implementation_plan.md b/docs/contracts/issue-5/implementation_plan.md new file mode 100644 index 0000000..21e2a69 --- /dev/null +++ b/docs/contracts/issue-5/implementation_plan.md @@ -0,0 +1,63 @@ +# ๐Ÿ“‘ Issue #5 Implementation Plan (Phase 1โ€“4 Execution & Rollback Specification) + +- **Document Version**: Rev.4 (Post Revision Review Corrections) +- **Issue**: [Gitea Issue #5](https://git.godopu.com/tmpl/multi-agent-mux/issues/5) +- **Status**: Target Architecture Roadmap & Execution Gate Criteria + +--- + +## 1. ๊ฐœ์š” ๋ฐ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์›์น™ + +๋ณธ ๋ฌธ์„œ๋Š” Issue #5(Python ๋ชจ๋“ˆํ™” ๋ฐ `lib_py` ์Šน๊ฒฉ)์˜ ๋‹จ๊ณ„์  ๊ตฌํ˜„(Phase 1โ€“4) ์ˆœ์„œ, ํ•„์ˆ˜ ์™„๋ฃŒ ๊ธฐ์ค€(Gate), ๋กค๋ฐฑ ์ ˆ์ฐจ, ๊ทธ๋ฆฌ๊ณ  ๋ช…์‹œ์  ๋น„๋ฒ”์œ„(Out of Scope)๋ฅผ ๊ทœ์ •ํ•ฉ๋‹ˆ๋‹ค. + +### ๐Ÿ“Œ ํ•ต์‹ฌ ์›์น™: +1. **์ ์ง„์  ๊ฒฉ๋ฆฌ ๋ฐ ๋ฌด์ค‘๋‹จ ์ „ํ™˜**: ํ”„๋กœ๋•์…˜ ์…ธ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜(`run_loop.sh`)์€ Phase 3 ์ด์ „๊นŒ์ง€ ๊ณ„์†ํ•ด์„œ ํ™œ์„ฑ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๋ฉฐ, ํŒŒ์ด์ฌ ๋ชจ๋“ˆ์€ ๋…๋ฆฝ๋œ ํŒจํ‚ค์ง€(`lib_py`)๋กœ ๋‹จ๊ณ„์ ์œผ๋กœ ์Šน๊ฒฉ๋ฉ๋‹ˆ๋‹ค. +2. **๋™์ผ ์ž‘์—…๊ณต๊ฐ„ ๋‹ค์ค‘ Writer ์ ˆ๋Œ€ ๊ธˆ์ง€**: ๊ณผ๋„๊ธฐ์  ๋‹จ๊ณ„์—์„œ๋„ ํŠน์ • ์›Œํฌ์ŠคํŽ˜์ด์Šค์—๋Š” ์˜ค์ง ๋‹จ ํ•˜๋‚˜์˜ ์ƒํƒœ ์ œ์–ด์ž(์‹ ๊ทœ ์—”์ง„ ๋˜๋Š” ๊ตฌํ˜• ์…ธ ๋ฃจํ”„)๋งŒ ์‹คํ–‰๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. +3. **Fail-Closed ๋ฐ Rollback ๋ณด์žฅ**: ๊ฐ ๋‹จ๊ณ„๋Š” ์ด์ „ ๋ฒ„์ „์œผ๋กœ ์ฆ‰์‹œ ๋˜๋Œ๋ฆด ์ˆ˜ ์žˆ๋Š” ๋ช…ํ™•ํ•œ ๋กค๋ฐฑ ์†Œ์œ ๊ถŒ๊ณผ ์ฒดํฌํฌ์ธํŠธ ์ธ๊ณ„ ์ ˆ์ฐจ๋ฅผ ๊ฐ–์ถ”์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. + +--- + +## 2. ๋‹จ๊ณ„๋ณ„ ๊ตฌํ˜„ ๋‹จ์œ„ ๋ฐ ํ•„์ˆ˜ ์™„๋ฃŒ ๊ธฐ์ค€ (Roadmap & Gates) + +| ๋‹จ๊ณ„ | ์ž‘์—… ๋ชฉํ‘œ | ์„ธ๋ถ€ ๊ตฌํ˜„ ๋‚ด์šฉ | ํ•„์ˆ˜ ์™„๋ฃŒ ๊ฒŒ์ดํŠธ (Gate Criteria) | ๋กค๋ฐฑ ์ ˆ์ฐจ (Rollback Path) | +|---|---|---|---|---| +| **Phase 0**
*(ํ˜„์žฌ)* | **์‚ฌ์ „ ๊ณ„์•ฝ ๊ณ ์ • ๋ฐ Fixture ์ˆ˜์ง‘** | โ€ข CLI, ์ด๋ฒคํŠธ, ๋ฆฌํฌํŠธ, ๋ฝ ๊ณ„์•ฝ ๊ธฐ์ค€์„  ๋ฌธ์„œํ™”
โ€ข Herdr 0.8.2 ๋„ค์ดํ‹ฐ๋ธŒ ์บก์ฒ˜ ๋ฐ ๋ณตํ•ฉ Watcher ์‹œ๋‚˜๋ฆฌ์˜ค 2๊ณ„์ธต ๋ถ„๋ฆฌ
โ€ข 6๊ฐœ ๋ฐฑ์—”๋“œ ์‹ค์ธก ์ฆ๊ฑฐ ํ–‰๋ ฌ ์ˆ˜์ง‘ | โ€ข 484๊ฐœ ์ „์ฒด ํ…Œ์ŠคํŠธ ์ˆ˜์ง‘ ๋ฐ ํšŒ๊ท€ 0-Fail (100% ํ†ต๊ณผ)
โ€ข Fixture ์Šคํ‚ค๋งˆ ๋ฐ ์ฆ๊ฑฐ ๊ฒฝ๊ณ„ ๊ฒ€์ฆ ํ†ต๊ณผ
โ€ข ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ ๋ฌด๋ณ€๊ฒฝ diff ๊ฒ€์ฆ | ๋ฏธ์ถ”์  ์‚ฐ์ถœ๋ฌผ ์ฆ‰์‹œ ์‚ญ์ œ (`rm -rf docs/contracts/issue-5 tests/fixtures/issue_5 tests/test_issue5_phase0_contracts.py`) | +| **Phase 1** | **`lib_py/delegation/` ์ถ”์ถœ** | โ€ข `lib.sh` L1833 ์ดํ•˜ ์œ„์ž„ ํ—ฌํผ๋ฅผ ํŒŒ์ด์ฌ ์„œ๋น„์Šค ํด๋ž˜์Šค๋กœ ์ถ”์ถœ
โ€ข `start_watchdog`์„ ๋‚ด๋ถ€ ๋ช…์นญ `ensure_subscriber_daemon`์œผ๋กœ ๋ช…๋ช…(๊ธฐ์กด ํ•จ์ˆ˜ ๋ณ„์นญ ๋ณด์กด)
โ€ข ์žฅ์ˆ˜ ํ”„๋กœ์„ธ์Šค ๋‹จ์ผ MQTT ์—ฐ๊ฒฐ ์žฌ์‚ฌ์šฉ ํ”„๋กœํ† ํƒ€์ž… | โ€ข CLI/non-fatal/HMAC/seq/๋””์Šคํฌ ํด๋ฐฑ ํšŒ๊ท€ 100% ํ†ต๊ณผ
โ€ข ๋ฐ๋ชฌ ์ˆ˜๋ช… ๋ฐ PID ๊ณ„์•ฝ ์ผ์น˜ ํ™•์ธ
โ€ข **์ž๋™์™„๋ฃŒ ์ •์ฑ… ๋ณ€๊ฒฝ ์—†์Œ** | `lib.sh` ์œ„์ž„ ํ—ฌํผ ํ˜ธ์ถœ์„ ์ด์ „ ์ธ๋ผ์ธ/์…ธ ๋ฐฉ์‹์œผ๋กœ ์ฆ‰์‹œ ๋ณต์› | +| **Phase 2a** | **`lib_py/loop/` ์ฝ”์–ด & Shadow Watcher** | โ€ข `engine.py` (๋‹จ์ผ ์ „์ด ์ฃผ์ฒด), `watcher.py` (`herdr agent wait` ๋ž˜ํ•‘) ๊ตฌํ˜„
โ€ข ๋ชจ๋“  Writer์— ๋Œ€ํ•œ ๋‹จ์ผ CAS ํŠธ๋žœ์žญ์…˜ ๊ฒฝ๊ณ„ ๊ตฌํ˜„
โ€ข ์ž๋™์™„๋ฃŒ๋ฅผ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๊ณ  "Would-Complete" ๋กœ๊น…๋งŒ ์ˆ˜ํ–‰ํ•˜๋Š” Shadow ๋ชจ๋“œ ๊ฐ€๋™ | โ€ข ๋ฝ ๋‚ด ๋น„๋™๊ธฐ/I/O ๋ฐฐ์ œ ๊ฒ€์ฆ
โ€ข ์†Œ์ผ“ ๋‹จ์ ˆ/์Šค๋ƒ…์ƒท ์žฌ์—ฐ๊ฒฐ ๋ฐฑ์˜คํ”„ ๊ฒ€์ฆ
โ€ข Shadow ๋ชจ๋“œ๊ฐ€ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ์ƒํƒœ๋ฅผ ์“ฐ์ง€ ์•Š์Œ์„ ๊ฒ€์ฆ | 1) Shadow ๋ชจ๋‹ˆํ„ฐ ํ”„๋กœ์„ธ์Šค ์ข…๋ฃŒ ๋ฐ ์ธ๋ฉ”๋ชจ๋ฆฌ ํ ํ๊ธฐ
2) CAS ์„œ๋น„์Šค๋Š” ํ•˜์œ„ ํ˜ธํ™˜ ๋ชจ๋“œ๋กœ ์œ ์ง€ํ•˜๊ฑฐ๋‚˜ `MAM_ENGINE_CAS=0` ํ”Œ๋ž˜๊ทธ๋กœ ๋น„ํ™œ์„ฑํ™”ํ•˜์—ฌ ๋ ˆ๊ฑฐ์‹œ ๋ฌด์กฐ๊ฑด ํ•„๋“œ ๋ณ‘ํ•ฉ(`update_job_status`)์œผ๋กœ ๋ณต๊ท€
3) ๊ธฐ์กด ์…ธ ๋ฃจํ”„ ์ƒํƒœ ๋ฐ ๋ ˆ์ฝ”๋“œ ๋ถˆ๋ณ€ ์œ ์ง€ | +| **Phase 2b** | **์ด์ค‘ ๊ฐ์ง€ ์ž๋™์™„๋ฃŒ ํ™œ์„ฑํ™” (Flag-Gated)** | โ€ข 4์ค‘ ์‹คํ–‰ ๊ท€์† + ๋ฆฌํฌํŠธ ํ™•์ • ๊ณ„์•ฝ + ๋‹จ์ผ CAS ์ „์ด ๊ฒŒ์ดํŠธ ์—ฐ๋™
โ€ข Pending Notification ์ง€์—ฐ ์•Œ๋ฆผ ๋ฐ ํฌ๋ž˜์‹œ ๋ณต๊ตฌ ๊ตฌํ˜„
โ€ข ๊ฒ€์ฆ๋œ ๋ฐฑ์—”๋“œ/์ž‘์—…์— ํ•œํ•ด opt-in ํ”Œ๋ž˜๊ทธ๋กœ ํ™œ์„ฑํ™” (๊ธฐ๋ณธ๊ฐ’ OFF) | โ€ข ๋ถ€์ˆ˜ํšจ๊ณผ ์ž‘์—… ์˜ˆ์™ธ ๋ฐ ๋ฌดํŒ์ • fail-closed ํ…Œ์ŠคํŠธ ์™„๋ฃŒ
โ€ข ๊ฒฝ์Ÿ/์žฌ์‹œ์ž‘ ์‹œ๋‚˜๋ฆฌ์˜ค ํ†ต๊ณผ
โ€ข ํ”Œ๋ž˜๊ทธ OFF ์‹œ ๊ธฐ์กด explicit event ๊ฒฝ๋กœ 100% ๋ณด์กด | ํ™˜๊ฒฝ๋ณ€์ˆ˜/ํ”Œ๋ž˜๊ทธ `MAM_AUTO_COMPLETE=0` ์„ค์ •; ์ด๋ฏธ ํ™•์ •๋œ terminal ๋ ˆ์ฝ”๋“œ๋Š” ๋ณด์กดํ•˜๊ณ  ์‹ ๊ทœ ์ž๋™์™„๋ฃŒ๋งŒ ์ค‘๋‹จ | +| **Phase 3** | **`run_loop.sh` Thin Wrapper ์ „ํ™˜ ๋ฐ ์ „๋ฉด ํšŒ๊ท€** | โ€ข `run_loop.sh`๋ฅผ ์ธ์ž ์ „๋‹ฌ ๋ž˜ํผ(`exec python3 -m lib_py.loop "$@"`)๋กœ ์ „ํ™˜
โ€ข ํ”Œ๋ž˜๋„ˆ/๋ฆฌ๋ทฐ์–ด/๋ฐ˜๋ก /์Šน๊ฒฉ ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ด์…˜ ๋กœ์ง ์™„์ „ ์ด์‹
โ€ข ์—„๊ฒฉํ•œ ํŒ์ • ํŒŒ์„œ(Strict Parser) ์ ์šฉ | โ€ข 5๊ฐœ subprocess ์ „์šฉ ํ…Œ์ŠคํŠธ(~1,621์ค„) ํฌํ•จ ์ „์ฒด 481๊ฐœ+ ํ…Œ์ŠคํŠธ 0-Fail
โ€ข ์‹œ๊ทธ๋„, ๋ฃจํ”„ ๊ฐ€๋“œ, ์ž„์˜ CWD ๋ฐ ๊ณต๋ฐฑ ๊ฒฝ๋กœ ๊ฒ€์ฆ ํ†ต๊ณผ | **์šฐ์•„ํ•œ ํ•ธ๋“œ์˜คํ”„ ์‹œํ€€์Šค**:
1) Quiesce: ์‹คํ–‰ ์ค‘์ธ Python ๋ฃจํ”„ ํ”„๋กœ์„ธ์Šค์— ์ข…๋ฃŒ ์‹ ํ˜ธ๋ฅผ ์ „๋‹ฌํ•˜์—ฌ ์‹ ๊ทœ ๋””์ŠคํŒจ์น˜ ์ค‘๋‹จ
2) Checkpoint: ํ˜„์žฌ ๋ฃจํ”„ ํšŒ์ฐจ, ํ™œ์„ฑ Job ID ๋ฐ ๋ฆฌ๋ทฐ ์ƒํƒœ๋ฅผ `.mam/loop-checkpoint.json`์— ์›์ž์  ๊ธฐ๋ก
3) Exit: Python ๋ฃจํ”„ ์ •์ƒ ์ข…๋ฃŒ ๋ฐ ๊ฐ€๋“œ ํ•ด์ œ
4) Resume: ๋ฐฑ์—…๋œ ์…ธ ์›๋ณธ ์Šคํฌ๋ฆฝํŠธ๋กœ `run_loop.sh` ๋ณต์› ํ›„ ์ฒดํฌํฌ์ธํŠธ ๊ธฐ๋ฐ˜ ์žฌ๊ฐœ ๊ฒ€์ฆ | +| **Phase 4** | **๋ถˆ๋ณ€ ๋Ÿฐํƒ€์ž„ ๊ฒ€์ฆ ๋ฐ Freeze ์ œ๊ฑฐ** | โ€ข ๊ธฐ๋™ ์‹œ ์ „์ฒด ๋ชจ๋“ˆ ์ƒ์ฃผ(Eager Import) ๋ฐ ์ž์‹ ํ…œํ”Œ๋ฆฟ ๋ถˆ๋ณ€์„ฑ ๊ฒ€์ฆ
โ€ข ์ž‘์—… ๋„์ค‘ ์Šคํ‚ฌ ํŒŒ์ผ ๊ต์ฒด ๋ฐ ๋Šฆ์€ ๊ฒฝ๋กœ ์ŠคํŠธ๋ ˆ์Šค ํ…Œ์ŠคํŠธ ํ†ต๊ณผ
โ€ข `/tmp/mam-loop-freeze.XXXXXX` ์šฐํšŒ์ฑ… ์™„์ „ ์ œ๊ฑฐ | โ€ข ๋Ÿฐํƒ€์ž„ ์ž๊ธฐ์ˆ˜์ • ํฌ๋ž˜์‹œ(B-13) ์žฌ๋ฐœ ๋ฐฉ์ง€ ๊ฒ€์ฆ
โ€ข Live Helper ๋ฒ„์ „ ํ˜ธํ™˜ ๊ฒŒ์ดํŠธ ๊ฒ€์ฆ ํ†ต๊ณผ
โ€ข ์šด์˜ ๋ฌธ์„œ ๋ฐ ๋ฐฐํฌ ์Šคํฌ๋ฆฝํŠธ ๊ฐฑ์‹  | Freeze ๋ณต์‚ฌ ๋กœ์ง(`run_loop.sh:112-127`) ์žฌํ™œ์„ฑํ™” ๋ฐ ๋ผ์ด๋ธŒ ํ—ฌํผ ๋ฒ„์ „ ๊ฒ€์‚ฌ ๋ณต๊ตฌ | + +--- + +## 3. ๋ผ์ด๋ธŒ ํ—ฌํผ ๋ฒ„์ „ ํ˜ธํ™˜์„ฑ ๋ฐ ์ž‘์—… ์ƒํƒœ ์ „์ด ๊ณ„์•ฝ (Live Helper Compatibility Gate) + +ํ†ตํ•ฉ ๋ณด๊ณ ์„œ(Rev.2) ๋ฐ B-13 ํ•ฉ์˜์— ๋”ฐ๋ผ, `multi-agent-mux-create`, `resume`, `stop` ๋“ฑ 1ํšŒ์„ฑ ์Šคํฌ๋ฆฝํŠธ๋Š” Freeze ๋ณต์‚ฌ๋ณธ์ด ์•„๋‹Œ Live Tree์—์„œ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. Phase 4์—์„œ Freeze๊ฐ€ ์ œ๊ฑฐ๋˜๊ธฐ ์ „๊นŒ์ง€, ๋ผ์ด๋ธŒ ํ—ฌํผ์™€ ๊ณ ์ • ์‹คํ–‰ ์—”์ง„ ๊ฐ„์˜ ์ถฉ๋Œ์„ ๋ฐฉ์ง€ํ•˜๊ณ  ๊ธฐ์กด ์ž‘์—… ์ƒํƒœ ์ „์ด ํšจ๊ณผ๋ฅผ ๋ณด์กดํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ ๊ทœ์น™์„ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค: + +1. **ABI ๋ฒ„์ „ ๋ฐ ํ”„๋กœํ† ์ฝœ ํ•ด์‹œ ๊ฒ€์ฆ**: + - Live Tree์˜ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์‹คํ–‰๋  ๋•Œ, ํ˜„์žฌ ์‹คํ–‰ ์ค‘์ธ ๋ฃจํ”„์˜ `MAM_VERSION` ๋˜๋Š” ๊ณตํ†ต ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ธํ„ฐํŽ˜์ด์Šค ํ•ด์‹œ๋ฅผ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. + - ๋ฉ”์ด์ € ๋ฒ„์ „ ๋ถˆ์ผ์น˜ ๋˜๋Š” ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์ด ๊ฐ์ง€๋˜๋ฉด, ๋ผ์ด๋ธŒ ํ—ฌํผ๋Š” ์ฆ‰์‹œ ์˜ค๋ฅ˜๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ณ  ์•ˆ์ „ํ•˜๊ฒŒ ์ค‘๋‹จ(fail-closed)ํ•ฉ๋‹ˆ๋‹ค. +2. **์ž‘์—… ์ทจ์†Œ(`cancelled`) ์ „์ด์˜ ์ •๊ทœ ์„œ๋น„์Šค ๊ฒฝ์œ  ๋ณด์กด**: + - `multi-agent-mux-stop/scripts/stop_session.sh`๋Š” ์„ธ์…˜ ๊ฐ•์ œ ์ข…๋ฃŒ ์‹œ ์ž”์—ฌ ํ™œ์„ฑ ์ž‘์—…์— ๋Œ€ํ•ด `cancelled` ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•ฉ๋‹ˆ๋‹ค (`stop_session.sh:242, 322`). + - ๋ผ์ด๋ธŒ ํ—ฌํผ๋Š” ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ JSON ํŒŒ์ผ์„ ์ž„์˜๋กœ ๋ณ€์กฐํ•˜์ง€ ์•Š๊ณ , `delegate_publish_event` ๋ฐ ๊ณตํ†ต ์ƒํƒœ ์ „์ด ์„œ๋น„์Šค(Transition Service)๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ CAS ๋ถˆ๋ณ€์‹์„ ์ค€์ˆ˜ํ•˜๋ฉด์„œ ์ž‘์—…์„ ์ •๊ทœ `cancelled` ์ƒํƒœ๋กœ ์ „์ด์‹œํ‚ต๋‹ˆ๋‹ค. + - ์ด๋ฅผ ํ†ตํ•ด ์„ธ์…˜ ์ค‘๋‹จ ์‹œ ์ž‘์—…์ด ์˜๊ตฌ ๊ณ ์ฐฉ(hang)๋˜์ง€ ์•Š๊ณ  ์˜ค์ผ€์ŠคํŠธ๋ ˆ์ดํ„ฐ ๋ฃจํ”„๊ฐ€ ์ฆ‰๊ฐ ์ทจ์†Œ๋ฅผ ์ธ์ง€ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. + +--- + +## 4. ๋ช…์‹œ์  ๋น„๋ฒ”์œ„(Out of Scope) ๋ฐ ํ˜ธํ™˜์„ฑ ๊ณ„์•ฝ + +1. **์ €์žฅ์†Œ ํ†ตํ•ฉ ๊ธˆ์ง€**: + - ์„ธ์…˜ ๊ด€๋ฆฌ์šฉ SQLite/YAML ์ €์žฅ์†Œ(`.mam/agent-sessions.yaml`, `.db`)์™€ ๋น„๋™๊ธฐ ์ž‘์—… ๊ด€๋ฆฌ์šฉ JSON ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ(`.mam/jobs/`)๋ฅผ ํ•˜๋‚˜๋กœ ํ†ตํ•ฉํ•˜๋ ค ์‹œ๋„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. +2. **์›์ƒท ์Šคํ‚ฌ์˜ Live Tree ์‹คํ–‰ ์œ ์ง€**: + - `multi-agent-mux-create`, `resume`, `stop` ๋“ฑ 1ํšŒ์„ฑ ์…ธ ์Šคํฌ๋ฆฝํŠธ๋Š” Freeze ๋ณต์‚ฌ๋ณธ์ด ์•„๋‹Œ ์‹ค์ œ ์ž‘์—… ํŠธ๋ฆฌ์˜ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. +3. **ํƒ€์ž„์•„์›ƒ 3์› ๋ถ„๋ฆฌ ๋ณด์กด**: + - (a) ์ „์ฒด ๋ฒฝ์‹œ๊ณ„ ํƒ€์ž„์•„์›ƒ, (b) MQTT ๊ฐ€์ž…์ž idle-timeout, (c) TUI idle ์ž๋™์™„๋ฃŒ๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ์œ ์ง€ํ•˜๋ฉฐ, ์ž๋™์™„๋ฃŒ๊ฐ€ ๊ธฐ์กด ํƒ€์ž„์•„์›ƒ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ๋ฎ์–ด์“ฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +--- + +## 5. ์„ฑ๋Šฅ ์ˆ˜์šฉ ๊ธฐ์ค€ (Performance Acceptance Criteria) + +๊ณ ์ •๋ฐ€ 10ms ํด๋ง ๋Œ€์‹  ์‹ค์ œ ์‹œ์Šคํ…œ ์ž์› ์ธก์ •์„ ๊ธฐ์ค€์œผ๋กœ ์„ฑ๋Šฅ์„ ํ‰๊ฐ€ํ•ฉ๋‹ˆ๋‹ค: +- **ํ”„๋กœ์„ธ์Šค ํฌํฌ ์ˆ˜ (Fork Count)**: ๋ฃจํ”„ ๋ฐ˜๋ณต๋‹น ์„œ๋ธŒํ”„๋กœ์„ธ์Šค ์ƒ์„ฑ ํšŸ์ˆ˜ ๋Œ€ํญ ๊ฐ์†Œ (์…ธ ๋ฃจํ”„ ๋Œ€๋น„ โ‰ฅ70% ๊ฐ์ถ•). +- **์œ ํœด CPU ์ ์œ ์œจ (Idle CPU)**: ๋Œ€๊ธฐ ์ค‘ CPU ์‚ฌ์šฉ๋ฅ  1% ๋ฏธ๋งŒ ์œ ์ง€ (์ด๋ฒคํŠธ ๊ตฌ๋™ ๋ฐ ์œ ๊ณ„ ํด๋ง). +- **MQTT ์—ฐ๊ฒฐ ์žฌ์‚ฌ์šฉ**: ๋‹จ์ผ ์žฅ์ˆ˜ ํ”„๋กœ์„ธ์Šค ๋‚ด์—์„œ ๋‹ค์ˆ˜์˜ ์ž‘์—… ๋ฐœํ–‰ ์‹œ ์—ฐ๊ฒฐ ์žฌ์ˆ˜๋ฆฝ ์˜ค๋ฒ„ํ—ค๋“œ ์ œ๊ฑฐ (p50 ๋ ˆ์ดํ„ด์‹œ ๋‹จ์ถ•). diff --git a/docs/contracts/issue-5/state-transitions.md b/docs/contracts/issue-5/state-transitions.md new file mode 100644 index 0000000..21bee75 --- /dev/null +++ b/docs/contracts/issue-5/state-transitions.md @@ -0,0 +1,120 @@ +# ๐Ÿ“‘ Target State Transitions and CAS Specification + +- **Document Version**: Rev.2 +- **Issue**: [Gitea Issue #5](https://git.godopu.com/tmpl/multi-agent-mux/issues/5) +- **Scope**: Target Compare-And-Swap (CAS) State Machine & Attribution Contract + +--- + +## 1. ์ด๋ฒคํŠธ(Event)์™€ ์ƒํƒœ(Status)์˜ ๊ฐœ๋…์  ๋ถ„๋ฆฌ + +๊ธฐ์กด ์‹œ์Šคํ…œ์˜ ํ˜ผ์„ ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ์šฉ์–ด๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ๋ถ„๋ฆฌํ•˜์—ฌ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค: +- **์ด๋ฒคํŠธ (Event)**: ์—์ด์ „ํŠธ, ์›Œ์ฒ˜(Watcher), ๋˜๋Š” ์™ธ๋ถ€ ์šด์˜์ž๊ฐ€ ํŠน์ • ์‹œ์ ์— ๋ฉ”์‹œ์ง• ๋ฐฑํ”Œ๋ ˆ์ธ(MQTT)์ด๋‚˜ ๋กœ์ปฌ ์ฑ„๋„๋กœ ์ „๋‹ฌํ•˜๋Š” **์‚ฌ๊ฑด(Fact)**์ž…๋‹ˆ๋‹ค (`started`, `progress`, `permission_required`, `completed`, `error`, `cancelled`). +- **์ƒํƒœ (Status)**: ๋‹จ์ผ ์ƒํƒœ ์ œ์–ด ์—”์ง„(Engine)์ด ์›์ž์  CAS ํŠธ๋žœ์žญ์…˜์„ ํ†ตํ•ด ๊ณตํ†ต ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ์— ์˜์†์ ์œผ๋กœ ๊ธฐ๋กํ•˜๋Š” **ํ˜„์žฌ ์ž‘์—…์˜ ๊ณต์‹ ์ƒ์• ์ฃผ๊ธฐ ๋‹จ๊ณ„**์ž…๋‹ˆ๋‹ค (`pending`, `running`, `completed`, `error`, `cancelled`). + +> ๐Ÿ“Œ **ํ•ต์‹ฌ ์›์น™**: ์ด๋ฒคํŠธ๋Š” ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ•ด ๋‹ฌ๋ผ๋Š” **์ „์ด ์š”์ฒญ(Transition Request)**์ผ ๋ฟ์ด๋ฉฐ, ์—”์ง„์˜ ์œ ํšจ์„ฑ ๊ฒ€์ฆ๊ณผ CAS ๊ฒŒ์ดํŠธ๋ฅผ ํ†ต๊ณผํ•˜๊ธฐ ์ „๊นŒ์ง€๋Š” ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +--- + +## 2. 4์ค‘ ์‹คํ–‰ ๊ท€์†(Execution Attribution) ๋ณด์žฅ ๊ณ„์•ฝ + +๋‹จ์ˆœํžˆ ์—์ด์ „ํŠธ ํ„ฐ๋ฏธ๋„์ด ์œ ํœด(`idle`) ์ƒํƒœ๋ผ๊ณ  ํ•ด์„œ ์ž‘์—…์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ข…๋ฃŒ๋˜์—ˆ๋‹ค๊ณ  ์ถ”๋ก (Inferred Completion)ํ•ด์„œ๋Š” ์•ˆ ๋ฉ๋‹ˆ๋‹ค. ์ด์ „ ์ž‘์—…์˜ ์ž”์—ฌ ์œ ํœด ์ƒํƒœ, ์„ธ์…˜ ์žฌ์‚ฌ์šฉ, ๋˜๋Š” ํ”„๋กœ์„ธ์Šค ์žฌ์‹œ์ž‘์œผ๋กœ ์ธํ•œ ์˜คํƒ์„ ์›์ฒœ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ **4์ค‘ ์‹๋ณ„์ž ํŠœํ”Œ**์„ ํ•„์ˆ˜๋กœ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค: + +$$\text{Attribution Tuple} = (\text{job\_id}, \text{attempt\_id}, \text{agent\_session\_uuid}, \text{pane\_process\_generation})$$ + +1. **`job_id`**: ๋“ฑ๋ก๋œ ๊ณ ์œ  ์ž‘์—… ์‹๋ณ„์ž (32-bit hex ๋˜๋Š” UUID). +2. **`attempt_id`**: ๋‹จ์ผ Job ๋‚ด์—์„œ ์žฌ์‹œ๋„ ์‹œ ๋‹จ์กฐ ์ฆ๊ฐ€ํ•˜๋Š” ์‹œ๋„ ๋ฒˆํ˜ธ. +3. **`agent_session_uuid`**: ์—์ด์ „ํŠธ ํ”„๋กœ์„ธ์Šค ๊ธฐ๋™ ์‹œ ํ• ๋‹น๋œ ์˜์† ๊ณ ์œ  ์‹๋ณ„์ž. +4. **`pane_process_generation`**: TMUX/Herdr ์ฐฝ ๋ฐ ๊ธฐ์ € ์…ธ ํ”„๋กœ์„ธ์Šค์˜ ์‹œ์ž‘ ์‹œ๊ฐ(`lstart`) ๋ฐ PID ์กฐํ•ฉ. + +### ๊ท€์† ๋ฐ ์‹คํ–‰ ์•ต์ปค ๋ถˆ๋ณ€์‹: +- ๋™์ผ ์—์ด์ „ํŠธ ์„ธ์…˜์—๋Š” ๋™์‹œ์— ๋‹จ ํ•˜๋‚˜์˜ ํ™œ์„ฑ ์ž‘์—…(`job_id`)๋งŒ ๋ฐฐ์ •๋ฉ๋‹ˆ๋‹ค. +- ๋””์ŠคํŒจ์น˜ ํ™•์ธ(`dispatch_ack`) ์ดํ›„, ํ•ด๋‹น `attempt_id`์— ์†ํ•˜๋Š” ์‹ค์ œ ํ„ฐ๋ฏธ๋„ ์ถœ๋ ฅ ๋ฐ ํ™œ๋™(Execution Anchor)์ด ๊ด€์ธก๋œ ๊ฒฝ์šฐ์—๋งŒ ์™„๋ฃŒ ํ›„๋ณด๋กœ ์ธ์ •ํ•ฉ๋‹ˆ๋‹ค. +- ๋„ค์ดํ‹ฐ๋ธŒ API๊ฐ€ ์ œ๊ณตํ•˜์ง€ ๋ชปํ•˜๋Š” ์‹๋ณ„์ž๋Š” `null + reason`์œผ๋กœ ํ‘œ๊ธฐํ•˜๋ฉฐ, ๊ฐ€์งœ ๊ฐ’์„ ์ž„์˜๋กœ ์ฃผ์ž…ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +--- + +## 3. ๋ชฉํ‘œ CAS ์ƒํƒœ ์ „์ดํ‘œ (Target State Machine) + +๋ชจ๋“  ์ƒํƒœ ๋ณ€๊ฒฝ์€ Registry Lock ๋ณดํ˜ธ ํ•˜์—์„œ ๊ธฐ๋Œ€ ์ƒํƒœ(`expected_state`), ์‹œ๋„ ์„ธ๋Œ€(`attempt_id`), ๋ฒ„์ „ ๋ฒˆํ˜ธ(`version`)๋ฅผ ์›์ž์ ์œผ๋กœ ๋น„๊ตํ•œ ํ›„ ๊ฐฑ์‹ (CAS)ํ•ฉ๋‹ˆ๋‹ค. + +| ํ˜„์žฌ ์ƒํƒœ (`Current`) | ์š”์ฒญ ์‹ ํ˜ธ (`Request`) | ์ „์ด ์กฐ๊ฑด ๋ฐ ๊ฒ€์ฆ ๊ทœ์น™ | ๊ฒฐ๊ณผ ์ƒํƒœ (`Next`) | ๋น„๊ณ  ๋ฐ ๊ฐˆ๋“ฑ ์ฒ˜๋ฆฌ | +|---|---|---|---|---| +| `pending` | `started` / `claim` | ์ž‘์—… ํ• ๋‹น ์„ธ์…˜ ๋ฐ `attempt_id` ์ผ์น˜ ํ™•์ธ | `running` | `started`๋Š” ์ƒํƒœ enum์ด ์•„๋‹ˆ๋ฉฐ `running`์œผ๋กœ ์ „์ด | +| `running` | `progress` | ๋น„-Terminal ์œ ์ง€. ์ตœ๊ทผ ํ™œ๋™ ์‹œ๊ฐ ๋ฐ ์„ธ๋ถ€ ์ •๋ณด ๊ฐฑ์‹  | `running` | ์ƒํƒœ ๋ณ€๊ฒฝ ์—†์ด ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋งŒ ๊ฐฑ์‹  | +| `running` | `permission_required` | ๋น„-Terminal ์œ ์ง€. `blocked_reason` ๊ธฐ๋ก | `running` | ๊ถŒํ•œ ๋Œ€๊ธฐ ์ค‘์—๋Š” ์ž๋™์™„๋ฃŒ ์ „๋ฉด ๊ธˆ์ง€ | +| `running` | `explicit terminal` | ์œ ํšจํ•œ ์ž‘์—…์ž ์„ธ์…˜์˜ ์„œ๋ช…๋œ `completed`/`error`/`cancelled` ์ˆ˜์‹  | ์š”์ฒญ๋œ Terminal | ์ •์ƒ ์ข…๋ฃŒ ์ปค๋ฐ‹ | +| `running` | `inferred completed` | 4์ค‘ ๊ท€์† ์ผ์น˜ + ์‹คํ–‰ ์•ต์ปค ํ™•์ธ + ํ™•์ • ํ•ด์‹œ/๋งค๋‹ˆํŽ˜์ŠคํŠธ ์กด์žฌ + Fresh `idle/done` + ๋ธ”๋กœ์ปค ์—†์Œ + ๋ถ€์ˆ˜ํšจ๊ณผ ๊ฒ€์ฆ | `completed` | Fail-Closed AND ๊ฒŒ์ดํŠธ ํ†ต๊ณผ ์‹œ์—๋งŒ CAS ์ปค๋ฐ‹ | +| `terminal` | ๋™์ผ ๋…ผ๋ฆฌ์  ์™„๋ฃŒ ์žฌ์ˆ˜์‹  | ๊ธฐ ์ปค๋ฐ‹๋œ Terminal ์ƒํƒœ์™€ ๋™์ผํ•œ ์š”์ฒญ | `terminal` (No-op) | ๋ฉฑ๋“ฑ(Idempotent) ACK ๋ฐ˜ํ™˜, ์ถ”๊ฐ€ ๋””์ŠคํŒจ์น˜ ๊ธˆ์ง€ | +| `terminal` | ์ƒ์ถฉ ์‹ ํ˜ธ (`progress`/๋‹ค๋ฅธ Terminal) | ์ด๋ฏธ Terminal ์ƒํƒœ์— ๋„๋‹ฌํ•œ ํ›„ ๋Šฆ๊ฒŒ ๋„์ฐฉํ•œ ์‹ ํ˜ธ | ๊ธฐ ์ปค๋ฐ‹ ์ƒํƒœ ์œ ์ง€ | ์ƒํƒœ ์—ญํ–‰ ๋ฐ ๋ฎ์–ด์“ฐ๊ธฐ ๊ฑฐ๋ถ€, Conflict Audit Log ๊ธฐ๋ก | +| ์ž„์˜ ์ƒํƒœ | Stale Attempt / Generation | ๊ณผ๊ฑฐ ์‹œ๋„ ์„ธ๋Œ€ ๋˜๋Š” ํ”„๋กœ์„ธ์Šค ์„ธ๋Œ€ ๋ถˆ์ผ์น˜ ์‹ ํ˜ธ | ์ „์ด ๊ฑฐ๋ถ€ | Stale Attempt ๊ฒฝ๊ณ  ๊ธฐ๋ก ๋ฐ ํŒจํ‚ท ํ๊ธฐ | +| `running` | `deadline_timeout` | ์ž‘์—… ํƒ€์ž„์•„์›ƒ ๊ฒฝ๊ณผ ๋ฐ ์‹คํ–‰ ์ค‘๋‹จ ํ™•์ธ | `error` | ํƒ€์ž„์•„์›ƒ ์›์ธ ๊ธฐ๋ก | + +--- + +## 4. ๋ฆฌํฌํŠธ ํ™•์ • ๋‹จ๊ณ„ (Report Finalization Lifecycle) + +์ž๋™์™„๋ฃŒ ์ถ”๋ก  ์‹œ ํŒŒ์ผ ์“ฐ๊ธฐ ๋„์ค‘ ๋ฐœ์ƒํ•˜๋Š” ๋ถˆ์™„์ „ ๋ฆฌํฌํŠธ ์ฑ„ํƒ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด 3๋‹จ๊ณ„ ํ™•์ • ๊ณ„์•ฝ์„ ์ˆ˜๋ฆฝํ•ฉ๋‹ˆ๋‹ค: + +``` +[writing] โ”€โ”€(์“ฐ๊ธฐ ์ง„ํ–‰ ์ค‘)โ”€โ”€> [stable_candidate] โ”€โ”€(์›์ž์  ๊ต์ฒด ๋ฐ ๋งค๋‹ˆํŽ˜์ŠคํŠธ)โ”€โ”€> [finalized] +``` + +1. **`writing` ๋‹จ๊ณ„**: + - ์—์ด์ „ํŠธ๊ฐ€ ์ž„์‹œ ํŒŒ์ผ(`report-final.md.tmp`)์„ ์ž‘์„ฑ ์ค‘์ธ ์ƒํƒœ. + - ์›Œ์ฒ˜๋Š” ์ด ์ƒํƒœ์—์„œ ์™„๋ฃŒ๋ฅผ ์ ˆ๋Œ€ ์ธ์ •ํ•˜์ง€ ์•Š์Œ (Hold). +2. **`stable_candidate` ๋‹จ๊ณ„ (1์ฐจ/๋ณด์กฐ ํ•„ํ„ฐ)**: + - ๋ฆฌํฌํŠธ ํŒŒ์ผ์˜ mtime์ด ๋‹จ์กฐ ์‹œ๊ณ„(monotonic clock) ๊ธฐ์ค€ **์ตœ์†Œ 15์ดˆ(๊ธฐ๋ณธ ๊ถŒ์žฅ๊ฐ’)** ๋™์•ˆ ๋ถˆ๋ณ€์ธ ์ƒํƒœ. + - โš ๏ธ **๋ถˆ๋ณ€์‹**: **๋””๋ฐ”์šด์Šค ๊ฒฝ๊ณผ(15s)๋งŒ์œผ๋กœ๋Š” ์™„๋ฃŒ ๊ถŒํ•œ์ด ์„ฑ๋ฆฝ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.** ์ด๋Š” ์ผ์‹œ์ ์ธ LLM ์ƒ๊ฐ ์ค‘๋‹จ์ด๋‚˜ ๋„คํŠธ์›Œํฌ ์ง€์—ฐ์ผ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์˜ค์ง 'ํ›„๋ณด ํ•„ํ„ฐ'๋กœ๋งŒ ๊ธฐ๋Šฅํ•ฉ๋‹ˆ๋‹ค. +3. **`finalized` ๋‹จ๊ณ„ (2์ฐจ/ํ™•์ • ๊ณ„์•ฝ)**: + - ์ตœ์ข… ๋Œ€์ƒ ๊ฒฝ๋กœ(`report-final.md`)๋กœ์˜ **์›์ž์  ์ด๋ฆ„ ๋ณ€๊ฒฝ(Atomic Rename)** ์™„๋ฃŒ. + - ํ•ด๋‹น ์‹œ๋„์˜ `attempt_id`์™€ `report SHA-256 hash`๊ฐ€ ๋ช…์‹œ๋œ **์™„๋ฃŒ ๋งค๋‹ˆํŽ˜์ŠคํŠธ(Commit Manifest)** ์ƒ์„ฑ ์™„๋ฃŒ. + - ์™ธ๋ถ€ ๋ถ€์ˆ˜ํšจ๊ณผ(Git push, API ํ˜ธ์ถœ)๊ฐ€ ์ˆ˜๋ฐ˜๋˜๋Š” ์ž‘์—…์ธ ๊ฒฝ์šฐ, ํ•ด๋‹น ๋ถ€์ˆ˜ํšจ๊ณผ์˜ ์™„๋ฃŒ ์ฆ๊ฑฐ๊ฐ€ ๋งค๋‹ˆํŽ˜์ŠคํŠธ์— ์ฒจ๋ถ€๋˜์–ด์•ผ ํ•จ. + - ๋ฆฌํฌํŠธ๊ฐ€ ์žฌ์ž‘์„ฑ๋˜๋ฉด ์ด์ „ ํ•ด์‹œ๋Š” ์ฆ‰์‹œ ๋ฌดํšจํ™”๋˜๋ฉฐ ์ƒˆ๋กœ์šด ํ™•์ • ์ ˆ์ฐจ๊ฐ€ ํ•„์š”ํ•จ. + +--- + +## 5. ์ง€์—ฐ ์•Œ๋ฆผ (Pending Notification) ๋ฐ ํŠธ๋žœ์žญ์…˜ ๋ณต๊ตฌ ์„ค๊ณ„ + +์ƒํƒœ ์ „์ด ๋ฝ(Registry Lock) ํ•˜์—์„œ ์™ธ๋ถ€ I/O(MQTT ๋ธŒ๋กœ์ปค ์ „์†ก, ๋„คํŠธ์›Œํฌ ํ˜ธ์ถœ)๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ฉด ๋ฐ๋“œ๋ฝ๊ณผ ์‹ฌ๊ฐํ•œ ์ง€์—ฐ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ๋‹ค์Œ **๋‚ด๊ตฌ์„ฑ ์ง€์—ฐ ์•Œ๋ฆผ(Pending Notification) ํŠธ๋žœ์žญ์…˜**์„ ๋ช…์„ธํ•ฉ๋‹ˆ๋‹ค: + +```mermaid +sequenceDiagram + participant Engine + participant Registry as Job Registry (Disk) + participant Publisher as Async Publisher (Wire) + participant Broker as MQTT Broker + + Note over Engine: 1. CAS ์ „์ด ๊ฒฐ์ • + activate Engine + Engine->>Registry: ๋ฝ ํš๋“ (fcntl.flock) + Engine->>Registry: ์›์ž์  ์ƒํƒœ ์ปค๋ฐ‹ (status=completed, pending_notification=intent) + Engine->>Registry: ๋ฝ ์ฆ‰์‹œ ํ•ด์ œ + deactivate Engine + + Note over Publisher: 2. ๋น„๋™๊ธฐ ์•Œ๋ฆผ ์ „์†ก + Publisher->>Broker: MQTT ์ด๋ฒคํŠธ ๋ฐœํ–‰ (HMAC ์„œ๋ช… ํฌํ•จ) + alt ์ „์†ก ์„ฑ๊ณต + Broker-->>Publisher: ACK (PUBACK) + Publisher->>Registry: pending_notification ์™„๋ฃŒ ํ‘œ์‹œ + else ์ „์†ก ์‹คํŒจ / ํฌ๋ž˜์‹œ + Note over Publisher: ๋ฐฑ์˜คํ”„ ์žฌ์‹œ๋„ ๋ฐ ๊ธฐ๋™ ์‹œ ๋ฏธ์™„๋ฃŒ ์•Œ๋ฆผ ๋ณต๊ตฌ ์ „์†ก + end +``` + +### ํŠธ๋žœ์žญ์…˜ ๋ณด์žฅ ๊ทœ์น™: +1. **๋‹จ์ผ ๋‚ด๊ตฌ์„ฑ ์ž‘์—… ๋ ˆ์ฝ”๋“œ**: + - ๋…๋ฆฝ๋œ ๋ณ„๋„์˜ Outbox DB๋ฅผ ์‹ ์„คํ•˜์ง€ ์•Š๊ณ , ๊ธฐ์กด `.mam/jobs/.json` ๋ ˆ์ฝ”๋“œ ๋‚ด์— `pending_notification: { event, timestamp, seq, signature }`๋ฅผ ์›์ž์ ์œผ๋กœ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค. +2. **๋ฝ ํ•ด์ œ ํ›„ ์ „์†ก**: + - ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๋ฝ์„ ํ•ด์ œํ•œ ํ›„ ๋น„๋™๊ธฐ ์›Œ์ปค๊ฐ€ MQTT ๋ฐœํ–‰์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. +3. **ํฌ๋ž˜์‹œ ๋ณต๊ตฌ(Crash Recovery)**: + - ์—”์ง„ ์žฌ์‹œ์ž‘ ์‹œ `status in TERMINAL`์ด๋ฉด์„œ `pending_notification`์ด ๋‚จ์•„์žˆ๋Š” ๋ ˆ์ฝ”๋“œ๋ฅผ ์Šค์บ”ํ•˜์—ฌ ๋ฉฑ๋“ฑ์ ์œผ๋กœ ์žฌ์ „์†กํ•ฉ๋‹ˆ๋‹ค. + +--- + +## 6. ๊ถŒํ•œ ์ฐจ๋‹จ(Permission Blocked) ํ•ด์ œ ์ •์ฑ… + +- ์—์ด์ „ํŠธ ํ„ฐ๋ฏธ๋„์ด `blocked` (๊ถŒํ•œ ์Šน์ธ ๋Œ€๊ธฐ) ์ƒํƒœ๋กœ ๊ด€์ธก๋˜๋ฉด, **์–ด๋– ํ•œ ๊ฒฝ์šฐ์—๋„ ์ž๋™์™„๋ฃŒ ์ „์ด๋ฅผ ์‹œ๋„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.** +- ์ฐจ๋‹จ ํ•ด์ œ ํŒ๋‹จ: + - ๋ฌต์‹œ์  ์ „์ด ๊ธˆ์ง€: ๋‹จ์ˆœํžˆ ๋‹ค์Œ ๊ด€์ธก์—์„œ `working`์ด ๋˜์—ˆ๋‹ค๊ณ  ํ•ด์„œ ์ด์ „ ์ฐจ๋‹จ์ด ์ •๋‹นํ•˜๊ฒŒ ํ•ด๊ฒฐ๋˜์—ˆ๋‹ค๊ณ  ๋‹จ์ •ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + - ๋ช…์‹œ์  ์Šน์ธ ์ฆ๊ฑฐ ๋˜๋Š” ์šด์˜์ž์˜ ์Šน์ธ ์ž…๋ ฅ ๋กœ๊ทธ๊ฐ€ ํ™•์ธ๋˜์ง€ ์•Š๋Š” ํ•œ, ์ž‘์—…์€ `running (blocked)` ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๋ฉฐ ๋ฐ๋“œ๋ผ์ธ ๋„๋‹ฌ ์‹œ ์•ˆ์ „ํ•˜๊ฒŒ ํƒ€์ž„์•„์›ƒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค. diff --git a/docs/contracts/issue-5/task.md b/docs/contracts/issue-5/task.md new file mode 100644 index 0000000..809c709 --- /dev/null +++ b/docs/contracts/issue-5/task.md @@ -0,0 +1,90 @@ +# ๐Ÿ“‘ Issue #5 Task Checklist & Verification Evidence + +- **Document Version**: Rev.3 (Post Cross-Review Corrections) +- **Issue**: [Gitea Issue #5](https://git.godopu.com/tmpl/multi-agent-mux/issues/5) +- **Status**: Phase 0 Complete / Phase 1โ€“4 Staged + +--- + +## Phase 0: ์‚ฌ์ „ ๊ณ„์•ฝ ๊ธฐ์ค€์„  ๋ฐ Fixture ์ˆ˜์ง‘ (์ด๋ฒˆ ์‹คํ–‰ ๋ฒ”์œ„) + +- [x] **P0-01** ๊ธฐ์ค€ commit, dirty diff, Herdr/Python ๋ฒ„์ „, pytest collection ์ˆ˜ ๊ธฐ๋ก. + - *์ฆ๊ฑฐ*: Commit `47640e2`, Herdr `0.8.2`, Python `3.9.6`, Pytest `469 tests collected initially` โž” `484 tests collected with Phase 0 suite` (+15 contract & mutation tests). +- [x] **P0-02** `docs/contracts/issue-5/baseline.md` ์ž‘์„ฑ: ๊ณต๊ฐœ CLI/default/invalid input/stdout-stderr/exit/interpreter/path/signal ๊ณ„์•ฝ๊ณผ ์†Œ์Šคยท๊ธฐ์กด ํ…Œ์ŠคํŠธ ๋งคํ•‘. + - *์ฆ๊ฑฐ*: `docs/contracts/issue-5/baseline.md` ยง2 (`--all-reviewer` ์šฐ์„ ์ˆœ์œ„ ๋ฐ L192โ€“196 ์†Œ์Šค ๋งคํ•‘ ๋ฐ˜์˜). +- [x] **P0-03** event schema/HMAC/seq/retain/audit/๋””์Šคํฌ fallback ๋ฐ publisher ์‹คํŒจ ํ›„ ๋กœ์ปฌ commit ๋™์ž‘ ๊ธฐ๋ก. + - *์ฆ๊ฑฐ*: `docs/contracts/issue-5/baseline.md` ยง3 (๊ฐ์‚ฌ ๋กœ๊ทธ ๊ฒฝ๋กœ `.mam/delegate_job_logs//` ์ •ํ™•ํžˆ ๋ฐ˜์˜). +- [x] **P0-04** event์™€ registry status ๋ถ„๋ฆฌ; permission clearanceยทcancelled subscriber ์ฐจ์ดยทpick_pending ๋“ฑ writer inventory/gap ๊ธฐ๋ก. + - *์ฆ๊ฑฐ*: `docs/contracts/issue-5/baseline.md` ยง3, `job_subscriber.py:46, 80` vs `publish_event.py:49` ๊ฐ„๊ทน ๋ช…์‹œ. +- [x] **P0-05** report strictness/promotion/cleanup ๋ฐ job/session/loop lock ํš๋“ยทํ•ด์ œ/ownership ๊ณ„์•ฝ ๊ธฐ๋ก. + - *์ฆ๊ฑฐ*: `docs/contracts/issue-5/baseline.md` ยง4, ยง5. +- [x] **P0-06** `docs/contracts/issue-5/state-transitions.md` ์ž‘์„ฑ: 4์ค‘ ๊ท€์†, dispatch anchor, target CAS ํ‘œ์™€ duplicate/conflict/deadline ์ •์ฑ…. + - *์ฆ๊ฑฐ*: `docs/contracts/issue-5/state-transitions.md` ยง2, ยง3. +- [x] **P0-07** writing/stable_candidate/finalized ๋ฐ manifest/hash/๋ถ€์ˆ˜ํšจ๊ณผ ํ™•์ • ๊ณ„์•ฝ ๊ธฐ๋ก; debounce๋งŒ์œผ๋กœ completed ๊ธˆ์ง€. + - *์ฆ๊ฑฐ*: `docs/contracts/issue-5/state-transitions.md` ยง4. +- [x] **P0-08** Herdr native wait version/help/spec ๋ฐ ๋‹จ์ผ ๊ฒฐ๊ณผ capture ์ˆ˜์ง‘. timeout/unknown target/transport failure์™€ unknown ์ƒํƒœ๋ฅผ ๋ถ„๋ฆฌํ•˜๊ณ  ์™ธ๋ถ€ collector์˜ before/after ์ฐธ์กฐ๋ฅผ ๊ฒ€์ฆ. + - *์ฆ๊ฑฐ*: `tests/fixtures/issue_5/herdr/wait-contract.json`, `docs/contracts/issue-5/herdr-runtime.md` ยง1 (timeout stderr ์ฑ„๋„ ๋ฐ˜์˜). +- [x] **P0-09** claude native ์ƒํƒœ/call-outcome ํ–‰๋ ฌยท์‹ค์ธก provenanceยท๋ฏธ๊ด€์ธก gap ๊ธฐ๋ก. + - *์ฆ๊ฑฐ*: `tests/fixtures/issue_5/herdr/claude/idle.json`, `herdr/manifest.json`. +- [x] **P0-10** agy native ์ƒํƒœ/call-outcome ํ–‰๋ ฌยท์‹ค์ธก provenanceยท๋ฏธ๊ด€์ธก gap ๊ธฐ๋ก. + - *์ฆ๊ฑฐ*: `tests/fixtures/issue_5/herdr/agy/working.json`, `tests/fixtures/issue_5/herdr/agy/wait_timeout.json`, `herdr/manifest.json`. +- [x] **P0-11** hermes native ์ƒํƒœ/call-outcome ํ–‰๋ ฌยท์‹ค์ธก provenanceยท๋ฏธ๊ด€์ธก gap ๊ธฐ๋ก. + - *์ฆ๊ฑฐ*: `herdr/manifest.json` (๋ฏธ๊ฐ€๋™ ์„ธ์…˜ gap์œผ๋กœ ์ •์งํ•˜๊ฒŒ ๊ธฐ๋ก). +- [x] **P0-12** grok native ์ƒํƒœ/call-outcome ํ–‰๋ ฌยท์‹ค์ธก provenanceยท๋ฏธ๊ด€์ธก gap ๊ธฐ๋ก. + - *์ฆ๊ฑฐ*: `tests/fixtures/issue_5/herdr/grok/idle.json`, `herdr/manifest.json`. +- [x] **P0-13** opencode native ์ƒํƒœ/call-outcome ํ–‰๋ ฌยท์‹ค์ธก provenanceยท๋ฏธ๊ด€์ธก gap ๊ธฐ๋ก. + - *์ฆ๊ฑฐ*: `herdr/manifest.json` (๋ฏธ๊ฐ€๋™ ์„ธ์…˜ gap์œผ๋กœ ์ •์งํ•˜๊ฒŒ ๊ธฐ๋ก). +- [x] **P0-14** codex native ์ƒํƒœ/call-outcome ํ–‰๋ ฌยท์‹ค์ธก provenanceยท๋ฏธ๊ด€์ธก gap ๊ธฐ๋ก. + - *์ฆ๊ฑฐ*: `tests/fixtures/issue_5/herdr/codex/done.json`, `herdr/manifest.json`. +- [x] **P0-15** `herdr/manifest.json`๊ณผ `watcher/manifest.json` ๋ถ„๋ฆฌ. native observed ์บก์ฒ˜๋งŒ ์ €์žฅ; watcher timeline source๋ณ„ observed/simulated, ์ „์ฒด mixed ์—ฌ๋ถ€, identity association๊ณผ redaction ๊ฒ€์ฆ. + - *์ฆ๊ฑฐ*: `tests/fixtures/issue_5/herdr/manifest.json`, `tests/fixtures/issue_5/watcher/manifest.json`. +- [x] **P0-16** ๋‘ ๊ณ„์ธต schema/reference/coverage ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€: native payload์— job/attempt/report phase ์ฃผ์ž… ๊ฑฐ๋ถ€, ์˜ค๋ฅ˜์™€ ์ƒํƒœ ๋ถ„๋ฆฌ, simulated๋ฅผ observed๋กœ ํ‘œ๊ธฐ ๊ฑฐ๋ถ€. ์šด์˜ session ์“ฐ๊ธฐ ๊ธˆ์ง€. + - *์ฆ๊ฑฐ*: `tests/test_issue5_phase0_contracts.py`. +- [x] **P0-16a** watcher ๋ณตํ•ฉ ์‹œ๋‚˜๋ฆฌ์˜ค 7์ข… ์ž‘์„ฑ: initial idle, tool active, report pause, finalized candidate, blocked, disconnect, generation restart. source๋ณ„ ์ฆ๊ฑฐ/๊ฐ€์ƒ ์‹œ๊ณ„/expected policy๋ฅผ ๊ตฌ๋ถ„. + - *์ฆ๊ฑฐ*: `tests/fixtures/issue_5/watcher/*.json` (7๊ฐœ ์‹œ๋‚˜๋ฆฌ์˜ค ์™„๋ฃŒ). +- [x] **P0-16b** `herdr-runtime.md`์— native APIยท๋‹จ์ผ waitยท์™ธ๋ถ€ collectorยทfile/event/dispatch source ๊ฒฝ๊ณ„๋ฅผ ๋ช…์‹œ. + - *์ฆ๊ฑฐ*: `docs/contracts/issue-5/herdr-runtime.md`. +- [x] **P0-17** Phase 1โ€“4 `implementation_plan.md`/`task.md` ์ž‘์„ฑ, live-helper version/๊ณตํ†ต CAS/notification ๋ณต๊ตฌ ๊ฒฐ์ • ๊ธฐ๋ก. + - *์ฆ๊ฑฐ*: `docs/contracts/issue-5/implementation_plan.md`, `docs/contracts/issue-5/task.md`. +- [x] **P0-18** ๊ด€๋ จ CLI/lock/rebuttal/adapter ํ…Œ์ŠคํŠธ์™€ ์ „์ฒด suite ์‹คํ–‰; ์ˆ˜์ง‘ยท์‹คํ–‰ยทํ†ต๊ณผยท์‹คํŒจ ์ˆซ์ž ๋ถ„๋ฆฌ. + - *์ฆ๊ฑฐ*: ์ „์ฒด 484๊ฐœ ์ˆ˜์ง‘, ์„ ํ–‰ ์ „์ฒด ์‹คํ–‰ 481๊ฐœ ํ†ต๊ณผ(0 failed, 0 skipped, 786.18s), 15๊ฐœ ๊ณ„์•ฝ/๋ณ€์ด ํ…Œ์ŠคํŠธ ์ „์ฒด ํ†ต๊ณผ, ์ง‘์ค‘ ์ฝ”์–ด ํšŒ๊ท€ 155๊ฐœ ํ†ต๊ณผ(60.48s). +- [x] **P0-19** ๋ฌธ์„œ ๋งํฌ/JSON/git diff --check/production ์ฝ”๋“œ ๋ฌด๋ณ€๊ฒฝ ํ™•์ธ. + - *์ฆ๊ฑฐ*: `git diff --check` ํด๋ฆฐ, ๊ณต๋ฐฑ ๋ฆฐํŠธ ์™„๋ฃŒ, ํ”„๋กœ๋•์…˜ `.sh`/`.py` ๋ฌด๋ณ€๊ฒฝ (์˜ค์ง `docs/`, `tests/` ๋ณ€๊ฒฝ). +- [x] **P0-20** ๊ฒฐ๊ณผ ๋ณด๊ณ : Phase 0 ์‚ฐ์ถœ๋ฌผ ์™„๋ฃŒ ์—ฌ๋ถ€, backend๋ณ„ native live gapยทmixed/simulated scenario ํ˜„ํ™ฉ, ๋‚จ์€ gate๋ฅผ ๋ณ„๋„ ๋ช…์‹œํ•˜๊ณ  peer review ์š”์ฒญ. + - *์ฆ๊ฑฐ*: `.mam/jobs/61f2fa87/agy-reports/report-final.md`. + +--- + +## Phase 1: `lib_py/delegation/` ๊ตฌ์ถ• (ํ›„์† ์Šน์ธ๋œ ์ž‘์—…) +- [ ] **P1-01** ๊ธฐ์กด delegation import ์„œ๋น„์Šค/CLI adapter ์ถ”์ถœ; ์ €์žฅ์†Œ ํ†ตํ•ฉ ์—†์ด nonfatalยทdaemon PID ๊ณ„์•ฝ ๋ณด์กด. +- [ ] **P1-02** ์žฅ์ˆ˜ ํ”„๋กœ์„ธ์Šค ๋‹จ์ผ MQTT ์—ฐ๊ฒฐ ์žฌ์‚ฌ์šฉ๊ณผ benchmark; legacy publisher ํ˜ธํ™˜ ํ…Œ์ŠคํŠธ. +- [ ] **P1-03** HMAC/seq/retain/disk fallback ํšŒ๊ท€์™€ rollback ํ™•์ธ. ์ž๋™์™„๋ฃŒ ๋น„ํ™œ์„ฑ ์œ ์ง€. + +--- + +## Phase 2a: `lib_py/loop/` ์ฝ”์–ด & Shadow Watcher (ํ›„์†) +- [ ] **P2A-01** ๋‹จ์ผ engine/checkpoint์™€ ๋ชจ๋“  writer ๊ณตํ†ต CAS ๊ฒฝ๊ณ„ ๊ตฌํ˜„. +- [ ] **P2A-02** 4์ค‘ ๊ท€์†/dispatch ์ฆ๊ฑฐ/์žฌ์—ฐ๊ฒฐ resnapshot/timeout/queue cancellation ์ฒ˜๋ฆฌ. +- [ ] **P2A-03** shadow-only watcher๊ฐ€ ์–ด๋–ค terminal๋„ ์“ฐ์ง€ ์•Š์Œ์„ ๊ฒ€์ฆ. +- [ ] **P2A-04** race/attempt ์žฌ์‚ฌ์šฉ/๋ถ€๋ถ„ report/๋„๊ตฌ ์‹คํ–‰/blocked/unknown/์žฌ์‹œ์ž‘ fixture ์‹œ๋‚˜๋ฆฌ์˜ค ์‹คํ–‰. + +--- + +## Phase 2b: ์ด์ค‘ ๊ฐ์ง€ ์ž๋™์™„๋ฃŒ ํ™œ์„ฑํ™” (ํ›„์†) +- [ ] **P2B-01** report finalization/manifest/hash ๋ฐ ์™ธ๋ถ€ ๋ถ€์ˆ˜ํšจ๊ณผ evidence protocol ๊ตฌํ˜„. +- [ ] **P2B-02** terminal+pending notification commit/์žฌ์ „์†กยท์žฌ์‹œ์ž‘ยท๋Šฆ์€ conflict ์ฒ˜๋ฆฌ ๊ฒ€์ฆ. +- [ ] **P2B-03** ๊ฒ€์ฆ๋œ backend/job์—๋งŒ default-off flag๋กœ ํ™œ์„ฑํ™”; unsupported explicit event ๊ฒฝ๋กœ ๋ณด์กด. + +--- + +## Phase 3: `run_loop.sh` Thin Wrapper ์ „ํ™˜ ๋ฐ ์ „๋ฉด ํšŒ๊ท€ (ํ›„์†) +- [ ] **P3-01** planner/reviewer/rebuttal/promotion ๋ฐ strict ํŒ์ •/์˜ˆ์‚ฐ/์ค‘์žฌ/ํ•ฉ์˜ ํ…Œ์ŠคํŠธ. +- [ ] **P3-02** thin wrapper ์ธ์žยทinterpreterยทcwdยทsignalยทguardยทchild cleanup ํ˜ธํ™˜ ๊ฒ€์ฆ. +- [ ] **P3-03** ์ „์ฒด suite+์‹ ๊ทœ race/restart/lifecycle, writer ๋‹จ์ผ์„ฑ, rollback checkpoint ์ธ๊ณ„ ๊ฒ€์ฆ. + +--- + +## Phase 4: ๋ถˆ๋ณ€ ๋Ÿฐํƒ€์ž„ ๊ฒ€์ฆ ๋ฐ Freeze ์ œ๊ฑฐ (ํ›„์†) +- [ ] **P4-01** eager import+child/helper/template ์‹คํ–‰ ๋ฒ„์ „ ์ •์ฑ… ๊ตฌํ˜„/๊ฒ€์ฆ. +- [ ] **P4-02** ์‹คํ–‰ ์ค‘ ์ฝ”๋“œ ๊ต์ฒด์™€ ๋Šฆ์€ ์žฌ์‹ฌ/์Šน๊ฒฉ/rollback ์ŠคํŠธ๋ ˆ์Šค ๊ฒ€์ฆ. +- [ ] **P4-03** gate ํ†ต๊ณผ ํ›„์—๋งŒ Freeze ์ œ๊ฑฐ ๋ฐ ์šด์˜ ๋ฌธ์„œ ๊ฐฑ์‹ .