From 8def235dc7805555f5b2cac5f0b1017c607440eb Mon Sep 17 00:00:00 2001 From: Godopu Date: Sun, 6 Sep 2026 16:29:06 +0900 Subject: [PATCH] docs(reports): archive phase 1 finalized implementation plan (plan-923e9403) --- .../plan-923e9403.md | 463 ++++++++++++++++++ 1 file changed, 463 insertions(+) create mode 100644 .agents/reports/planner-reviewer-claude-01/plan-923e9403.md diff --git a/.agents/reports/planner-reviewer-claude-01/plan-923e9403.md b/.agents/reports/planner-reviewer-claude-01/plan-923e9403.md new file mode 100644 index 0000000..2b0c449 --- /dev/null +++ b/.agents/reports/planner-reviewer-claude-01/plan-923e9403.md @@ -0,0 +1,463 @@ +# ๐Ÿ“‘ Issue #5 Phase 1 ๊ตฌํ˜„ ๊ณ„ํš์„œ (Rev.2) โ€” `lib_py/delegation/` ์ถ”์ถœ ๋ฐ ์žฅ์ˆ˜ MQTT ์—ฐ๊ฒฐ ํ”„๋กœํ† ํƒ€์ž… + +- **Job ID**: `9aae4233` (Rev.1: `923e9403`) / **Planner**: `planner-reviewer-claude-01` +- **๋ฐ˜์˜ํ•œ ์ด์˜์ œ๊ธฐ**: `creator-claude-01`์˜ Challenge (Job `c0b9c987`) โ€” D2 shim ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์˜ ์ปจํ…์ŠคํŠธ ์ถฉ๋Œ +- **Issue**: [Gitea Issue #5](https://git.godopu.com/tmpl/multi-agent-mux/issues/5) โ€” Phase 1 (`implementation_plan.md` Rev.4 ยง2 Phase 1 ํ–‰, `task.md` P1-01~P1-03) +- **๊ธฐ์ค€ ์ปค๋ฐ‹**: `fc0bd96` (branch `refactor`, working tree clean) / Python `3.9.6` / paho-mqtt `2.1.0` / herdr `0.8.2` / ์ „์ฒด ํ…Œ์ŠคํŠธ ์ˆ˜์ง‘ **510** +- **๋ฌธ์„œ ์„ฑ๊ฒฉ**: Worker๊ฐ€ ๊ทธ๋Œ€๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๋‹จ๊ณ„๋ณ„ ๊ตฌํ˜„ ๋ช…์„ธ. ๋ชจ๋“  ์ธ์šฉ ํ–‰ ๋ฒˆํ˜ธ๋Š” ๋ผ์ด๋ธŒ ์ฝ”๋“œ๋กœ ์ง์ ‘ ํ™•์ธํ•จ(ยง1). Rev.1 ๋Œ€๋น„ ๋ณ€๊ฒฝ์€ ยง0-1์— ๋ชจ์•„ ๋‘์—ˆ๊ณ , ๋ณธ๋ฌธ์—๋Š” ์ด๋ฏธ ๋ฐ˜์˜๋˜์–ด ์žˆ๋‹ค. + +--- + +## 0. ์š”์•ฝ (TL;DR) + +1. **์ถ”์ถœ ๋Œ€์ƒ์€ `lib.sh` L1833โ€“1941์˜ ์œ„์ž„ ๋ธ”๋ก 5๊ฐœ ํ•จ์ˆ˜๋ฟ์ด๋‹ค.** L1943 ์ดํ›„(`wait_for_tui_ready`, `send_keys_safe` ๋“ฑ)๋Š” TUI ์กฐ์ž‘ ํ—ฌํผ์ด๋ฉฐ Phase 1 ๋น„๋ฒ”์œ„๋‹ค. +2. **`scripts/{mqtt_common,registry,publish_event,job_subscriber}.py`์˜ ํŒŒ์ผ ๊ฒฝ๋กœ๋Š” ์™€์ด์–ด ๊ณ„์•ฝ์ด๋‹ค.** ์—์ด์ „ํŠธ์—๊ฒŒ ์ฃผ์ž…๋˜๋Š” ์ง€์‹œ๋ฌธ(`create_session.sh:483`, ๋ž˜ํผ `:197/:318`), SKILL.md, BOOTSTRAP*.md, `deploy/install.sh:103`, ํ…Œ์ŠคํŠธ 3์ข…์ด ๋ฆฌํ„ฐ๋Ÿด ๊ฒฝ๋กœ๋กœ ํ˜ธ์ถœํ•œ๋‹ค. ๊ตฌํ˜„์ฒด๋Š” `lib_py/delegation/`์œผ๋กœ ์˜ฎ๊ธฐ๋˜ `scripts/*.py`๋Š” **๋™์ผ ๋ชจ๋“ˆ ๊ฐ์ฒด๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋Š” ํ˜ธํ™˜ shim**์œผ๋กœ ๋‚จ๊ธด๋‹ค. +3. **shim์€ `sys.path`๋ฅผ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๊ณ  `lib_py.delegation` ํŒจํ‚ค์ง€๋งŒ ์ž๊ธฐ ์˜† ํŠธ๋ฆฌ์— "ํ”„๋กœ์„ธ์Šค๋‹น 1ํšŒ" ๋ฐ”์ธ๋”ฉํ•œ๋‹ค(Rev.2, D2).** ์ด์˜์ œ๊ธฐ๊ฐ€ ์‹ค์ฆํ•œ ๋Œ€๋กœ `sys.path.insert(0, ...)` ๋ฐฉ์‹์€ pytest์—์„œ ์œ„์ž„๊ณผ ๋ฌด๊ด€ํ•œ `lib_py.*`๊นŒ์ง€ ์ž„์‹œ ์‚ฌ๋ณธ์œผ๋กœ ๊ฐ€๋กœ์ฑˆ๋‹ค. ์ด์˜์ œ๊ธฐ๊ฐ€ ์ œ์•ˆํ•œ "์žŽ(leaf) ๋ชจ๋“ˆ ํŒŒ์ผ ์ง์ ‘ ์ ์žฌ"๋„ ์žฌํ˜„ ๊ฒฐ๊ณผ ๋น„-์žŽ ๋ชจ๋“ˆ(`publish_event`)์—์„œ ๋‘ ํŠธ๋ฆฌ๊ฐ€ ์„ž์ด๋ฏ€๋กœ ์ฑ„ํƒํ•˜์ง€ ์•Š๊ณ , **ํŒจํ‚ค์ง€ ๋‹จ์œ„ bind-once**๋กœ ํ™•์ •ํ–ˆ๋‹ค(ยง1-8 ๊ทผ๊ฑฐ). +4. **ํ˜„์žฌ ์…ธ ํ—ฌํผ 3์ข…์„ ์ง์ ‘ ๊ฒ€์ฆํ•˜๋Š” ํ…Œ์ŠคํŠธ๋Š” 0๊ฐœ๋‹ค.** ์ด๋™ ์ „์— ํŠน์„ฑํ™”(golden) ํ…Œ์ŠคํŠธ๋ฅผ ๋จผ์ € ์ถ”๊ฐ€ํ•œ๋‹ค(P1-00). +5. **์žฅ์ˆ˜ ์—ฐ๊ฒฐ ํ”„๋กœํ† ํƒ€์ž…์€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ „์šฉ์ด๋‹ค.** CLI๋Š” ํ”„๋กœ์„ธ์Šค ๋‹จ์œ„๋กœ ์ข…๋ฃŒ๋˜๋ฏ€๋กœ ์žฌ์‚ฌ์šฉ ์ด๋“์ด ์—†๋‹ค. ๋ฒค์น˜๋งˆํฌ๋Š” ๊ฒฐ์ •์  ๊ฐ€์งœ ์ „์†ก + ์‹ค๋ธŒ๋กœ์ปค(์ •๋ณด์„ฑ) 2๋ชจ๋“œ. +6. **์ž๋™์™„๋ฃŒ ์ •์ฑ…์€ 3๊ฐœ ํ‘œ๋ฉด์—์„œ ํ•œ ์ค„๋„ ๋ฐ”๋€Œ์ง€ ์•Š๋Š”๋‹ค.** ํ…Œ์ŠคํŠธ๋กœ ๊ณ ์ •ํ•œ๋‹ค. + +### 0-1. Rev.1 โ†’ Rev.2 ๋ณ€๊ฒฝ ๋‚ด์—ญ (์ด์˜์ œ๊ธฐ ๋ฐ˜์˜) + +| ์ด์˜์ œ๊ธฐ ํ•ญ๋ชฉ | Planner ๋…๋ฆฝ ๊ฒ€์ฆ ๊ฒฐ๊ณผ | ๊ณ„ํš ๋ณ€๊ฒฝ | +|---|---|---| +| ยง1 D2 `sys.path.insert(0)`์ด pytest์—์„œ `lib_py` ์ „์ฒด๋ฅผ ์ƒŒ๋“œ๋ฐ•์Šค๋กœ ๊ณ ์ • | **์žฌํ˜„ ํ™•์ธ** (ยง1-8 ํ‘œ, ๋ณ€ํ˜• `d2_insert0` ์‹œ๋‚˜๋ฆฌ์˜ค A: `lib_py.agents`๊ฐ€ sandbox์—์„œ ๋กœ๋“œ) | D2 ์ „๋ฉด ๊ต์ฒด | +| ยง2-1 ์ •์ฒด์„ฑ ๋‹จ์ •(`is`)์ด "๊ฐ™์€ ํŠธ๋ฆฌ"๋ฅผ ์ฆ๋ช…ํ•˜์ง€ ๋ชปํ•จ | **๋™์˜**. ์‹œ๋‚˜๋ฆฌ์˜ค A์—์„œ ๋‘ ์ฐธ์กฐ๊ฐ€ ๋ชจ๋‘ sandbox์ธ๋ฐ ๋‹จ์ •์€ `True` | D2-2 ์ถœ์ฒ˜(provenance) ๋‹จ์ • PV-1~PV-4 ์ถ”๊ฐ€, G4 ์žฌ์ •์˜ | +| ยง2-2 R3 ์ „์ œ("์‹ค์ œ ํŠธ๋ฆฌ์—์„œ๋งŒ ๋กœ๋“œ")๊ฐ€ D2์™€ ๋ชจ์ˆœ | **๋™์˜** | R3์„ "์ˆ˜์šฉ"์—์„œ "ํ•ด์†Œ"๋กœ ์žฌ์ž‘์„ฑ | +| ยง3 `append`๋กœ ๋ฐ”๊พธ๋ฉด freeze๊ฐ€ ๊นจ์ง | **๋™์˜**. ๋ณธ ์„ธ์…˜ `PYTHONPATH`=๋ผ์ด๋ธŒ ํŠธ๋ฆฌ, ์‹คํ–‰ ๋ช…๋ น=freeze ๊ฒฝ๋กœ, `run_loop.sh`์— `PYTHONPATH` ์ฒ˜๋ฆฌ 0๊ฑด ๋ชจ๋‘ ์žฌํ™•์ธ | D2-1 ์ปจํ…์ŠคํŠธ ์šฐ์„ ์ˆœ์œ„ ํ‘œ ์‹ ์„ค | +| ยง5-1 "์˜† ํŒŒ์ผ ์ง์ ‘ ์ ์žฌ(`spec_from_file_location`) + ๋‹ค๋ฅด๋ฉด ๊ต์ฒด" | **๋ถ€๋ถ„ ์ฑ„ํƒ**. ์žŽ ๋ชจ๋“ˆ ๋‹จ๋… ์ž„ํฌํŠธ์—์„œ๋Š” ๋™์ž‘ํ•˜๋‚˜, `publish_event`๋งŒ ์ž„ํฌํŠธ๋˜๋Š” ์‹ค์ œ ์—์ด์ „ํŠธ ํ˜ธ์ถœ ํ˜•ํƒœ(C2_pe)์—์„œ `publish_event`=freeze, `mqtt_common`=live๋กœ **ํ˜ผ์žฌ**ํ•˜๊ณ  ์ •์ฒด์„ฑ ๋‹จ์ •์ด `False`๊ฐ€ ๋จ. ์ง„์งœ standalone(C1)์—์„œ๋Š” ๋ถ€๋ชจ ํŒจํ‚ค์ง€ ๋ถ€์žฌ๋กœ `ModuleNotFoundError`. ๋‘ ์ƒŒ๋“œ๋ฐ•์Šค๊ฐ€ ์„ž์ด๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค(D)์—์„œ๋„ ํ˜ผ์žฌ | ์ ์žฌ ๋‹จ์œ„๋ฅผ **๋ชจ๋“ˆ โ†’ ํŒจํ‚ค์ง€**๋กœ ์˜ฌ๋ฆฌ๊ณ , "๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ์‚ฌ๋ณธ์œผ๋กœ ๊ต์ฒด"๊ฐ€ ์•„๋‹ˆ๋ผ **"ํ”„๋กœ์„ธ์Šค๋‹น ์ตœ์ดˆ ๋ฐ”์ธ๋”ฉ ์œ ์ง€(bind-once)"** ๋กœ ๊ทœ์น™ ๋ณ€๊ฒฝ. standalone์€ `lib_py` ๋ถ€์žฌ ์‹œ์—๋งŒ `sys.path.append` ํด๋ฐฑ | +| ยง5-2 ์ถœ์ฒ˜ ๋‹จ์ • ๋ณ‘๊ธฐ | ์ฑ„ํƒ | PV-1~PV-4 | +| ยง5-3 ์ปจํ…์ŠคํŠธ 4์ข… ์šฐ์„ ์ˆœ์œ„ ํ‘œ + 4์ข… ์ „๋ถ€ ํ…Œ์ŠคํŠธ | ์ฑ„ํƒ (6 ์ปจํ…์ŠคํŠธ๋กœ ํ™•์žฅ) | D2-1, P1-01 ํ…Œ์ŠคํŠธ | +| ยง5-4 ์„ธ์…˜ ์œ„์ƒ ํ…Œ์ŠคํŠธ | **๋ถ€๋ถ„ ์ฑ„ํƒ**. "tmp ๊ฒฝ๋กœ๊ฐ€ `sys.path`์— ์—†์Œ"์€ ํ˜„ํ–‰ `test_tier1_unit.py:23`์ด ์ƒŒ๋“œ๋ฐ•์Šค scripts ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ์˜๊ตฌ ์‚ฝ์ž…ํ•˜๋ฏ€๋กœ ์ด๋ฏธ ์‹คํŒจํ•จ. ์กฐ๊ฑด์„ "`lib_py` ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ ํฌํ•จํ•˜๋Š” tmp ๊ฒฝ๋กœ๊ฐ€ ์—†์Œ"์œผ๋กœ ์ •๋ฐ€ํ™” | PV-3 | +| ยง6 D6ยท์„ค์น˜ ๊ฒฝ๋กœ ์•ˆ์ „ ํ™•์ธ | **๋™์˜**. `mqtt_common.py`์˜ `os.environ` ์ฐธ์กฐ๋Š” ์ „๋ถ€ ํ•จ์ˆ˜ ๋‚ด๋ถ€(`:139,:196,:203,:225-233`), ๋ชจ๋“ˆ ์ƒ์ˆ˜(`:117-128`) env ๋ฌด๊ด€, ๋‚˜๋จธ์ง€ 3๊ฐœ ์Šคํฌ๋ฆฝํŠธ `os.environ` 0๊ฑด, `install.sh:127/131/135` ํŠธ๋ฆฌ ํ†ต์งธ ๋ณต์‚ฌ, `install_mam.sh:126` `.agents/` rsync ์žฌํ™•์ธ | ๋ณ€๊ฒฝ ์—†์Œ (P1-04์˜ `core_files` ์ถ”๊ฐ€๋Š” ๋ฌด๊ฒฐ์„ฑ ๋ชฉ๋ก ๊ฐฑ์‹  ๋ชฉ์ ์ž„์„ ๋ช…์‹œ) | + +--- + +## 1. Planner ์‚ฌ์ „ ์‹ค์ธก (๋ผ์ด๋ธŒ ๊ฒ€์ฆ ๊ฒฐ๊ณผ) + +### 1-1. ์ถ”์ถœ ๋ฒ”์œ„ ๊ฒฝ๊ณ„ (`lib.sh`, ์ด 2312ํ–‰) + +| ํ–‰ | ํ•จ์ˆ˜ | ๋ถ„๋ฅ˜ | ๋น„๊ณ  | +|---|---|---|---| +| 1833 | ์„น์…˜ ์ฃผ์„ `multi-agent-mux-delegate-job integration helpers` | ๊ฒฝ๊ณ„ ์‹œ์ž‘ | | +| 1841 | `_delegate_py_bin` | **์…ธ ์œ ์ง€** | ํŒŒ์ด์ฌ ์ธํ„ฐํ”„๋ฆฌํ„ฐ ์ž์ฒด๋ฅผ ์ฐพ๋Š” ๋ถ€ํŠธ์ŠคํŠธ๋žฉ. L1598(`env_python`), L1660(`atomic_dump_yaml`), `reconcile.sh:67`, `create_session.sh:191/193/215/482`, `stop_session.sh:199`, `resume_session.sh:116/126`์—์„œ๋„ ์‚ฌ์šฉ. `tests/test_c1_tui_readiness.py:168,513`์ด mock์œผ๋กœ ๋ฎ์–ด์”€ | +| 1865 | `_delegate_script` | **์…ธ ์œ ์ง€** | ์Šคํฌ๋ฆฝํŠธ ๊ฒฝ๋กœ ํ•ด์„. shim์ด ๋‚จ์œผ๋ฏ€๋กœ ๊ทธ๋Œ€๋กœ ๋™์ž‘ | +| 1876 | `delegate_submit_job` | ์ถ”์ถœ | `registry.py register` ํ˜ธ์ถœ, stdout์— JID๋งŒ ์ถœ๋ ฅ | +| 1897 | `delegate_publish_event` | ์ถ”์ถœ | ๋น„์น˜๋ช… ๊ณ„์•ฝ: ๋นˆ job_id/์Šคํฌ๋ฆฝํŠธ ๋ถ€์žฌ/๋ธŒ๋กœ์ปค ์‹คํŒจ ๋ชจ๋‘ `return 0` | +| 1912 | `start_watchdog` | ์ถ”์ถœ โ†’ `ensure_subscriber_daemon` | `pgrep -f "bash --subscribe"` ์„ ๊ฒ€์‚ฌ ํ›„ `nohup bash reconcile.sh --subscribe --idle-timeout 0` ์Šคํฐ, PID๋ฅผ stdout ์ถœ๋ ฅ. ์Šคํฌ๋ฆฝํŠธ ๋ถ€์žฌ ์‹œ stderr `ERROR: monitor script not found: ...` + `return 1` | +| 1943~ | `wait_for_tui_ready`, `inject_instructions`, `_pane_*`, `send_keys_safe`, `handle_startup_dialogs` | **๋น„๋ฒ”์œ„** | TUI ์กฐ์ž‘ | + +`lib_py`๋Š” `lib.sh:25`(`SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"` ์งํ›„ `export PYTHONPATH="$SKILL_DIR:..."`) ๋ฐ `:164`๋กœ ์ž„ํฌํŠธ ๊ฐ€๋Šฅํ•ด์ง„๋‹ค. ์ฆ‰ **์–ด๋А ํŠธ๋ฆฌ์˜ `lib.sh`๋ฅผ source ํ–ˆ๋А๋ƒ๊ฐ€ ๊ทธ ์…ธ ํ”„๋กœ์„ธ์Šค์˜ `lib_py` ํŠธ๋ฆฌ๋ฅผ ๊ฒฐ์ •**ํ•œ๋‹ค. `env_python`(L1583)์€ `PYTHONPATH=$SKILL_DIR`๋ฅผ ๋ช…์‹œ ์ „๋‹ฌํ•˜๋Š” ์„ ๋ก€. + +### 1-2. ์…ธ ํ—ฌํผ ํ˜ธ์ถœ์ž (๋ผ์ด๋ธŒ ํŠธ๋ฆฌ) + +| ํ˜ธ์ถœ์ž | ํ—ฌํผ | ๊ณ„์•ฝ์ƒ ์˜์กด | +|---|---|---| +| `create_session.sh:345` | `delegate_submit_job` | `$( )`๋กœ stdout ์บก์ฒ˜ โ†’ JID | +| `create_session.sh:497,501` | `delegate_publish_event error/started` | ๋น„์น˜๋ช… | +| `create_session.sh:504-505` | `start_watchdog "$JOB" "$WORKSPACE"` | `$( )`๋กœ PID ์บก์ฒ˜ ํ›„ `echo`๋งŒ ํ•จ(ํŒŒ์‹ฑ ์—†์Œ) | +| `stop_session.sh:193,242,322` | `delegate_publish_event progress/cancelled` | `implementation_plan.md` ยง3-2๊ฐ€ ๋ณด์กด ์š”๊ตฌ | +| `update_yaml_resumed.sh:257` | `delegate_publish_event progress "resumed"` | ๋น„์น˜๋ช… | +| `run_loop.sh:144-155` | (ํ—ฌํผ ๋ฏธ์‚ฌ์šฉ) ๋ž˜ํผ `multi-agent-mux-delegate-job`๋ฅผ ์ง์ ‘ ์‹คํ–‰ | Phase 1 ๋ฌด๊ด€ | + +### 1-3. ํŒŒ์ด์ฌ ํ‘œ๋ฉด๊ณผ ๋ฆฌํ„ฐ๋Ÿด ๊ฒฝ๋กœ ๊ณ„์•ฝ + +- ์Šคํฌ๋ฆฝํŠธ 4์ข…: `mqtt_common.py`(670ํ–‰), `registry.py`(426ํ–‰), `publish_event.py`(239ํ–‰), `job_subscriber.py`(339ํ–‰). ์„œ๋กœ๋ฅผ `import mqtt_common` / `import registry`์ฒ˜๋Ÿผ **์ตœ์ƒ์œ„ ์ด๋ฆ„**์œผ๋กœ ์ž„ํฌํŠธํ•œ๋‹ค. **๋‹ค๋ฅธ `lib_py` ๋ชจ๋“ˆ์€ ํ•˜๋‚˜๋„ ์ž„ํฌํŠธํ•˜์ง€ ์•Š๋Š”๋‹ค**(`grep -n lib_py scripts/*.py` โ†’ 0๊ฑด; D2์˜ ์ „์ œ). +- ๋ฆฌํ„ฐ๋Ÿด ๊ฒฝ๋กœ ํ˜ธ์ถœ์ž: ๋ž˜ํผ 12๊ฐœ์†Œ(`:136,166,197,277,287,318,355,394,400,544,550,571,575,583,586`), `create_session.sh:483`, `SKILL.md:78`, `BOOTSTRAP.md`/`BOOTSTRAP.ko.md:158-180`, `README.md:211`/`README.ko.md:194`, `MESSAGING.md:399`, `deploy/install.sh:103`, `reconcile.sh:98-113`(์ž„๋ฒ ๋””๋“œ ๊ตฌ๋…์ž๊ฐ€ scripts ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ `sys.path`์— **append** ํ›„ `import mqtt_common, registry`). +- ํ…Œ์ŠคํŠธ ์ž„ํฌํŠธ ๋ฐฉ์‹: `tests/test_tier1_unit.py:23,571-820`๊ณผ `tests/test_tier2_component.py:25`๋Š” ์ƒŒ๋“œ๋ฐ•์Šค scripts ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ `sys.path`์— ๋„ฃ๊ณ  `import registry, publish_event, job_subscriber, mqtt_common`; `tests/test_tier4_e2e.py:414`๋Š” `publish_event.py`๋ฅผ ์„œ๋ธŒํ”„๋กœ์„ธ์Šค๋กœ ์‹คํ–‰. `tests/conftest.py:10-12`๋Š” **์‹ค์ œ** skills ๋””๋ ‰ํ„ฐ๋ฆฌ๋ฅผ `sys.path[0]`์— ๋„ฃ๊ณ , `mam_sandbox`๋Š” `.agents/skills` ์ „์ฒด๋ฅผ `tmp_path/skills`์™€ `tmp_path/.agents/skills`๋กœ ๋ณต์‚ฌํ•œ๋‹ค(์ด๋™ ํ›„ ์‚ฌ๋ณธ์—๋„ `lib_py/delegation/`์ด ํฌํ•จ๋จ). +- ๋ณธ job๊ณผ Rev.1 job์˜ ์œ„์ž„ ๋ฉ”์‹œ์ง€ ์ž์ฒด๊ฐ€ `.../mam-loop-freeze.PfAQNj/.agents/skills/multi-agent-mux-delegate-job/scripts/publish_event.py` ๊ฒฝ๋กœ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ์ด๋•Œ ์—์ด์ „ํŠธ ์„ธ์…˜์˜ `PYTHONPATH`๋Š” **๋ผ์ด๋ธŒ ํŠธ๋ฆฌ**๋‹ค(์‹ค์ธก: `/Users/.../multi-agent-mux/.agents/skills:...`). `run_loop.sh`์—๋Š” `PYTHONPATH` ์ฒ˜๋ฆฌ๊ฐ€ ์—†๊ณ (`grep` 0๊ฑด) `:14-17`์—์„œ ์ž๊ธฐ ์œ„์น˜ ๊ธฐ์ค€ `REPO_ROOT`์˜ `lib.sh`๋ฅผ source ํ•˜๋ฏ€๋กœ **๋ฃจํ”„ ํ”„๋กœ์„ธ์Šค ์ž์ฒด๋Š” freeze `lib.sh`๊ฐ€ exportํ•œ freeze `PYTHONPATH`** ๋ฅผ ๊ฐ–๋Š”๋‹ค. + +### 1-4. ๊ธฐ์กด ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ ์‹ค์ธก + +| ๋Œ€์ƒ | ์ปค๋ฒ„ ํ…Œ์ŠคํŠธ | ๋น„๊ณ  | +|---|---|---| +| `publish_event` ์‹คํŒจ ์‹œ status/๊ฐ์‚ฌ๋กœ๊ทธ/seq ์†Œ๋น„ | `test_tier1_unit.py` G-1~G-4 (:568-698) | 127.0.0.1:65432 ๋น„๋ผ์šฐํŒ… ํฌํŠธ, ๋„คํŠธ์›Œํฌ ๋ฏธ์ ‘์ด‰ | +| `job_subscriber` ๋””์Šคํฌ ํด๋ฐฑ/exit 0ยท1ยท3 | G-5~G-9 (:700-836) | | +| ๋ž˜ํผ rc=3 ๋งคํ•‘ ๋ฆฌํ„ฐ๋Ÿด | G-10 (:838) | **๋ž˜ํผ ํ…์ŠคํŠธ๋ฅผ ๊ฑด๋“œ๋ฆฌ๋ฉด ๊นจ์ง** | +| HMAC/retry/topic/reason_code | `test_mqtt_*` (:339-420) | | +| seq ๋‹จ์กฐ ์ฆ๊ฐ€, ๋ชจ๋‹ˆํ„ฐ ์ค‘๋ณต ์ž ๊ธˆ | `test_tier2_component.py:1082, :1007` | | +| `delegate_submit_job` / `delegate_publish_event` / `start_watchdog` | **์—†์Œ (0๊ฑด)** | | + +๋น ๋ฅธ ํšŒ๊ท€ ๋ถ€๋ถ„์ง‘ํ•ฉ(G-1~G-10 + mqtt + seq + monitor lock): **18 passed in 1.74s**. ์ด ๋ถ€๋ถ„์ง‘ํ•ฉ์˜ ํŒŒ์ผ๋“ค์€ `lib_py`๋ฅผ ๋ชจ๋“ˆ ๋ ˆ๋ฒจ์—์„œ ์ž„ํฌํŠธํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ(`test_tier1_unit.py`์˜ `lib_py` ์‚ฌ์šฉ์€ `:848,:873,:902,:947,:978,:1037,:1072` ์ „๋ถ€ ํ•จ์ˆ˜ ๋‚ด๋ถ€) ์ด์˜์ œ๊ธฐ ยง4์˜ ์ง€์ ๋Œ€๋กœ "์ƒŒ๋“œ๋ฐ•์Šค shim์ด ๋จผ์ € ์ž„ํฌํŠธ๋˜๋Š”" ์‹œ๋‚˜๋ฆฌ์˜ค A์— ํ•ด๋‹นํ•œ๋‹ค โ€” Rev.2 D2๋Š” ์ด ๊ฒฝ์šฐ์—๋„ `lib_py.agents` ๋“ฑ์„ ๊ฐ€๋กœ์ฑ„์ง€ ์•Š๋Š”๋‹ค(ยง1-8). + +### 1-5. ์ž๋™์™„๋ฃŒ ์ •์ฑ…์ด ๊ตฌํ˜„๋œ 3๊ฐœ ํ‘œ๋ฉด (Phase 1 ๋ถˆ๋ณ€) + +1. `publish_event.py:51-56` `EVENT_TO_STATUS` ๋ฐ `:227`์˜ `update_job_status` ๋™๊ธฐํ™”. +2. ๋ž˜ํผ `:335-348` ๊ตฌ๋…์ž exit code โ†’ `job_status` ๋งคํ•‘ ๋ฐ `:394/:400`์˜ `registry.py status --set completed|error`. +3. `reconcile.sh:125-168` `_MUTATION` + `handle_terminal`. + +`run_loop.sh:238-276` `wait_for_job`์€ `.mam/jobs/.json`์˜ `status`๋ฅผ 3์ดˆ ๊ฐ„๊ฒฉ ํด๋งํ•  ๋ฟ์ด๋ฏ€๋กœ ์˜ํ–ฅ ์—†์Œ. + +### 1-6. ์žฅ์ˆ˜ ์—ฐ๊ฒฐ์˜ ์‹ค์ธก ๋™๊ธฐ + +Rev.1 job์˜ `started` ์ด๋ฒคํŠธ: payload `timestamp` 05:58:49Z โ†’ `attempt 1/3 failed: no CONNACK from broker` (05:59:00Z) โ†’ ๋ชจ๋‹ˆํ„ฐ ์ˆ˜์‹  05:59:01.487Z. 1ํšŒ ๋ฐœํ–‰ ์•ฝ 12.5์ดˆ, ์›์ธ์€ `CONNECT_ACK_TIMEOUT=10`(`publish_event.py:58`)์˜ 1ํšŒ ์‹คํŒจ + ์žฌ์—ฐ๊ฒฐ. Rev.1 `completed` ๋ฐœํ–‰๊ณผ ๋ณธ job์˜ `started` ๋ฐœํ–‰์—์„œ๋„ ๋™์ผํ•œ 1ํšŒ CONNACK ์‹คํŒจ๊ฐ€ ์žฌํ˜„๋๋‹ค. + +### 1-7. ํ™˜๊ฒฝ ์ œ์•ฝ + +- ๋กœ์ปฌ ๋ธŒ๋กœ์ปค ์—†์Œ(`mosquitto`, `nats-server`, `docker` ๋ฏธ์„ค์น˜). ๋ฒค์น˜๋งˆํฌ ์‹ค๋ธŒ๋กœ์ปค ๋ชจ๋“œ๋Š” ์ •๋ณด์„ฑ. +- `mqtt_common.py:112`์—์„œ `_load_dotenv()`๊ฐ€ **์ž„ํฌํŠธ ์‹œ์ **์— ์‹คํ–‰๋˜์–ด `.mam.env`(์กด์žฌ, 0600)๋ฅผ ์ฃผ์ž…. ๋ฃจํŠธ ํƒ์ƒ‰์€ `__file__` ๊ธฐ์ค€ ์ƒํ–ฅ(`.agents` ๋˜๋Š” `.git`). +- B-13 freeze(`run_loop.sh:112-126`)๋Š” `.agents/skills` ์ „์ฒด๋ฅผ `cp -R` ํ•˜๋ฏ€๋กœ `lib_py/delegation/`์€ ์ž๋™ ํฌํ•จ(ํ˜„์žฌ freeze์— `lib_py/` ์กด์žฌ ํ™•์ธ). +- `baseline.md` ยง5-2 `VALID_STATUSES` ์ธ์šฉ์€ ์ปค๋ฐ‹ `19d798b`์—์„œ `registry.py:38`๋กœ ์ •์ • ์™„๋ฃŒ(`baseline.md:93`). + +### 1-8. ์ด์˜์ œ๊ธฐ ์žฌํ˜„ ๋ฐ ๋Œ€์•ˆ ๋น„๊ต (Rev.2 ์‹ ๊ทœ) + +์„ธ shim ๋ณ€ํ˜•์„ `real`/`sandbox`/`sandbox2`/`freeze` ๋„ค ํŠธ๋ฆฌ(๊ฐ `lib_py/{__init__,agents/__init__,delegation/{__init__,mqtt_common,publish_event}}.py` + `scripts/` shim)์— ๋ฐฐ์น˜ํ•˜๊ณ , `env -u PYTHONPATH`์˜ ์ƒˆ ์ธํ„ฐํ”„๋ฆฌํ„ฐ์—์„œ ์‹œ๋‚˜๋ฆฌ์˜ค๋ณ„๋กœ ์‹คํ–‰ํ–ˆ๋‹ค. `publish_event`๋Š” `from lib_py.delegation import mqtt_common`์„ ํฌํ•จํ•ด **๋น„-์žŽ ๋ชจ๋“ˆ**์„ ๋ชจ๋ธ๋งํ•œ๋‹ค. ์—ด: ์–ด๋А ํŠธ๋ฆฌ์—์„œ ๋กœ๋“œ๋๋Š”๊ฐ€. + +| ์‹œ๋‚˜๋ฆฌ์˜ค (์ฒซ ์ž„ํฌํŠธ โ†’ ์ดํ›„) | `d2_insert0` (Rev.1) | `worker_leaf_file` (์ด์˜์ œ๊ธฐ ยง5-1) | `pkg_bind_once` (**Rev.2 ์ฑ„ํƒ**) | +|---|---|---|---| +| A pytest: real on path โ†’ sandbox shim(mc, pe) | mc=sb, pe=sb, **agents=sb** โœ— | mc=sb, pe=sb, agents=real | mc=sb, pe=sb, agents=real โœ“ | +| A_pe pytest: sandbox shim(**pe๋งŒ**) | agents=sb โœ— | **pe=sb, pe.dep=real, identity=False** โœ— | pe=sb, pe.dep=sb, identity=True โœ“ | +| B pytest: real `lib_py.agents` ๋จผ์ € โ†’ sandbox shim | ์ „๋ถ€ real | mc=sb, pe=sb, agents=real | mc=sb, pe=sb, agents=real โœ“ | +| B2 pytest: real `lib_py.delegation` ๋จผ์ € โ†’ sandbox shim | (๋ฏธ์ธก์ •) | **alias=sb, pkg=real, identity=False** โœ— | ์ „๋ถ€ real, identity=True โœ“ | +| C1 standalone(`PYTHONPATH` ์—†์Œ): freeze shim | ์ „๋ถ€ freeze | **`ModuleNotFoundError: lib_py`** โœ— (๋น„-์žŽ ๋ชจ๋“ˆ์˜ ๋‚ด๋ถ€ ์ž„ํฌํŠธ๊ฐ€ ๋ถ€๋ชจ ํŒจํ‚ค์ง€๋ฅผ ์š”๊ตฌ) | ์ „๋ถ€ freeze โœ“ (ํด๋ฐฑ append) | +| C2 freeze shim + `PYTHONPATH`=live (mc, pe) | ์ „๋ถ€ freeze | ์ „๋ถ€ freeze, agents=real | ์ „๋ถ€ freeze, agents=real โœ“ | +| C2_pe freeze shim(**pe๋งŒ**) + `PYTHONPATH`=live โ€” **์‹ค์ œ ์—์ด์ „ํŠธ ํ˜ธ์ถœ ํ˜•ํƒœ** | ์ „๋ถ€ freeze | **pe=freeze, pe.dep=real, identity=False** โœ— (B-13 ๋ถ€๋ถ„ ์ƒ์‹ค) | pe=freeze, pe.dep=freeze โœ“ | +| D ๋‘ ์ƒŒ๋“œ๋ฐ•์Šค: sb shim(mc) โ†’ sb2 shim(pe) | agents=sb โœ— | **pe=sb2, pe.dep=sb** ํ˜ผ์žฌ โœ— | ์ „๋ถ€ sb (์ตœ์ดˆ ๋ฐ”์ธ๋”ฉ ์œ ์ง€) โœ“ | +| E sandbox shim(mc) โ†’ ์ดํ›„ `from lib_py.delegation import publish_event` ์ง์ ‘ ์ž„ํฌํŠธ | (ํ•ด๋‹น ์—†์Œ) | ์ง์ ‘ ์ž„ํฌํŠธ=real, shim=sb ํ˜ผ์žฌ | ๋‘˜ ๋‹ค sb โœ“ | + +๊ฒฐ๋ก : ์ด์˜์ œ๊ธฐ์˜ **๊ฒฐํ•จ ์ง€์ ์€ ์ •ํ™•**ํ•˜๊ณ , ์ œ์•ˆํ•œ ์ ์žฌ ๋ฐฉ์‹์€ "์žŽ ๋ชจ๋“ˆ์„ ๋จผ์ € ์ž„ํฌํŠธํ•˜๋Š”" ๊ฒฝ์šฐ์—๋งŒ ์„ฑ๋ฆฝํ•œ๋‹ค. ๊ฒฐ์ • ๊ธฐ์ค€์€ (i) `lib_py.delegation.*`๊ฐ€ ํ•œ ํ”„๋กœ์„ธ์Šค ์•ˆ์—์„œ ํ•ญ์ƒ **๋‹จ์ผ ํŠธ๋ฆฌ**์ผ ๊ฒƒ, (ii) `lib_py`์˜ ๋‹ค๋ฅธ ์„œ๋ธŒํŒจํ‚ค์ง€๋Š” shim์ด **์ ˆ๋Œ€ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์„ ๊ฒƒ**, (iii) `sys.path` ๋ฌด๋ณ€๊ฒฝ(standalone ํด๋ฐฑ ์ œ์™ธ) โ€” ์ด๋ฅผ ๋ชจ๋‘ ๋งŒ์กฑํ•˜๋Š” ๊ฒƒ์€ ํŒจํ‚ค์ง€ ๋‹จ์œ„ bind-once๋ฟ์ด๋‹ค. ์žฌํ˜„ ํ•˜๋‹ˆ์Šค๋Š” `tests/fixtures/issue_5/phase1/shim_contexts.py`๋กœ ๊ทธ๋Œ€๋กœ ์˜ฎ๊ฒจ P1-01 ํ…Œ์ŠคํŠธ์— ์‚ฌ์šฉํ•œ๋‹ค(๋ถ€๋ก B). + +--- + +## 2. ์„ค๊ณ„ ๊ฒฐ์ • (Design Decisions) + +### D1. ํŒจํ‚ค์ง€ ๋ฐฐ์น˜ โ€” ๊ธฐ์กด ์ด๋ฆ„ ๋ณด์กด, ์‹ ๊ทœ ์ถ”๊ฐ€ + +``` +.agents/skills/lib_py/delegation/ + __init__.py # ๊ณต๊ฐœ API ์žฌ์ˆ˜์ถœ: DelegationService, ensure_subscriber_daemon, PersistentPublisher + mqtt_common.py # git mv (scripts/mqtt_common.py) โ€” ๋‚ด์šฉ ๋ณ€๊ฒฝ์€ D6ยทD8 ํ•ญ๋ชฉ๋งŒ + registry.py # git mv (scripts/registry.py) + publish_event.py # git mv (scripts/publish_event.py) โ€” D7์˜ ํ•จ์ˆ˜ ๋ถ„๋ฆฌ๋งŒ + job_subscriber.py # git mv (scripts/job_subscriber.py) + service.py # DelegationService ํŒŒ์‚ฌ๋“œ (์‹ ๊ทœ) + daemon.py # ensure_subscriber_daemon (์‹ ๊ทœ, start_watchdog ์ด์‹) + publisher.py # OneShotPublisher / PersistentPublisher (์‹ ๊ทœ) + cli.py # python -m lib_py.delegation.cli {submit|publish|ensure-daemon} (์‹ ๊ทœ) + __main__.py # cli.main ์œ„์ž„ (์‹ ๊ทœ) + bench.py # ๋ฒค์น˜๋งˆํฌ ํ•˜๋‹ˆ์Šค (์‹ ๊ทœ) +``` + +- `git mv`๋กœ ์ด๋™ํ•ด ์ด๋ ฅ์„ ๋ณด์กดํ•œ๋‹ค. ์ด๋™ํ•œ 4๊ฐœ ๋ชจ๋“ˆ ๋‚ด๋ถ€์˜ `import mqtt_common` / `import registry` / `from mqtt_common import ...`๋Š” ์ „๋ถ€ `from lib_py.delegation import mqtt_common, registry` ํ˜•ํƒœ๋กœ ๋ฐ”๊พผ๋‹ค. +- **ํŒจํ‚ค์ง€ ๋‚ด๋ถ€ ์ž„ํฌํŠธ ๊ทœ์น™(ํ…Œ์ŠคํŠธ๋กœ grep ๊ณ ์ •)**: (a) ์ตœ์ƒ์œ„ ์ด๋ฆ„(`import registry` ๋“ฑ) ๊ธˆ์ง€ โ€” `lib_py.agents.registry`์™€์˜ ํ˜ผ๋™ ์ฐจ๋‹จ; (b) **`lib_py.delegation` ๋ฐ–์˜ ์–ด๋–ค `lib_py` ๋ชจ๋“ˆ๋„ ์ž„ํฌํŠธ ๊ธˆ์ง€**(`paths`, `state`, `atomic_yaml`, `agents`, `layout`, `workspace_uuid`) โ€” D2์—์„œ ๋ถ€๋ชจ `lib_py`์™€ `lib_py.delegation`์ด ์„œ๋กœ ๋‹ค๋ฅธ ํŠธ๋ฆฌ์ผ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ(C2), ํ˜•์ œ ๋ชจ๋“ˆ์„ ์ž„ํฌํŠธํ•˜๋ฉด ํŠธ๋ฆฌ๊ฐ€ ์„ž์ธ๋‹ค. ํ˜„์žฌ ์Šคํฌ๋ฆฝํŠธ๋Š” ์ด๋ฏธ ์ด ๊ทœ์น™์„ ๋งŒ์กฑํ•œ๋‹ค(ยง1-3). +- Python 3.9 ํ˜ธํ™˜ ํ•„์ˆ˜: `match`, PEP 604 ๋Ÿฐํƒ€์ž„ ์œ ๋‹ˆ์˜จ, `zip(strict=)` ๊ธˆ์ง€. ๊ฒŒ์ดํŠธ์—์„œ `compileall`. + +### D2. `scripts/*.py` shim โ€” `lib_py.delegation` ํŒจํ‚ค์ง€๋ฅผ ์ž๊ธฐ ์˜† ํŠธ๋ฆฌ์— ํ”„๋กœ์„ธ์Šค๋‹น 1ํšŒ ๋ฐ”์ธ๋”ฉ (Rev.2) + +4๊ฐœ shim์€ ์•„๋ž˜ ๋ธ”๋ก์„ **๋ฐ”์ดํŠธ ๋™์ผํ•˜๊ฒŒ** ํฌํ•จํ•œ๋‹ค(`{mod}`๋งŒ ๋‹ค๋ฆ„). ๊ณต์œ  ํ—ฌํผ ํŒŒ์ผ๋กœ ๋นผ์ง€ ์•Š๋Š” ์ด์œ : `import _bootstrap` ๊ฐ™์€ ์ตœ์ƒ์œ„ ์ด๋ฆ„์ด ์ฒซ ์ƒŒ๋“œ๋ฐ•์Šค์— ์บ์‹œ๋˜์–ด ๋‹ค๋ฅธ ํŠธ๋ฆฌ์˜ shim์ด ๋‚จ์˜ ํ—ฌํผ๋ฅผ ์“ฐ๊ฒŒ ๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ๋™์ผ์„ฑ์€ ํ…Œ์ŠคํŠธ๋กœ ๊ณ ์ •ํ•œ๋‹ค. + +```python +#!/usr/bin/env python3 +"""Compatibility shim โ€” implementation lives in lib_py.delegation.{mod}. +Binds the lib_py.delegation *package* to the tree next to this file, once per +process, without touching sys.path. See docs/contracts/issue-5/... (D2).""" +import importlib, importlib.util, os, sys +_SKILLS = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) +_PKG = "lib_py.delegation" +if _PKG not in sys.modules: # bind once per process; never rebind + try: + import lib_py # parent resolved normally; shims never rebind it + except ModuleNotFoundError: + sys.path.append(_SKILLS) # standalone only: no lib_py exists anywhere to shadow + import lib_py + _d = os.path.join(_SKILLS, "lib_py", "delegation") + _spec = importlib.util.spec_from_file_location( + _PKG, os.path.join(_d, "__init__.py"), submodule_search_locations=[_d]) + _pkg = importlib.util.module_from_spec(_spec) + sys.modules[_PKG] = _pkg # register BEFORE exec so __init__'s own imports resolve here + _spec.loader.exec_module(_pkg) + lib_py.delegation = _pkg +_impl = importlib.import_module(_PKG + ".{mod}") # resolved through the bound package's __path__ +if __name__ != "__main__": + sys.modules[__name__] = _impl # `import {mod}` is lib_py.delegation.{mod} +else: + sys.exit(_impl.main()) +``` + +๊ทœ์น™(๋ฌธ์„œํ™”ยทํ…Œ์ŠคํŠธ ๋Œ€์ƒ): +1. **๋ฐ”์ธ๋”ฉ ๋‹จ์œ„๋Š” ํŒจํ‚ค์ง€**๋‹ค. `lib_py.delegation`์ด `sys.modules`์— ์—†์„ ๋•Œ๋งŒ shim ์˜† ํŠธ๋ฆฌ์˜ `lib_py/delegation/`์„ ํŒจํ‚ค์ง€๋กœ ๋“ฑ๋กํ•˜๊ณ , ์ดํ›„ ๋ชจ๋“  `lib_py.delegation.*` ์ž„ํฌํŠธ๋Š” ๊ทธ `__path__` ์•ˆ์—์„œ ํ•ด์„๋œ๋‹ค. ์žŽ/๋น„-์žŽ ๊ตฌ๋ถ„ ์—†์ด ๋‹จ์ผ ํŠธ๋ฆฌ๊ฐ€ ๋ณด์žฅ๋œ๋‹ค. +2. **bind-once, never rebind.** ์ด๋ฏธ ๋ฐ”์ธ๋”ฉ๋ผ ์žˆ์œผ๋ฉด(๋‹ค๋ฅธ ํŠธ๋ฆฌ๋ผ๋„) ๊ทธ๋Œ€๋กœ ์“ด๋‹ค. "๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ์‚ฌ๋ณธ์œผ๋กœ ๊ต์ฒด"๋Š” ๊ธฐ์กด ์ฐธ์กฐ(`sys.modules['mqtt_common']` ๋ณ„์นญ ๋“ฑ)์™€ ์ƒˆ ๊ฐ์ฒด๋ฅผ ๊ฐˆ๋ผ๋†“์•„ ์ •์ฒด์„ฑยทmonkeypatch๋ฅผ ๊นจ๋œจ๋ฆฐ๋‹ค(ยง1-8 B2ยทD). ๋„ค ์ปจํ…์ŠคํŠธ ์ค‘ ํ•œ ํ”„๋กœ์„ธ์Šค๊ฐ€ ํŠธ๋ฆฌ๋ฅผ ๋ฐ”๊ฟ”์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ๋Š” ์—†๋‹ค. +3. **๋ถ€๋ชจ `lib_py`๋Š” ์ ˆ๋Œ€ ์žฌ๋ฐ”์ธ๋”ฉํ•˜์ง€ ์•Š๊ณ , `sys.path`๋„ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค.** ์œ ์ผํ•œ ์˜ˆ์™ธ๋Š” `lib_py`๊ฐ€ ์–ด๋””์—๋„ ์—†๋Š” standalone(C1)์ด๋ฉฐ, ์ด๋•Œ๋งŒ ์ž๊ธฐ ํŠธ๋ฆฌ๋ฅผ `append` ํ•œ๋‹ค(๊ฐ€๋ฆด ๋Œ€์ƒ์ด ์—†์œผ๋ฏ€๋กœ ์•ˆ์ „). +4. ๊ฒฐ๊ณผ์ ์œผ๋กœ pytest์—์„œ๋Š” ์œ„์ž„ ํŒจํ‚ค์ง€๋งŒ "์ตœ์ดˆ shim์˜ ์ƒŒ๋“œ๋ฐ•์Šค ์‚ฌ๋ณธ(๋˜๋Š” ๋จผ์ € ์ง์ ‘ ์ž„ํฌํŠธ๋œ ์‹ค์ œ ํŠธ๋ฆฌ)"์— ๋ฐ”์ธ๋”ฉ๋˜๊ณ  `lib_py.agents`/`layout`/`state` ๋“ฑ์€ ํ•ญ์ƒ ์‹ค์ œ ํŠธ๋ฆฌ๋‹ค. ์ƒŒ๋“œ๋ฐ•์Šค ์‚ฌ๋ณธ์€ `copytree`๋œ ๋™์ผ ๋‚ด์šฉ์ด๋ฏ€๋กœ ๊ฒ€์ฆ ๋Œ€์ƒ ์ฝ”๋“œ๋Š” ๊ฐ™๋‹ค(PV-1์ด ์ด๋ฅผ ๋‹จ์ •). +5. `mqtt_common.py`์˜ PEP 562 `__getattr__("LOGS_DIR")`์€ ์‹ค์ œ ๋ชจ๋“ˆ์— ๋‚จ์•„ ๋ณ„์นญ์„ ํ†ตํ•ด ๋…ธ์ถœ. ์‹คํ–‰ ๋น„ํŠธ ์œ ์ง€, `--help` ์ถœ๋ ฅ ๋™์ผ. +6. `_load_dotenv`(D6 ์ดํ›„ `ensure_env_loaded`)์˜ ๋ฃจํŠธ ํƒ์ƒ‰์€ ์‹ค๋ชจ๋“ˆ `__file__` ๊ธฐ์ค€์ด๋ฏ€๋กœ ๋ฐ”์ธ๋”ฉ๋œ ํŠธ๋ฆฌ์˜ ๋ฃจํŠธ(์ €์žฅ์†Œ ๋ฃจํŠธ ๋˜๋Š” freeze ๋ฃจํŠธ)๊ฐ€ ๋œ๋‹ค โ€” ์ด๋™ ์ „๊ณผ ๋™์ผ. + +### D2-1. ์‹คํ–‰ ์ปจํ…์ŠคํŠธ๋ณ„ "์–ด๋А ํŠธ๋ฆฌ๊ฐ€ ์ด๊ฒจ์•ผ ํ•˜๋Š”๊ฐ€" (Rev.2) + +| # | ์ปจํ…์ŠคํŠธ | `lib_py.delegation` ํ•ด์„ ์ฃผ์ฒด | ์ด๊ฒจ์•ผ ํ•˜๋Š” ํŠธ๋ฆฌ | Rev.2 ๊ฒฐ๊ณผ | ํ…Œ์ŠคํŠธ | +|---|---|---|---|---|---| +| K1 | ๋ฃจํ”„ ํ”„๋กœ์„ธ์Šค(`run_loop.sh`๊ฐ€ freeze `lib.sh` source โ†’ `PYTHONPATH`=freeze) ๋ฐ ๊ทธ ์ž์‹(๋ž˜ํผ, `-m lib_py.delegation.cli`) | `PYTHONPATH` | freeze | freeze | K1: `PYTHONPATH=` + `-m lib_py.delegation.cli --help` | +| K2 | ์—์ด์ „ํŠธ ์„ธ์…˜(`PYTHONPATH`=live)์ด ์œ„์ž„ ๋ฉ”์‹œ์ง€์˜ **freeze ๊ฒฝ๋กœ ์Šคํฌ๋ฆฝํŠธ**๋ฅผ ์‹คํ–‰ | shim ์˜† ํŠธ๋ฆฌ | freeze (B-13) | freeze, `lib_py` ๋ถ€๋ชจ๋Š” live(๋ฌดํ•ด: D1(b)) | C2_pe | +| K3 | ๋ผ์ด๋ธŒ ํ—ฌํผ(create/stop/resume/reconcile; live `lib.sh` source) | `PYTHONPATH`=live ๋˜๋Š” shim ์˜† ํŠธ๋ฆฌ(๊ฐ™์Œ) | live | live | K3: ์‹ค์ œ ํŠธ๋ฆฌ์—์„œ shim ์‹คํ–‰ | +| K4 | standalone(`PYTHONPATH` ์—†์Œ; BOOTSTRAP.md์˜ ์ˆ˜๋™ ๋ช…๋ น, ์™ธ๋ถ€ ์…ธ) | shim ํด๋ฐฑ append | ์ž๊ธฐ ํŠธ๋ฆฌ | ์ž๊ธฐ ํŠธ๋ฆฌ | C1, C1_pe | +| K5 | pytest ์ธํ”„๋กœ์„ธ์Šค(conftest๊ฐ€ ์‹ค์ œ ํŠธ๋ฆฌ ์‚ฝ์ž…, ์ƒŒ๋“œ๋ฐ•์Šค shim ์ž„ํฌํŠธ) | ์ตœ์ดˆ shim ๋˜๋Š” ์ตœ์ดˆ ์ง์ ‘ ์ž„ํฌํŠธ | ์œ„์ž„: ์ตœ์ดˆ ๋ฐ”์ธ๋”ฉ(์‚ฌ๋ณธ=๋™์ผ ๋‚ด์šฉ) / **๋‚˜๋จธ์ง€ `lib_py.*`: ์‹ค์ œ ํŠธ๋ฆฌ** | A, A_pe, B, B2, D, E | PV-1~PV-3 | +| K6 | pytest ์„œ๋ธŒํ”„๋กœ์„ธ์Šค(e2e `:414`, freeze ์‹œ๋ฎฌ๋ ˆ์ด์…˜) โ€” ์ƒˆ ์ธํ„ฐํ”„๋ฆฌํ„ฐ, `PYTHONPATH`=live ์ƒ์† ๊ฐ€๋Šฅ | shim ์˜† ํŠธ๋ฆฌ | ์ƒŒ๋“œ๋ฐ•์Šค | ์ƒŒ๋“œ๋ฐ•์Šค | K6: ์„œ๋ธŒํ”„๋กœ์„ธ์Šค JSON ์ถœ๋ ฅ ๋‹จ์ • | + +### D2-2. ๊ฐ€๋“œ โ€” ์ •์ฒด์„ฑ๋งŒ์œผ๋กœ๋Š” ๋ถˆ์ถฉ๋ถ„, ์ถœ์ฒ˜(provenance)๋ฅผ ๋ณ‘๊ธฐ (Rev.2) + +- **PV-1 (์ธํ”„๋กœ์„ธ์Šค, ๊ฐ shim ์ž„ํฌํŠธ ์งํ›„)**: `mqtt_common is lib_py.delegation.mqtt_common` **๊ทธ๋ฆฌ๊ณ ** `Path(lib_py.delegation.__path__[0]).resolve()`๊ฐ€ `{์‹ค์ œ skills ๋””๋ ‰ํ„ฐ๋ฆฌ, pytest basetemp ํ•˜์œ„}` ์ค‘ ํ•˜๋‚˜์ด๋ฉฐ, 4๊ฐœ ๋ณ„์นญ(`mqtt_common/registry/publish_event/job_subscriber`)์˜ `__file__`์ด **๋ชจ๋‘ ๊ฐ™์€** `__path__[0]` ์•„๋ž˜. +- **PV-2 (์„ธ์…˜ ์ข…๋ฃŒ ์‹œ, conftest์˜ session-scope autouse finalizer)**: `lib_py.__file__`, ๊ทธ๋ฆฌ๊ณ  `sys.modules`์— ์žˆ๋Š” ๋ชจ๋“  `lib_py.` ์ค‘ `x != "delegation"`์˜ `__file__`์ด ์‹ค์ œ skills ๋””๋ ‰ํ„ฐ๋ฆฌ ์•„๋ž˜. ์œ„๋ฐ˜ ์‹œ ์–ด๋А ๋ชจ๋“ˆ์ด ์–ด๋А tmp ๊ฒฝ๋กœ์—์„œ ์™”๋Š”์ง€ ์ถœ๋ ฅ. +- **PV-3 (์„ธ์…˜ ์ข…๋ฃŒ ์‹œ ์œ„์ƒ)**: `sys.path` ํ•ญ๋ชฉ ์ค‘ basetemp ํ•˜์œ„์ธ ๊ฒƒ ๊ฐ€์šด๋ฐ `/lib_py`๊ฐ€ ๋””๋ ‰ํ„ฐ๋ฆฌ์ธ ํ•ญ๋ชฉ 0๊ฐœ. (scripts ๋””๋ ‰ํ„ฐ๋ฆฌ ์‚ฝ์ž…์€ `lib_py`๋ฅผ ๋‹ด์ง€ ์•Š์œผ๋ฏ€๋กœ ํ—ˆ์šฉ โ€” ํ˜„ํ–‰ `test_tier1_unit.py:23`๊ณผ ํ˜ธํ™˜.) +- **PV-4 (์„œ๋ธŒํ”„๋กœ์„ธ์Šค ๋งคํŠธ๋ฆญ์Šค)**: ยง1-8์˜ ์‹œ๋‚˜๋ฆฌ์˜ค A, A_pe, B, B2, C1, C1_pe, C2, C2_pe, D, E๋ฅผ ์‹ค์ œ ์ด๋™๋œ ํŠธ๋ฆฌ์˜ ์‚ฌ๋ณธ์œผ๋กœ ์žฌ์‹คํ–‰ํ•˜๊ณ  JSON์œผ๋กœ ํŠธ๋ฆฌ๋ช…์„ ๋ฐ›์•„ ๋‹จ์ •. K1ยทK3ยทK6๋„ ๊ฐ™์€ ํ•˜๋‹ˆ์Šค์— ํฌํ•จ. +- **PV-5**: 4๊ฐœ shim์˜ ๋ถ€ํŠธ์ŠคํŠธ๋žฉ ๋ธ”๋ก์ด `{mod}` ์น˜ํ™˜์„ ์ œ์™ธํ•˜๊ณ  ๋ฐ”์ดํŠธ ๋™์ผ. + +### D3. ์…ธ์— ๋‚จ๋Š” ๊ฒƒ๊ณผ ํŒŒ์ด์ฌ์œผ๋กœ ๊ฐ€๋Š” ๊ฒƒ + +- `_delegate_py_bin`, `_delegate_script`: **์…ธ ์œ ์ง€**. ์‹œ๊ทธ๋‹ˆ์ฒ˜ยท์ถœ๋ ฅ ๋ถˆ๋ณ€. +- `delegate_submit_job`, `delegate_publish_event`, `ensure_subscriber_daemon`(์‹ ๊ทœ ์ด๋ฆ„) + `start_watchdog`(๋ณ„์นญ): ๋ณธ์ฒด๋ฅผ `python -m lib_py.delegation.cli ...` ํ˜ธ์ถœ๋กœ ๊ต์ฒด. `PYTHONPATH="$SKILL_DIR:${PYTHONPATH:-}"`๋ฅผ ๋ช…์‹œ ์ „๋‹ฌ(`env_python` L1583๊ณผ ๋™์ผ). `-m` ๊ฒฝ๋กœ๋Š” shim์„ ๊ฑฐ์น˜์ง€ ์•Š์œผ๋ฏ€๋กœ D2-1์˜ K1/K3์ฒ˜๋Ÿผ `SKILL_DIR` ํŠธ๋ฆฌ๊ฐ€ ๊ฒฐ์ •ํ•œ๋‹ค. +- ๋กค๋ฐฑ ์Šค์œ„์น˜ `MAM_DELEGATION_PY`(๊ธฐ๋ณธ `1`): `0`์ด๋ฉด ๊ธฐ์กด ์ธ๋ผ์ธ ๋ณธ์ฒด(`_*_legacy`)๋กœ ๋ถ„๊ธฐ. ํŠน์„ฑํ™” ํ…Œ์ŠคํŠธ๋ฅผ ๋‘ ๊ฒฝ๋กœ์— ๋Œ€ํ•ด parametrize. legacy ๋ณธ์ฒด ์ œ๊ฑฐ๋Š” Phase 4. + +```bash +start_watchdog() { ensure_subscriber_daemon "$@"; } # ๊ธฐ์กด ํ•จ์ˆ˜ ๋ณ„์นญ ๋ณด์กด +ensure_subscriber_daemon() { + if [ "${MAM_DELEGATION_PY:-1}" = "0" ]; then _start_watchdog_legacy "$@"; return $?; fi + PYTHONPATH="$SKILL_DIR:${PYTHONPATH:-}" "$(_delegate_py_bin)" -m lib_py.delegation.cli \ + ensure-daemon --job "$1" --workdir "${2:-$PWD}" +} +delegate_publish_event() { + local job_id="$1" event="$2" detail="${3:-}" + [ -n "$job_id" ] || return 0 # ๋น„์น˜๋ช… ๊ณ„์•ฝ 1 + if [ "${MAM_DELEGATION_PY:-1}" = "0" ]; then _delegate_publish_event_legacy "$@"; return $?; fi + PYTHONPATH="$SKILL_DIR:${PYTHONPATH:-}" "$(_delegate_py_bin)" -m lib_py.delegation.cli \ + publish --job "$job_id" --event "$event" --detail "$detail" || true # ๋น„์น˜๋ช… ๊ณ„์•ฝ 2ยท3 +} +``` + +### D4. `cli.py`๋Š” ๋ผ์šฐํ„ฐ, ๋กœ์ง์€ ๊ธฐ์กด `main()` ์žฌ์‚ฌ์šฉ + +- `publish` โ†’ `publish_event.main(argv)` ๊ทธ๋Œ€๋กœ ํ˜ธ์ถœ(exit 0/1/2, stdout ๋ฌด์ถœ๋ ฅ, ์˜ต์…˜ ์ „๋ถ€ ํ†ต๊ณผ). +- `submit` โ†’ `registry.main(["--registry-dir", ..., "register", ...])`. stdout์— JID ํ•œ ์ค„, ๋กœ๊ทธ๋Š” stderr. +- `ensure-daemon` โ†’ `daemon.ensure_subscriber_daemon(job_id, workdir)`; stdout PID, exit 0 / ๋ถ€์žฌ ์‹œ stderr ๋™์ผ ๋ฌธ๊ตฌ + exit 1. +- ๋ž˜ํผ `multi-agent-mux-delegate-job`(bash)์€ **Phase 1์—์„œ ์ˆ˜์ •ํ•˜์ง€ ์•Š๋Š”๋‹ค**(G-10 ๋ณดํ˜ธ). + +### D5. `daemon.ensure_subscriber_daemon(job_id, workdir) -> int` ์˜๋ฏธ ๋ณด์กด + +| ํ•ญ๋ชฉ | ๋ ˆ๊ฑฐ์‹œ(`lib.sh:1912-1941`) | ํŒŒ์ด์ฌ ์ด์‹ | +|---|---|---| +| ๋ชจ๋‹ˆํ„ฐ ์Šคํฌ๋ฆฝํŠธ ๊ฒฝ๋กœ | `/.agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh` | ๋™์ผ | +| ๋ถ€์žฌ ์‹œ | stderr `ERROR: monitor script not found: `, rc 1 | ๋™์ผ ๋ฌธ๊ตฌยทrc | +| ์ค‘๋ณต ๊ฒ€์‚ฌ | `pgrep -f "bash --subscribe"` | `subprocess.run(["pgrep","-f",...])` ๋™์ผ ํŒจํ„ด | +| ๊ธฐ์กด PID ์ถœ๋ ฅ | `echo "$pid"` (๋‹ค์ค‘ ๋งค์น˜๋ฉด ์—ฌ๋Ÿฌ ์ค„) | ๋™์ผํ•˜๊ฒŒ ์ „๋Ÿ‰ ์ถœ๋ ฅ(์ •๋ฆฌ๋Š” Phase 4 ํ›„๋ณด) | +| ์Šคํฐ | `nohup bash --subscribe --idle-timeout 0 > /.mam/multi-agent-mux-monitor.log 2>&1 &`, cwd=workdir | `Popen([...], cwd=workdir, stdin=DEVNULL, stdout=log(append), stderr=STDOUT, start_new_session=True, preexec_fn=SIGHUP ๋ฌด์‹œ)` | +| ๋ฐ˜ํ™˜ | ์Šคํฐ๋œ PID | ๋™์ผ | +| ๋ฐ๋ชฌ ์ž์ฒด ์ค‘๋ณต ๋ฐฉ์ง€ | `reconcile.sh:86-95` `.mam/monitor.lock` flock | ๋ณ€๊ฒฝ ์—†์Œ | + +### D6. ์ž„ํฌํŠธ ์‹œ์  ๋ถ€์ž‘์šฉ ์ œ๊ฑฐ โ€” `_load_dotenv()`๋ฅผ ์ง„์ž…์  ์‹œ์ ์œผ๋กœ + +`mqtt_common.py:112`์˜ ๋ชจ๋“ˆ ๋ ˆ๋ฒจ `_load_dotenv()` ํ˜ธ์ถœ์„ `ensure_env_loaded()`(๋ฉฑ๋“ฑ, 1ํšŒ)๋กœ ๋ฐ”๊พธ๊ณ  **๋ชจ๋“  ํ”„๋กœ์„ธ์Šค ์ง„์ž…์ ** ์ฒซ ์ค„์—์„œ ํ˜ธ์ถœ: `publish_event.main`, `job_subscriber.main`, `registry.main`, `cli.main`, `bench.main`, `reconcile.sh` ์ž„๋ฒ ๋””๋“œ ๊ตฌ๋…์ž(`import mqtt_common` ์งํ›„ 1์ค„). ์ด์˜์ œ๊ธฐ ยง6์ด ๋…๋ฆฝ ๊ฒ€์ฆํ•œ ๋Œ€๋กœ `mqtt_common.py`์˜ env ์ฐธ์กฐ๋Š” ์ „๋ถ€ ํ•จ์ˆ˜ ๋‚ด๋ถ€์ด๊ณ  ๋ชจ๋“ˆ ์ƒ์ˆ˜๋Š” env ๋ฌด๊ด€ํ•˜๋ฏ€๋กœ ๊ฐ’์ด ๋‹ฌ๋ผ์ง€๋Š” ์ƒ์ˆ˜๋Š” ์—†๋‹ค. `conftest.mam_sandbox`์— `monkeypatch.setenv("MAM_ENV_FILE", str(tmp_path / ".mam.env.absent"))` ์ถ”๊ฐ€(`_load_dotenv:48-52`๋Š” ๋ช…์‹œ ํŒŒ์ผ ๋ถ€์žฌ ์‹œ ๋ฌด๋กœ๋”ฉ). + +### D7. ๋ฐœํ–‰ ๊ฒฝ๋กœ ๋ถ„๋ฆฌ โ€” ์ „์†ก(transport)๋งŒ ๊ต์ฒด ๊ฐ€๋Šฅํ•˜๊ฒŒ + +`publish_event.main()`(`:133-235`)์„ ๋™์ž‘ ๋ถˆ๋ณ€์œผ๋กœ ์„ธ ์กฐ๊ฐ์œผ๋กœ ๋‚˜๋ˆˆ๋‹ค. + +```python +def prepare_event(job_id, event, detail, data, registry_dir) -> PreparedEvent # load_job, broker cfg, topic, next_seq(์†Œ๋น„), build_payload, retain ๊ฒฐ์ • +def record_outcome(prepared, registry_dir, publish_ok, publish_error) -> None # append_event(audit) + registry.append_event + EVENT_TO_STATUS ๋™๊ธฐํ™” +def main(argv=None) -> int: # parse โ†’ ensure_env_loaded โ†’ prepare_event โ†’ with_retry(_publish_once) โ†’ record_outcome โ†’ 0/2 +``` + +- G-1/G-2/G-4์˜ ์˜๋ฏธ๊ฐ€ `record_outcome` ํ•œ ๊ณณ์— ๋‚จ๋Š”๋‹ค. +- `publisher.OneShotPublisher.publish(prepared)` = ๊ธฐ์กด `_publish_once`. +- `publisher.PersistentPublisher`: ์ปจํ…์ŠคํŠธ ๋งค๋‹ˆ์ €. 1ํšŒ ์—ฐ๊ฒฐ + `loop_start()` + `reconnect_delay_set(1,16)`; `publish(prepared)`๋Š” QoS1 ๋ฐœํ–‰ + `wait_for_publish(PUBLISH_ACK_TIMEOUT)`; ๋ฏธํ™•์ธ ์‹œ ์˜ˆ์™ธ๋ฅผ ๋˜์ ธ ํ˜ธ์ถœ์ž์˜ `with_retry`๊ฐ€ ์žฌ์‹œ๋„. ๋ธŒ๋กœ์ปค ์„ค์ •์ด ๋‹ค๋ฅธ job ํ˜ผ์ž… ์‹œ ์˜ˆ์™ธ. +- `service.DelegationService(registry_dir, persistent=False)`: `submit_job()`, `publish_event()`, `ensure_subscriber_daemon()`. CLI/์…ธ์€ ํ•ญ์ƒ `persistent=False`. Phase 2a ์—”์ง„์ด `persistent=True`์˜ ์ฒซ ์†Œ๋น„์ž. +- ํŒจ๋ฆฌํ‹ฐ ํ…Œ์ŠคํŠธ: ๋™์ผ ์ž…๋ ฅยท๊ฐ€์งœ ์ „์†ก์—์„œ OneShot๊ณผ Persistent์˜ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ(`last_seq`, `status`)ยท๊ฐ์‚ฌ ๋ ˆ์ฝ”๋“œ(`published`, `seq`, `retain`, `payload.data.hmac_sig`)๊ฐ€ ํ•„๋“œ ๋‹จ์œ„๋กœ ๋™์ผ. + +### D8. ๋ฒค์น˜๋งˆํฌ โ€” ๊ฒฐ์ •์  ์ง€ํ‘œ์™€ ์ •๋ณด์„ฑ ์ง€ํ‘œ ๋ถ„๋ฆฌ + +`python -m lib_py.delegation.bench --n 50 --transport {fake|broker} [--connect-latency-ms 50] --out ` + +| ์ง€ํ‘œ | fake ์ „์†ก(CI์—์„œ ๋‹จ์ •) | broker ์ „์†ก(์ˆ˜๋™, ์ •๋ณด์„ฑ) | +|---|---|---| +| ์—ฐ๊ฒฐ ์ˆ˜๋ฆฝ ํšŸ์ˆ˜ / NํšŒ ๋ฐœํ–‰ | OneShot = N, Persistent = 1 (+์žฌ์—ฐ๊ฒฐ ์ˆ˜) โ€” ํ…Œ์ŠคํŠธ๋กœ ๊ณ ์ • | ๋™์ผ ์นด์šดํŠธ ์‹ค์ธก | +| ๋ฐœํ–‰๋‹น ์ง€์—ฐ p50/p95/max | ํ•ฉ์„ฑ ์ง€์—ฐ ๊ธฐ์ค€ ์‚ฐ์ˆ  ํ™•์ธ | ์‹ค์ธก (CONNACK ํƒ€์ž„์•„์›ƒ ๊ฑด์ˆ˜ ๋ณ„๋„) | +| ํ”„๋กœ์„ธ์Šค ํฌํฌ ์ˆ˜(์…ธ ๊ฒฝ๋กœ) | `delegate_publish_event` 1ํšŒ๋‹น ๋ ˆ๊ฑฐ์‹œ 3 fork โ†’ ์‹ ๊ทœ 2 fork | ๋ถ„์„๊ฐ’ | + +๊ฒฐ๊ณผ๋Š” `docs/contracts/issue-5/phase1-benchmark.md`์— ํ™˜๊ฒฝ๊ณผ ํ•จ๊ป˜ ๊ธฐ๋ก. ์‹ค๋ธŒ๋กœ์ปค ์ˆ˜์น˜๋Š” ๊ฒŒ์ดํŠธ ์กฐ๊ฑด์ด ์•„๋‹ˆ๋‹ค. + +### D9. ๋น„๋ฒ”์œ„ ๋ฐ ํ˜ธํ™˜์„ฑ ๊ณ„์•ฝ(์žฌํ™•์ธ) + +- ์ €์žฅ์†Œ ํ†ตํ•ฉ ๊ธˆ์ง€: `lib_py/delegation`์€ `lib_py.state`/`lib_py.atomic_yaml`์„ ์ž„ํฌํŠธํ•˜์ง€ ์•Š๋Š”๋‹ค โ€” D1(b)๊ฐ€ ์ด๋ฅผ ํฌํ•จํ•˜๋Š” ๋” ๋„“์€ ๊ทœ์น™. +- ์Šคํ‚ค๋งˆ ๋ถˆ๋ณ€: `SCHEMA_VERSION = 1`, ๋ ˆ์ฝ”๋“œ ํ•„๋“œ, ํ† ํ”ฝ, ๊ฐ์‚ฌ ๋กœ๊ทธ ๋ ˆ์ด์•„์›ƒ, `.events.log` ํ˜•์‹ ์ „๋ถ€ ๋ถˆ๋ณ€ โ†’ ยง3 ๋ผ์ด๋ธŒ ํ—ฌํผ ๋ฒ„์ „ ๊ฒŒ์ดํŠธ์ƒ ABI ํŒŒ๊ดด ์—†์Œ. K2์—์„œ ๋ถ€๋ชจ `lib_py`(live)์™€ ์œ„์ž„ ํŒจํ‚ค์ง€(freeze)๊ฐ€ ๋‹ค๋ฅธ ํŠธ๋ฆฌ์ธ ๊ฒƒ์€ D1(b) ๋•๋ถ„์— ๊ด€์ธก ๊ฐ€๋Šฅํ•œ ์ฐจ์ด๋ฅผ ๋งŒ๋“ค์ง€ ์•Š๋Š”๋‹ค. +- `job_subscriber`๋Š” ์ด๋ฏธ ์‹คํ–‰๋‹น ์žฅ์ˆ˜ ์—ฐ๊ฒฐ์ด๋ฏ€๋กœ ์ „์†ก ๋ณ€๊ฒฝ ์—†์Œ. +- ์ž๋™์™„๋ฃŒ ๊ด€๋ จ ์ฝ”๋“œ ์ผ์ฒด ์ถ”๊ฐ€ ๊ธˆ์ง€. + +--- + +## 3. ์ž‘์—… ๋ถ„ํ•ด (WBS) โ€” ์ˆœ์„œ ๊ณ ์ •, ์ปค๋ฐ‹ 5๊ฐœ + +### P1-00 ํŠน์„ฑํ™” ํ…Œ์ŠคํŠธ ์„ ํ–‰ (์ปค๋ฐ‹ 1: `test(issue-5): phase 1 delegation characterization suite`) + +- **ํŒŒ์ผ**: `tests/test_issue5_phase1_delegation.py`(์‹ ๊ทœ), `tests/conftest.py`(`MAM_ENV_FILE` 1์ค„ + PV-2/PV-3 session finalizer), `tests/fixtures/issue_5/phase1/`(๊ฐ€์งœ `reconcile.sh`, ๊ฐ€์งœ `python` ์Šคํ…, `shim_contexts.py` ํ•˜๋‹ˆ์Šค โ€” ๋ถ€๋ก B). +- **์…ธ ํ—ฌํผ golden ํ…Œ์ŠคํŠธ** (์ƒŒ๋“œ๋ฐ•์Šค์—์„œ `source lib.sh` ํ›„ ์‹คํ–‰, `_delegate_py_bin`์€ `test_c1_tui_readiness.py:513` ๋ฐฉ์‹์œผ๋กœ ์Šคํ…): + - C-01 `delegate_publish_event "" started` โ†’ rc 0, python ๋ฏธ์‹คํ–‰. + - C-02 ์Šคํฌ๋ฆฝํŠธ ๋ถ€์žฌ โ†’ rc 0. + - C-03 python ์Šคํ… exit 2 โ†’ rc 0. + - C-04 ์ •์ƒ ๊ฒฝ๋กœ argv์— `--job --event --detail ` ์ •ํ™•ํžˆ ํฌํ•จ. + - C-05 `delegate_submit_job p a s` โ†’ stdout์ด ์Šคํ…์˜ JID ํ•œ ์ค„๊ณผ ์ •ํ™•ํžˆ ๋™์ผ. + - C-06 `start_watchdog` ์Šคํฌ๋ฆฝํŠธ ๋ถ€์žฌ โ†’ stderr ์ •ํ™• ๋ฌธ๊ตฌ, rc 1. + - C-07 ์‹ ๊ทœ ์Šคํฐ โ†’ stdout ์ˆซ์ž PID, ์ƒ์กด, cwd=workdir, ๋กœ๊ทธ ํŒŒ์ผ ์ƒ์„ฑ. + - C-08 ๋‘ ๋ฒˆ์งธ ํ˜ธ์ถœ โ†’ ๋™์ผ PID, ํ”„๋กœ์„ธ์Šค ์ˆ˜ ๋ถˆ๋ณ€. + - C-09 `start_watchdog` โ‰ก `ensure_subscriber_daemon` โ€” P1-02 ์ดํ›„ ํ™œ์„ฑ. + - `MAM_DELEGATION_PY` โˆˆ {`0`,`1`} parametrize. +- **CLI ๊ณ„์•ฝ ์Šค๋ƒ…์ƒท**: argparse ํŒŒ์„œ์—์„œ ์˜ต์…˜๋ช…ยทchoicesยทdefault๋ฅผ ์ถ”์ถœํ•ด ๋‹จ์ •. +- **์ž๋™์™„๋ฃŒ ๋ถˆ๋ณ€ ๊ฐ€๋“œ**: `EVENT_TO_STATUS` ๋™๋“ฑ; ๋ž˜ํผ `:335-348` ๋ฆฌํ„ฐ๋Ÿด; `reconcile.sh` `_MUTATION` ํ•ต์‹ฌ 4์ค„ ๋ฆฌํ„ฐ๋Ÿด. +- **PV-2/PV-3 finalizer๋Š” ์ด ์ปค๋ฐ‹์—์„œ ์ด๋ฏธ ํ™œ์„ฑ** โ€” ํ˜„ํ–‰ ํŠธ๋ฆฌ์—์„œ ํ†ต๊ณผํ•ด์•ผ ํ•œ๋‹ค(shim ๋„์ž… ์ „ ๊ธฐ์ค€์„ ). +- **์™„๋ฃŒ ๊ธฐ์ค€**: ์‹ ๊ทœ ํ…Œ์ŠคํŠธ ์ „๋ถ€ ํ†ต๊ณผ(๋ ˆ๊ฑฐ์‹œ ์ฝ”๋“œ ๊ทธ๋Œ€๋กœ), ์ „์ฒด ์Šค์œ„ํŠธ 510 + ์‹ ๊ทœ ํ†ต๊ณผ. + +### P1-01 ํŒจํ‚ค์ง€ ์ƒ์„ฑยท์ด๋™ยทshim (์ปค๋ฐ‹ 2: `refactor(issue-5): move delegation scripts into lib_py.delegation with package-bound shims`) + +- **ํŒŒ์ผ**: `git mv` 4์ข…, ๋‚ด๋ถ€ ์ž„ํฌํŠธ ๊ต์ •, `lib_py/delegation/__init__.py`, `scripts/*.py` 4์ข… shim(D2 ๋ธ”๋ก), `mqtt_common.ensure_env_loaded()`(D6) + 4๊ฐœ `main()` ๋ฐ `reconcile.sh` ์ž„๋ฒ ๋””๋“œ ๊ตฌ๋…์ž 1์ค„. +- **์ถ”๊ฐ€ ํ…Œ์ŠคํŠธ**(Rev.2 ํ™•์žฅ): PV-1(4 ๋ณ„์นญ), PV-4 ์„œ๋ธŒํ”„๋กœ์„ธ์Šค ๋งคํŠธ๋ฆญ์Šค 10 ์‹œ๋‚˜๋ฆฌ์˜ค + K1/K3/K6, PV-5 shim ๋™์ผ์„ฑ, "๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ž„ํฌํŠธ ํ›„ `os.environ` ํ‚ค ์ง‘ํ•ฉ ๋ถˆ๋ณ€", `ensure_env_loaded` ๋ฉฑ๋“ฑ, D1(a)(b) ์ž„ํฌํŠธ ๊ทœ์น™ grep, `reconcile.sh --subscribe --idle-timeout 1`์ด `test_comp_monitor_concurrency_lock`๊ณผ ๋™์ผ ๋™์ž‘. +- **์™„๋ฃŒ ๊ธฐ์ค€**: ์ „์ฒด ์Šค์œ„ํŠธ ํ†ต๊ณผ, `git diff --check`, `compileall`, 3๊ฐœ shim `--help` ์ถœ๋ ฅ์ด ์ด๋™ ์ „ ์บก์ฒ˜๋ณธ๊ณผ diff ์—†์Œ, **PV-2/PV-3๊ฐ€ ์ „์ฒด ์Šค์œ„ํŠธ ์‹คํ–‰ ํ›„์—๋„ ํ†ต๊ณผ**(์ด๊ฒƒ์ด ์ด์˜์ œ๊ธฐ๊ฐ€ ์š”๊ตฌํ•œ "์–ด๋А ํŠธ๋ฆฌ๋ฅผ ๊ฒ€์ฆํ–ˆ๋Š”์ง€" ์ฆ๋น™). + +### P1-02 ์„œ๋น„์ŠคยทCLIยท๋ฐ๋ชฌ + ์…ธ ํ—ฌํผ ๊ต์ฒด (์ปค๋ฐ‹ 3: `feat(issue-5): DelegationService, cli router, ensure_subscriber_daemon; lib.sh helpers delegate to python`) + +- **ํŒŒ์ผ**: `service.py`, `daemon.py`, `cli.py`, `__main__.py`; `lib.sh` L1876-1941 ๊ต์ฒด(D3). +- **์ถ”๊ฐ€ ํ…Œ์ŠคํŠธ**: daemon ๋‹จ์œ„(PID ์ƒ์กดยท์ค‘๋ณตยท๋กœ๊ทธ ๊ฒฝ๋กœยท๋ถ€๋ชจ ์ข…๋ฃŒ ํ›„ ์ƒ์กด), `cli publish` โ‰ก `publish_event.main`(G-1~G-4 cli ๊ฒฝ๋กœ parametrize), `cli submit` stdout, C-01~C-09 ์‹ ๊ทœ ๋ณธ์ฒด ์žฌํ†ต๊ณผ. +- **์™„๋ฃŒ ๊ธฐ์ค€**: ๋‘ ๊ฒฝ๋กœ ํŠน์„ฑํ™” ํ†ต๊ณผ, `bash -n` 6๊ฐœ ํŒŒ์ผ, ์ „์ฒด ์Šค์œ„ํŠธ ํ†ต๊ณผ. + +### P1-03 ์žฅ์ˆ˜ ์—ฐ๊ฒฐ ํ”„๋กœํ† ํƒ€์ž… (์ปค๋ฐ‹ 4: `feat(issue-5): persistent MQTT publisher prototype and benchmark harness`) + +- **ํŒŒ์ผ**: `publish_event.py` ํ•จ์ˆ˜ ๋ถ„๋ฆฌ(D7), `publisher.py`, `service.py` `persistent` ์˜ต์…˜, `bench.py`. +- **์ถ”๊ฐ€ ํ…Œ์ŠคํŠธ**: ๊ฐ€์งœ paho ํด๋ผ์ด์–ธํŠธ๋กœ (a) OneShot NํšŒ = N connect, Persistent = 1, (b) ํŒจ๋ฆฌํ‹ฐ, (c) PUBACK ๋ฏธํ™•์ธ โ†’ ์žฌ์‹œ๋„ โ†’ `record_outcome(published=False)`, (d) ๋ธŒ๋กœ์ปค ์„ค์ • ์ƒ์ด ํ˜ผ์ž… ์˜ˆ์™ธ, (e) `bench --transport fake` ์Šคํ‚ค๋งˆยท์นด์šดํŠธ. ๋„คํŠธ์›Œํฌ ์ ‘์ด‰ 0. +- **์™„๋ฃŒ ๊ธฐ์ค€**: ์œ„ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ, G-1~G-4 ๋™์ผ ํ†ต๊ณผ, ์ „์ฒด ์Šค์œ„ํŠธ ํ†ต๊ณผ. + +### P1-04 ๋ฒค์น˜๋งˆํฌ ์‹ค์ธก ๋ฐ ๋ฌธ์„œ (์ปค๋ฐ‹ 5: `docs(issue-5): phase 1 benchmark results, contract mapping, task checklist`) + +- ์‹คํ–‰: fake ๋ชจ๋“œ + broker ๋ชจ๋“œ 1ํšŒ(์ž„์‹œ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ). `docs/contracts/issue-5/phase1-benchmark.md`์— ๊ธฐ๋ก. +- ๋ฌธ์„œ ๊ฐฑ์‹ : `SKILL.md`(version `4.2.0`โ†’`4.3.0`, "๊ตฌํ˜„์€ `lib_py.delegation`, `scripts/*`๋Š” ํŒจํ‚ค์ง€ ๋ฐ”์ธ๋”ฉ shim" 1๋‹จ๋ฝ + D2-1 ํ‘œ ์š”์•ฝ), ์Šคํ‚ฌ `README.md`, `registry.md`/`job-protocol.md` ๊ฐ์ฃผ, `deploy/install.sh` `core_files`์— `.agents/skills/lib_py/delegation/__init__.py` ์ถ”๊ฐ€(๋ฌด๊ฒฐ์„ฑ ๋ชฉ๋ก ๊ฐฑ์‹  ๋ชฉ์ ; ๋ฐฐํฌ ์ž์ฒด๋Š” ํŠธ๋ฆฌ ํ†ต์งธ ๋ณต์‚ฌ๋ผ ๋ˆ„๋ฝ ์œ„ํ—˜ ์—†์Œ), `baseline.md` ยง6 ๋งคํ•‘ ํ–‰, `task.md` P1-01~03 ์ฒดํฌ + ์ฆ๊ฑฐ, `implementation_plan.md` Phase 1 ํ–‰ ์ƒํƒœ. +- ๋ผ์ด๋ธŒ ์Šค๋ชจํฌ(์ˆ˜๋™, ๋ณด๊ณ ์„œ ๊ธฐ๋ก): ์ž„์‹œ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ์—์„œ `registry.py register` โ†’ shim `publish_event.py --event started` โ†’ `-m lib_py.delegation.cli publish --event completed` โ†’ `.events.log` 2์ค„, `events.ndjson` `published` 2๊ฑด, `status.json`=completed. ์ด์–ด `MAM_DELEGATION_PY=0`์œผ๋กœ ๋ฐ˜๋ณต. **์ถ”๊ฐ€(Rev.2)**: ์—์ด์ „ํŠธ ์„ธ์…˜๊ณผ ๊ฐ™์€ ์กฐ๊ฑด(`PYTHONPATH`=live)์—์„œ **freeze ์‚ฌ๋ณธ ๊ฒฝ๋กœ**์˜ shim์„ ์‹คํ–‰ํ•˜๊ณ  `python -c`๋กœ `lib_py.delegation.__path__`๊ฐ€ freeze ์‚ฌ๋ณธ์ž„์„ ์ถœ๋ ฅํ•ด ์ฒจ๋ถ€(K2 ์‹ค์ฆ). + +--- + +## 4. ๊ณ„์•ฝ ๋ณด์กด ๋งคํŠธ๋ฆญ์Šค + +| ๊ณ„์•ฝ | ๊ตฌํ˜„ ์œ„์น˜(์ด๋™ ํ›„) | ๋ณด์žฅ ํ…Œ์ŠคํŠธ | +|---|---|---| +| ์Šคํฌ๋ฆฝํŠธ ๋ฆฌํ„ฐ๋Ÿด ๊ฒฝ๋กœยท์‹คํ–‰ ๋น„ํŠธยท`--help` | `scripts/*.py` shim | P1-01 `--help` diff, K6, e2e `:414` | +| `import mqtt_common` ๋“ฑ ์ตœ์ƒ์œ„ ์ด๋ฆ„ โ†’ ๋‹จ์ผ ํŠธ๋ฆฌ์˜ ๋™์ผ ๊ฐ์ฒด | shim ํŒจํ‚ค์ง€ ๋ฐ”์ธ๋”ฉ + ๋ณ„์นญ | **PV-1(์ •์ฒด์„ฑ+์ถœ์ฒ˜)**, PV-4 | +| ์œ„์ž„ ์™ธ `lib_py.*`๋Š” ํ•ญ์ƒ ์‹ค์ œ ํŠธ๋ฆฌ(pytest) / ๋ถ€๋ชจ ๋ฌด์žฌ๋ฐ”์ธ๋”ฉ | shim ๊ทœ์น™ 3 | **PV-2, PV-3** | +| `publish_event` exit 0/1/2, stdout ๋ฌด์ถœ๋ ฅ | `publish_event.main` ๋ถˆ๋ณ€ | G-1~G-4 + cli parametrize | +| seq ๋‹จ์กฐยท์‹คํŒจ ์‹œ ์†Œ๋น„ | `prepare_event`โ†’`next_seq` | G-4, `test_comp_monitor_monotonic_seq_updates` | +| HMAC `data.hmac_sig` | `build_payload` ๋‹จ์ผ ๊ฒฝ๋กœ | `test_mqtt_verify_hmac_*`, ํŒจ๋ฆฌํ‹ฐ | +| ํ„ฐ๋ฏธ๋„ ์ด๋ฒคํŠธ retain | `prepare_event` | ํŒจ๋ฆฌํ‹ฐ(`retain`) | +| `job_subscriber` exit 0/1/2/3, ๋””์Šคํฌ ํด๋ฐฑ 3s | ์ด๋™๋งŒ | G-5~G-9 | +| ์…ธ ํ—ฌํผ ๋น„์น˜๋ช… | ์…ธ ๊ฐ€๋“œ + `\|\| true` | C-01~C-04 (๋‘ ๊ฒฝ๋กœ) | +| `delegate_submit_job` stdout=JID 1์ค„ | `cli submit` + stderr ๋กœ๊น… | C-05 | +| ๋ฐ๋ชฌ PID/์ˆ˜๋ช…/์ค‘๋ณต/๋กœ๊ทธ ๊ฒฝ๋กœ | `daemon.py` | C-06~C-09, daemon ๋‹จ์œ„ | +| `start_watchdog` ์ด๋ฆ„ | ๋ณ„์นญ | C-09 | +| ๊ฐ์‚ฌ ๋กœ๊ทธ ๋ ˆ์ด์•„์›ƒยท`DELEGATE_JOB_LOGS_DIR` | `mqtt_common` ๋ถˆ๋ณ€ | G-2, G-3 | +| env ๋กœ๋”ฉ ์ˆœ์„œ(OS env ์šฐ์„ ), ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ž„ํฌํŠธ ๋ฌด๋ถ€์ž‘์šฉ | `ensure_env_loaded`(์ง„์ž…์ ) | env ํ…Œ์ŠคํŠธ 2์ข… | +| ์ž๋™์™„๋ฃŒ 3ํ‘œ๋ฉด ๋ถˆ๋ณ€ | ๋ณ€๊ฒฝ ์—†์Œ | ๋ถˆ๋ณ€ ๊ฐ€๋“œ 3์ข… | +| ๋ž˜ํผ ํ…์ŠคํŠธ ๋ถˆ๋ณ€ | ๋ณ€๊ฒฝ ์—†์Œ | G-10 + ํ™•์žฅ | +| ์œ„์ž„ ํŒจํ‚ค์ง€์˜ ํ˜•์ œ `lib_py` ๋ชจ๋“ˆ ๋ฌด์ž„ํฌํŠธ | D1(b) | grep ํ…Œ์ŠคํŠธ | +| freeze(B-13) โ€” K1/K2 ๋ชจ๋‘ freeze ํŠธ๋ฆฌ | `PYTHONPATH`(K1) / shim ๋ฐ”์ธ๋”ฉ(K2) | K1, C2, C2_pe, P1-04 ์‹ค์ฆ | + +--- + +## 5. ๊ฒŒ์ดํŠธ + +| # | ์กฐ๊ฑด | ์ธก์ • ๋ฐฉ๋ฒ• | +|---|---|---| +| G1 | ๊ธฐ์กด 510๊ฐœ + ์‹ ๊ทœ(์˜ˆ์ƒ 40ยฑ5) ์ „๋ถ€ ํ†ต๊ณผ, skipped 0 | `.venv/bin/python -m pytest tests/ -q` (โ‰ˆ15๋ถ„, ๋ฐฑ๊ทธ๋ผ์šด๋“œ) | +| G2 | ์…ธ ๋ฌธ๋ฒ•ยท๊ณต๋ฐฑ ํด๋ฆฐ | `bash -n` 6๊ฐœ ํŒŒ์ผ, `git diff --check` | +| G3 | Python 3.9 ์ปดํŒŒ์ผ | `compileall -q .agents/skills/lib_py/delegation` | +| **G4** | **์ •์ฒด์„ฑ+์ถœ์ฒ˜(PV-1), ์œ„์ž„ ์™ธ `lib_py` ์‹ค์ œ ํŠธ๋ฆฌยท`sys.path` ์œ„์ƒ(PV-2/PV-3, ์ „์ฒด ์Šค์œ„ํŠธ ์ข…๋ฃŒ ์‹œ์ ), ์ปจํ…์ŠคํŠธ ๋งคํŠธ๋ฆญ์Šค(PV-4 10+3), shim ๋™์ผ์„ฑ(PV-5), env ๋ฌด์˜ค์—ผ** | P1-01 ํ…Œ์ŠคํŠธ + conftest finalizer | +| G5 | ๋ฐ๋ชฌ/PID ๊ณ„์•ฝ | C-06~C-09 ๋‘ ๊ฒฝ๋กœ | +| G6 | ์ž๋™์™„๋ฃŒ 3ํ‘œ๋ฉด ๋ถˆ๋ณ€ | ๋ถˆ๋ณ€ ๊ฐ€๋“œ + `git diff --stat`(๋ž˜ํผ ๋ฌด๋ณ€๊ฒฝ, `reconcile.sh`๋Š” env 1์ค„๋งŒ) | +| G7 | ๋ฒค์น˜ ๊ฒฐ์ •์  ์ง€ํ‘œ + ์‹ค์ธก ๋ฌธ์„œ | fake ์ „์†ก ํ…Œ์ŠคํŠธ, `phase1-benchmark.md` | +| G8 | ๋กค๋ฐฑ ๋“œ๋ฆด | `MAM_DELEGATION_PY=0` + ๋ผ์ด๋ธŒ ์Šค๋ชจํฌ | +| G9 | ๋ผ์ด๋ธŒ ์Šค๋ชจํฌ(shimยทcliยท**K2 freeze ์‚ฌ๋ณธ**) ๊ฐ์‚ฌ ๋กœ๊ทธ ์ •ํ•ฉ | ยง3 P1-04 | + +Worker ์™„๋ฃŒ ๋ณด๊ณ ์„œ์—๋Š” G1 pytest ์š”์•ฝ ์›๋ฌธ, G4์˜ PV-2/PV-3 finalizer ํ†ต๊ณผ ๋กœ๊ทธ, G7 ํ‘œ, G9 ๋ฐœ์ทŒ(K2 `__path__` ์ถœ๋ ฅ ํฌํ•จ)๋ฅผ ๊ทธ๋Œ€๋กœ ๋ถ™์ธ๋‹ค. file:line ์ธ์šฉ์€ `grep -n` ์ถœ๋ ฅ์œผ๋กœ ๋’ท๋ฐ›์นจ. + +--- + +## 6. ๋กค๋ฐฑ ์ ˆ์ฐจ + +1. **์ฆ‰์‹œ**: `export MAM_DELEGATION_PY=0` โ†’ ์…ธ ํ—ฌํผ legacy ๋ถ„๊ธฐ. ํŒจํ‚ค์ง€ยทshim์€ ๊ทธ๋Œ€๋กœ ๋‘์–ด๋„ ๋ฌดํ•ด. +2. **์ปค๋ฐ‹ ๋‹จ์œ„**: 5โ†’4โ†’3 ์ˆœ `git revert`; ์ปค๋ฐ‹ 2๊นŒ์ง€ ๋˜๋Œ๋ฆฌ๋ฉด ์ด๋™ ์ „ ์ƒํƒœ. ์ปค๋ฐ‹ 1์€ ๋ ˆ๊ฑฐ์‹œ์—์„œ๋„ ํ†ต๊ณผ. +3. **์ง„ํ–‰ ์ค‘ ๋ฃจํ”„**: freeze ์Šค๋ƒ…์ƒท์€ ์‹œ์ž‘ ์‹œ์  ํŠธ๋ฆฌ์ด๋ฏ€๋กœ ๋กค๋ฐฑ์€ ๋‹ค์Œ ๋ฃจํ”„๋ถ€ํ„ฐ. ๋ผ์ด๋ธŒ ํ—ฌํผ๋Š” ์ฆ‰์‹œ legacy. ์Šคํ‚ค๋งˆ ๋ถˆ๋ณ€์ด๋ฏ€๋กœ ํ˜ผ์žฌ ํ˜ธํ™˜. + +--- + +## 7. ๋ฆฌ์Šคํฌ์™€ ์™„ํ™” + +| # | ๋ฆฌ์Šคํฌ | ์™„ํ™” | +|---|---|---| +| R1 | ๋ชจ๋“ˆ ์‚ฌ๋ณธ ๋ถ„๊ธฐ(ํ…Œ์ŠคํŠธ shim vs ํ”„๋กœ๋•์…˜ ํŒจํ‚ค์ง€) | D2 ํŒจํ‚ค์ง€ ๋ฐ”์ธ๋”ฉ + PV-1 ์ถœ์ฒ˜ ๋‹จ์ • | +| R2 | ์ž„ํฌํŠธ ์‹œ `.mam.env` ์ฃผ์ž… ์˜ค์—ผ | D6 + conftest `MAM_ENV_FILE` | +| **R3 (Rev.2 ์žฌ์ž‘์„ฑ)** | shim ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์ด pytest์—์„œ ์œ„์ž„ ์™ธ `lib_py`๋ฅผ tmp ์‚ฌ๋ณธ์œผ๋กœ ๊ฐ€๋กœ์ฑ„๊ฑฐ๋‚˜(insert), freeze์—์„œ live์— ๋ฐ€๋ฆฌ๊ฑฐ๋‚˜(append), ๋น„-์žŽ ๋ชจ๋“ˆ์—์„œ ๋‘ ํŠธ๋ฆฌ๊ฐ€ ์„ž์ž„(์žŽ ํŒŒ์ผ ์ ์žฌ) | **ํ•ด์†Œ**: ํŒจํ‚ค์ง€ ๋‹จ์œ„ bind-once, `sys.path` ๋ฌด๋ณ€๊ฒฝ, ๋ถ€๋ชจ ๋ฌด์žฌ๋ฐ”์ธ๋”ฉ. PV-2/PV-3/PV-4๊ฐ€ ํšŒ๊ท€๋ฅผ ๊ฒ€์ถœ | +| R4 | Python 3.10+ ๋ฌธ๋ฒ• | G3 | +| R5 | ๊ณต์šฉ ๋ธŒ๋กœ์ปค ๋ถˆ์•ˆ์ • | ํ…Œ์ŠคํŠธ๋Š” ๋น„๋ผ์šฐํŒ… ํฌํŠธ/๊ฐ€์งœ ์ „์†ก๋งŒ | +| R6 | `pgrep -f` ํŒจํ„ด | ๋ ˆ๊ฑฐ์‹œ ๊ทธ๋Œ€๋กœ, C-08 | +| R7 | `$( )` ์บก์ฒ˜ stdout ์˜ค์—ผ | stderr ๋กœ๊น…, C-05/C-07 | +| R8 | ๋ž˜ํผ ๋ฆฌํ„ฐ๋Ÿด(G-10) ํŒŒ์† | ๋ž˜ํผ ๋ฌด์ˆ˜์ • | +| **R9 (Rev.2 ์ˆ˜์ •)** | `lib.sh` ๋ฏธ์†Œ์‹ฑ/`PYTHONPATH` ๋ถ€์žฌ ํ™˜๊ฒฝ์—์„œ shimยท`-m` ์‹คํŒจ | shim: `lib_py` ๋ถ€์žฌ ์‹œ์—๋งŒ ์ž๊ธฐ ํŠธ๋ฆฌ `append`(K4). `-m` ๊ฒฝ๋กœ: ์…ธ ํ—ฌํผ๋Š” ํ•ญ์ƒ `lib.sh` ๋‚ด๋ถ€์ด๋ฏ€๋กœ `SKILL_DIR` ์กด์žฌ + ๋ช…์‹œ ์ „๋‹ฌ | +| R10 | `lib_py.agents.registry` ํ˜ผ๋™ | D1(a) grep | +| R11 | `start_new_session` vs `nohup` | SIGHUP ๋ฌด์‹œ, ์ƒ์กด ํ…Œ์ŠคํŠธ | +| **R12 (์‹ ๊ทœ)** | K2์—์„œ ๋ถ€๋ชจ `lib_py`(live)์™€ ์œ„์ž„ ํŒจํ‚ค์ง€(freeze)๊ฐ€ ๋‹ค๋ฅธ ํŠธ๋ฆฌ | D1(b): ์œ„์ž„ ํŒจํ‚ค์ง€๋Š” ํ˜•์ œ `lib_py` ๋ชจ๋“ˆ์„ ์ž„ํฌํŠธํ•˜์ง€ ์•Š์Œ(grep ๊ณ ์ •). ํ˜„์žฌ 0๊ฑด | +| **R13 (์‹ ๊ทœ)** | ๋‘ ํŠธ๋ฆฌ์˜ ์œ„์ž„ ๋ชจ๋“ˆ์„ ํ•œ ํ”„๋กœ์„ธ์Šค์—์„œ ๋™์‹œ์— ์จ์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ | ์กด์žฌํ•˜์ง€ ์•Š์Œ(K1~K6 ์ „์ˆ˜). ๋ฐœ์ƒ ์‹œ bind-once ๊ทœ์น™์ƒ ์ตœ์ดˆ ํŠธ๋ฆฌ ์œ ์ง€ โ€” ๋ฌธ์„œํ™” | +| **R14 (์‹ ๊ทœ)** | pytest์—์„œ ์œ„์ž„ ํŒจํ‚ค์ง€๊ฐ€ ์ƒŒ๋“œ๋ฐ•์Šค ์‚ฌ๋ณธ์— ๋ฐ”์ธ๋”ฉ๋˜์–ด "์‹ค์ œ ํŠธ๋ฆฌ ๋ฏธ๊ฒ€์ฆ" ์šฐ๋ ค | ์‚ฌ๋ณธ์€ `copytree` ๋™์ผ ๋‚ด์šฉ์ด๋ฉฐ PV-1์ด ์‚ฌ๋ณธ ์œ„์น˜๋ฅผ basetemp๋กœ ํ•œ์ •. K6 ์„œ๋ธŒํ”„๋กœ์„ธ์Šค๊ฐ€ ์‚ฌ๋ณธ ๊ฒฝ๋กœ ์‹คํ–‰์„ ๋ณ„๋„ ๊ฒ€์ฆ | + +--- + +## 8. ๋ช…์‹œ์  ๋น„๋ฒ”์œ„ + +- `lib.sh` L1943 ์ดํ›„ TUI ํ—ฌํผ์˜ ํŒŒ์ด์ฌํ™”. +- ๋ž˜ํผ, `run_loop.sh`, `reconcile.sh`(env 1์ค„ ์ œ์™ธ) ์ˆ˜์ •. +- `job_subscriber`/๋ชจ๋‹ˆํ„ฐ ์ „์†ก ๋ณ€๊ฒฝ, ์Šคํ‚ค๋งˆ v2, ํ† ํ”ฝ ๋ณ€๊ฒฝ. +- ์ž๋™์™„๋ฃŒ ๋กœ์ง ์ผ์ฒด(Phase 2a/2b). +- ์ €์žฅ์†Œ ํ†ตํ•ฉ, Windows, `pgrep` ๋‹ค์ค‘ PID ์ •๋ฆฌ(Phase 4 ํ›„๋ณด). +- ๊ธฐ์กด tier1/tier2 ํ…Œ์ŠคํŠธ์˜ ์ž„ํฌํŠธ ๋ฐฉ์‹ ๋ณ€๊ฒฝ(ํšŒ๊ท€ ์˜ค๋ผํด ๋ณด์กด). + +--- + +## 9. Worker ์ธ์ˆ˜์ธ๊ณ„ ์ฒดํฌ๋ฆฌ์ŠคํŠธ + +- [ ] P1-00 ์ปค๋ฐ‹ 1: ํŠน์„ฑํ™”ยท์Šค๋ƒ…์ƒทยท๋ถˆ๋ณ€ ๊ฐ€๋“œ + **PV-2/PV-3 finalizer**๊ฐ€ ๋ ˆ๊ฑฐ์‹œ ์ฝ”๋“œ์—์„œ ํ†ต๊ณผ +- [ ] P1-01 ์ปค๋ฐ‹ 2: `git mv` 4์ข…, D2 shim 4์ข…(PV-5 ๋™์ผ), `ensure_env_loaded`, PV-1/PV-4 ๋งคํŠธ๋ฆญ์Šค, ์ „์ฒด ์Šค์œ„ํŠธ ๋…น์ƒ‰ **+ finalizer ๋…น์ƒ‰** +- [ ] P1-02 ์ปค๋ฐ‹ 3: `service/daemon/cli`, `lib.sh` ๊ต์ฒด(legacy ๋ณด์กด, `MAM_DELEGATION_PY`), ๋‘ ๊ฒฝ๋กœ ํŠน์„ฑํ™” ๋…น์ƒ‰, `bash -n` +- [ ] P1-03 ์ปค๋ฐ‹ 4: `prepare_event/record_outcome`, `PersistentPublisher`, ํŒจ๋ฆฌํ‹ฐยท์นด์šดํŠธ, ๋„คํŠธ์›Œํฌ 0 +- [ ] P1-04 ์ปค๋ฐ‹ 5: ๋ฒค์น˜ ๋ฌธ์„œ, SKILL/README/registry.md/job-protocol.md/install.sh/baseline ยง6/task.md/implementation_plan.md, K2 ์‹ค์ฆ ์ฒจ๋ถ€ +- [ ] ์™„๋ฃŒ ๋ณด๊ณ ์„œ: G1 ์›๋ฌธ, G4 finalizer ๋กœ๊ทธ, G7 ํ‘œ, G9 ๋ฐœ์ทŒ, `grep -n` ์ธ์šฉ +- [ ] ์ปค๋ฐ‹ ์ ‘๋‘: `test(issue-5)`, `refactor(issue-5)`, `feat(issue-5)`, `feat(issue-5)`, `docs(issue-5)` + +--- + +## ๋ถ€๋ก A. ๊ณ„ํš ์ˆ˜๋ฆฝ ์‹œ ์‹คํ–‰ํ•œ ๊ฒ€์ฆ ๋ช…๋ น (Rev.1 + Rev.2) + +```bash +awk 'NR>=1800 && /^[a-zA-Z_][a-zA-Z0-9_]*\(\)/ {print NR": "$0}' .agents/skills/lib.sh +grep -rn 'delegate_publish_event\|delegate_submit_job\|start_watchdog\|_delegate_py_bin' --include='*.sh' --include='*.py' .agents tests +grep -l 'delegate_publish_event\|start_watchdog\|delegate_submit_job' tests/*.py # โ†’ (์—†์Œ) +grep -n 'lib_py' .agents/skills/multi-agent-mux-delegate-job/scripts/*.py # โ†’ (์—†์Œ) D1(b) ์ „์ œ +echo "$PYTHONPATH" # ์—์ด์ „ํŠธ ์„ธ์…˜ = ๋ผ์ด๋ธŒ ํŠธ๋ฆฌ +grep -n PYTHONPATH .agents/skills/multi-agent-mux-loop/scripts/run_loop.sh # 0๊ฑด +sed -n '24,25p;163,164p' .agents/skills/lib.sh # SKILL_DIR ๊ธฐ์ค€ PYTHONPATH export +ls /var/folders/.../mam-loop-freeze.PfAQNj/.agents/skills/lib_py # freeze์— lib_py ์กด์žฌ +grep -n 'os.environ' .agents/skills/multi-agent-mux-delegate-job/scripts/mqtt_common.py # ์ „๋ถ€ ํ•จ์ˆ˜ ๋‚ด๋ถ€ +.venv/bin/python -m pytest tests/ --collect-only -q | tail -1 # 510 +``` + +## ๋ถ€๋ก B. shim ์ปจํ…์ŠคํŠธ ์žฌํ˜„ ํ•˜๋‹ˆ์Šค (P1-01 PV-4์˜ ์›ํ˜•) + +์„ธ ๋ณ€ํ˜• ร— ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์ƒˆ ์ธํ„ฐํ”„๋ฆฌํ„ฐ(`env -u PYTHONPATH`)์—์„œ ์‹คํ–‰ํ•˜๊ณ  ๊ฐ ๋ชจ๋“ˆ์˜ `TREE` ๋งˆ์ปค๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. Worker๋Š” ์ด ํ•˜๋‹ˆ์Šค๋ฅผ `tests/fixtures/issue_5/phase1/shim_contexts.py`๋กœ ์˜ฎ๊ธฐ๊ณ , ๊ฐ€์งœ ํŠธ๋ฆฌ ๋Œ€์‹  **์‹ค์ œ ์ด๋™๋œ ํŠธ๋ฆฌ์˜ ์‚ฌ๋ณธ**(`copytree`)์„ `real/sandbox/sandbox2/freeze`๋กœ ์‚ฌ์šฉํ•˜๋ฉฐ, `TREE` ๋งˆ์ปค ๋Œ€์‹  `__file__`/`__path__` ์ ‘๋‘์–ด๋กœ ํŠธ๋ฆฌ๋ฅผ ํŒ๋ณ„ํ•˜๋„๋ก ๋ฐ”๊พผ๋‹ค. + +```python +# ํŠธ๋ฆฌ ๊ตฌ์„ฑ: /lib_py/{__init__.py, agents/__init__.py(TREE), delegation/{__init__.py, mqtt_common.py(TREE), publish_event.py(TREE + from lib_py.delegation import mqtt_common)}} +# /multi-agent-mux-delegate-job/scripts/{mqtt_common,publish_event}.py โ† D2 shim +# ์‹œ๋‚˜๋ฆฌ์˜ค (sys.path ์กฐ์ž‘์€ conftest/tier1/์‹คํ–‰ ๋ฐฉ์‹์„ ๋ชจ๋ธ๋ง): +# A : insert(0, real); insert(0, sandbox/scripts); import mqtt_common, publish_event +# A_pe : ๊ฐ™์Œ, import publish_event ๋งŒ +# B : insert(0, real); import lib_py.agents; insert(0, sandbox/scripts); import mqtt_common, publish_event +# B2 : insert(0, real); import lib_py.delegation.mqtt_common; insert(0, sandbox/scripts); import mqtt_common, publish_event +# C1 : insert(0, freeze/scripts); import mqtt_common, publish_event (PYTHONPATH ์—†์Œ) +# C1_pe: ๊ฐ™์Œ, publish_event ๋งŒ +# C2 : insert(0, real); insert(0, freeze/scripts); import mqtt_common, publish_event +# C2_pe: ๊ฐ™์Œ, publish_event ๋งŒ โ† ์—์ด์ „ํŠธ ํ˜ธ์ถœ ํ˜•ํƒœ +# D : insert(0, real); insert(0, sandbox/scripts); import mqtt_common; insert(0, sandbox2/scripts); import publish_event +# E : insert(0, real); insert(0, sandbox/scripts); import mqtt_common; from lib_py.delegation import publish_event +# ๋‹จ์ •: ๊ฐ ์‹œ๋‚˜๋ฆฌ์˜ค์—์„œ {alias_mc, pkg_mc, pe, pe.dep} ๊ฐ€ ๋‹จ์ผ ํŠธ๋ฆฌ์ด๊ณ , pytest ๊ณ„์—ด(A,A_pe,B,B2,D,E)์—์„œ agents == real, +# identity(alias is pkg) == True, sys.path ์— lib_py ๋ฅผ ๋‹ด์€ ๋น„-real ๋ฃจํŠธ๊ฐ€ ์—†์Œ(C1 ๊ณ„์—ด ์ œ์™ธ). +```