# ๐Ÿ“ ๊ตฌํ˜„ ๊ณ„ํš์„œ **Rev.2** โ€” Job `d6f54b7f` (์›์•ˆ: `79ff98ed`) - **์—ญํ• **: Planner (`MULTI_AGENT_RULES.md` ยง1 โ€” ์ €์žฅ์†Œ ์ฝ”๋“œ/๋ฌธ์„œ ๋ฌด์ˆ˜์ •, ์‚ฐ์ถœ๋ฌผ์€ ๋ณธ ๋ณด๊ณ ์„œ) - **๊ธฐ์ค€ ์ปค๋ฐ‹**: `14e306b` (branch `refactor`, working tree clean) - **๋ฒ ์ด์Šค๋ผ์ธ**: `pytest tests/ --collect-only` โ†’ **333 collected** - **์ž…๋ ฅ**: Job `9f85218e` ๋ฆฌ๋ทฐ `[VERDICT: PASS WITH CHALLENGE]` (Challenge C-1, Observation C-2) --- ## 0. Rev.1 โ†’ Rev.2 ๋ณ€๊ฒฝ ์š”์•ฝ | ํ•ญ๋ชฉ | ํŒ์ • | ์กฐ์น˜ | |---|---|---| | **Challenge C-1** โ€” T5 ๋ฌธ์„œ ๊ฐ€๋“œ์˜ ๋ธ”๋ก ์นด์šดํŒ… ์˜ค๋ฅ˜ + ๋ถ€๋ถ„ ๋ฌธ์ž์—ด ํ—ˆ์  | **์ „๋ฉด ์ˆ˜์šฉ. ๋‘ ๊ฐˆ๋ž˜ ๋ชจ๋‘ ์‹ค์ธก ํ™•์ธ** | ยง5 T5 ์žฌ์„ค๊ณ„ (ยง1.10์— ์‹ค์ธก ๊ทผ๊ฑฐ) | | **Observation C-2** โ€” `_env_int` ์˜ `ValueError` ์‹œ `continue` | **์ˆ˜์šฉ. ์ฑŒ๋ฆฐ์ €๊ฐ€ ์ œ์‹œํ•œ ๊ฒƒ๋ณด๋‹ค ๊ทผ๊ฑฐ๊ฐ€ ๋” ๊ฐ•ํ•จ** | ยง4.5 S5 ๋ณ€๊ฒฝ + ์ „์šฉ ํ…Œ์ŠคํŠธ T1b ์‹ ์„ค (ยง1.11) | | (์ž์ฒด ์ •์ •) Rev.1 ยง5 ์˜ "์‹ ์„ค 8๊ฑด" | **์˜ค์‚ฐ โ€” ์‹ค์ œ 7๊ฑด** | Rev.2 ๋Š” 8๊ฑด(C-2 ํ…Œ์ŠคํŠธ 1๊ฑด ์ถ”๊ฐ€). ๊ธฐ๋Œ€ collected 341 ์€ ๋™์ผํ•˜๋‚˜ ๊ทผ๊ฑฐ๊ฐ€ ๋‹ฌ๋ผ์ง | | (์‹ ๊ทœ) K-5 | โ€” | ๋ฌธ์„œํ™”๋˜์ง€ ์•Š์€ `MAM_MIN_COLS` ๊ฐ€ ๋ฌธ์„œํ™”๋œ `MAM_MIN_PANE_COLS` ๋ณด๋‹ค **์šฐ์„ ์ˆœ์œ„๊ฐ€ ๋†’๋‹ค** (ยง9) | C-1 ์€ ๊ณ„ํš๋Œ€๋กœ ๊ตฌํ˜„ํ•˜๋ฉด **ํ…Œ์ŠคํŠธ๊ฐ€ 100% ์‹คํŒจ**ํ•˜๋Š” ๊ฒฐํ•จ์ด์—ˆ์Šต๋‹ˆ๋‹ค. ์ฑŒ๋ฆฐ์ €์˜ ์ง€์ ์ด ์ •ํ™•ํ–ˆ๊ณ , ์‹ค์ธก์œผ๋กœ ์žฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค(ยง1.10). ๋‹ค๋งŒ ์ฑŒ๋ฆฐ์ €๊ฐ€ ์ œ์‹œํ•œ ์ˆ˜์ •์•ˆ์€ **๋‹ค๋ฅธ ์‹คํŒจ ๋ชจ๋“œ๋ฅผ ์ƒˆ๋กœ ๋งŒ๋“ญ๋‹ˆ๋‹ค** โ€” ๋ฌธ์„œ ์ „์ฒด๋ฅผ ์Šค์บ”ํ•˜๋ฏ€๋กœ ์‚ฐ๋ฌธ ์† ํŒŒ์ผ๋ช… ์–ธ๊ธ‰์„ ๋ช…๋ น์œผ๋กœ ์˜ค์ธํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ **์ปค๋งจ๋“œ ๋‹จ์œ„ ๊ฒ€์ฆ(์ฑŒ๋ฆฐ์ €์˜ ํ•ต์‹ฌ ๊ต์ •)** ์— **ํŽœ์Šค ์Šค์ฝ”ํ”„(์ถ”๊ฐ€ ๋ณด๊ฐ•)** ๋ฅผ ํ•ฉ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. ยง1.10.3 ์— ๋‘ ์‹คํŒจ ๋ชจ๋“œ๋ฅผ ๊ฐ๊ฐ ์‹ค์ธกํ–ˆ์Šต๋‹ˆ๋‹ค. C-2 ๋Š” ์ฑŒ๋ฆฐ์ €๊ฐ€ "๋‹ค์ค‘ fallback ์ทจ์ง€์— ๋ถ€ํ•ฉ" ์ •๋„๋กœ ์™„๊ณกํ•˜๊ฒŒ ์ œ๊ธฐํ–ˆ์ง€๋งŒ, ์‹ค์ธกํ•ด ๋ณด๋‹ˆ **`.mam.env.example` ์ด ๋ฌธ์„œํ™”ํ•œ ์œ ์ผํ•œ ์ด๋ฆ„์ด ์กฐ์šฉํžˆ ๋ฌด์‹œ๋˜๋Š”** ๊ฒฝ๋กœ์˜€์Šต๋‹ˆ๋‹ค. ๊ทผ๊ฑฐ๋ฅผ ๊ฐ•ํ™”ํ•ด ์ˆ˜์šฉํ•ฉ๋‹ˆ๋‹ค(ยง1.11). --- ## 1. ์‹ค์ธก (Measurements) > ยง1.1 ~ ยง1.9 ๋Š” Rev.1 ์—์„œ ํ™•์ •๋œ ์‹ค์ธก์ด๋ฉฐ ์žฌ๊ฒ€์ฆ ์—†์ด ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. ยง1.10 ยท ยง1.11 ์ด Rev.2 ์‹ ๊ทœ์ž…๋‹ˆ๋‹ค. ### 1.1 ์—์ด์ „ํŠธ ํ•ด์„๊ธฐ๊ฐ€ ์ €์žฅ์†Œ์— **4๊ฐœ** ์กด์žฌํ•œ๋‹ค | # | ์œ„์น˜ | ์šฐ์„ ์ˆœ์œ„ | ์‹คํŒจ ์‹œ | |---|---|---|---| | **1** | `lib_py/agents/registry.py:26` `agent_of_row()` | `agent` ํ•„๋“œ โ†’ ์ด๋ฆ„ ์ ‘๋ฏธ์‚ฌ โ†’ `pane.cmd` | `None` | | **2** | `stop_session.sh:101-109` | ์ด๋ฆ„ ์ ‘๋ฏธ์‚ฌ๋งŒ (์—ญํ•  ํ•œ์ •) | `exit 2` | | **3** | `update_yaml_resumed.sh:44-51` | **#2 ์™€ ์™„์ „ ๋™์ผํ•œ ๋ณต์‚ฌ๋ณธ** | `exit 2` | | **4** | `run_loop.sh:278` | `agent` ํ•„๋“œ โ†’ `pane.cmd` โ†’ ํ•˜์ดํ”ˆ ์„ธ๊ทธ๋จผํŠธ โ†’ **`claude` ๊ธฐ๋ณธ๊ฐ’** | ์‹คํŒจ ์—†์Œ | ``` SESSION_NAME stop/upd run_loop registry ---------------------------------- ---------- ---------- ---------- x-creator-claude claude claude claude agy-creator-01 EXIT2 agy None โ† ๋ผ์ด๋ธŒ ์„ธ์…˜ my-project-dev-claude EXIT2 claude claude โ† INSTALL.md ์˜ˆ์ œ ์ด๋ฆ„ worker-1-agy EXIT2 agy agy foo-cline EXIT2 cline cline bad-session-name EXIT2 claude None โ† run_loop ์€ ์กฐ์šฉํžˆ claude orc-hermes-main EXIT2 hermes None ``` 1. **`agy-creator-01` ์€ ์ง€๊ธˆ ์ด ์›Œํฌ์ŠคํŽ˜์ด์Šค์— running ์œผ๋กœ ๋“ฑ๋ก๋œ ์‹ค์ œ ์„ธ์…˜์ž…๋‹ˆ๋‹ค.** `pane.cmd = 'agy'` ๊ฐ€ ๊ธฐ๋ก๋ผ ์žˆ๋Š”๋ฐ๋„ `--agent` ์—†์ด๋Š” `exit 2` ๋กœ ๊ฑฐ๋ถ€๋ฉ๋‹ˆ๋‹ค. ๋ธŒ๋ฆฌํ”„๊ฐ€ ์ง€๋ชฉํ•œ ๊ฒฐํ•จ์˜ ์žฌํ˜„ ๊ฐ€๋Šฅํ•œ ๊ตฌ์ฒด ์‚ฌ๋ก€์ž…๋‹ˆ๋‹ค. 2. `my-project-dev-claude` ๋Š” `deploy/INSTALL.md:95` ๊ฐ€ ์Šค์Šค๋กœ ๋ฌธ์„œํ™”ํ•œ ์„ธ์…˜ ์ด๋ฆ„์ž…๋‹ˆ๋‹ค. ์ ‘๋ฏธ์‚ฌ๊ฐ€ `-dev-claude` ๋ผ #2 ์˜ ์—ญํ•  ํ•œ์ • ์ผ€์ด์Šค์— ๊ฑธ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. INSTALL.md ๊ฐ€ `--agent claude` ๋ฅผ ๋ช…์‹œํ•ด ์‚ฌ๊ณ ๊ฐ€ ์•ˆ ๋‚ฌ์„ ๋ฟ์ž…๋‹ˆ๋‹ค. 3. `run_loop.sh` ๋Š” ํ•ด์„ ์‹คํŒจ๋ฅผ `claude` ๋กœ ํก์ˆ˜ํ•ฉ๋‹ˆ๋‹ค. ํ˜ธ์ถœ 12๊ณณ์ด๋ผ ์ด๋ฒˆ ๋ฒ”์œ„ ๋ฐ–(ยง9 K-1). ๋ผ์ด๋ธŒ 3๊ฐœ ํ–‰์— `agent_of_row` ์ง์ ‘ ์ ์šฉ: ``` canary-projects-multi-agent-mux-creator-claude agent_of_row='claude' match_cmd=False โ†’ 'claude' canary-projects-multi-agent-mux-creator-cline agent_of_row='cline' match_cmd=False โ†’ 'cline' agy-creator-01 agent_of_row='agy' match_cmd=False โ†’ None ``` `match_cmd=True` ๋Š” docstring ์ƒ **๋น„-์ž…์–‘(non-adoption) ์กฐํšŒ**์šฉ์ด๊ณ  `stop`/`update_yaml_resumed` ๊ฐ€ ์ •ํ™•ํžˆ ๊ทธ ๊ฒฝ์šฐ์ž…๋‹ˆ๋‹ค. (`reconcile.sh` ์ž…์–‘ ๋ฃจํ”„ ๊ธˆ์ง€๋ผ๋Š” `3aee63cf` ยง1.2 ๋ฐ˜์ฆ์€ ์œ ํšจํ•˜๋ฉฐ, ์ด ๊ณ„ํš์€ `reconcile.sh` ๋ฅผ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.) ### 1.2 `agent` ํ•„๋“œ๋Š” ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค ``` row keys ํ•ฉ์ง‘ํ•ฉ: ['agy_conversation_id_own', 'attach_command', 'child_pid', 'claude_session_id_own', 'cline_conversation_id_own', 'delegate_job_id', 'herdr_server', 'herdr_session', 'herdr_session_created_at', 'herdr_session_epoch', 'kill_command', 'last_visible_status', 'last_visible_status_at_termination', 'mcp_attachments', 'name', 'pane', 'role', 'start_command', 'status', 'tui'] ``` 3๊ฐœ ํ–‰ ์ „๋ถ€ `agent=None`, `pane.cmd` ๋Š” 3๊ฐœ ์ „๋ถ€ ์ฑ„์›Œ์ง. โ†’ `agent` ํ•„๋“œ๋ฅผ **์“ฐ๋Š”** ์ฝ”๋“œ๋Š” ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๊ณ , ์šฐ์„ ์ˆœ์œ„ โ‘ ์€ ํ…Œ์ŠคํŠธ๋กœ๋งŒ ๊ณ ์ •ํ•ฉ๋‹ˆ๋‹ค(T4b). ### 1.3 `load_state_json` ์€ YAML ์ด ์•„๋‹ˆ๋ผ SQLite ๋ฅผ ์ฝ๋Š”๋‹ค `lib.sh:938-978` โ€” `.db` ์šฐ์„ , ์—†์„ ๋•Œ๋งŒ `.yaml`. ๋ผ์ด๋ธŒ์— `.mam/agent-sessions.db`(40 KiB) ์กด์žฌ. ๋ฌธ์„œยท์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€์—์„œ "๋ ˆ์ง€์ŠคํŠธ๋ฆฌ" ๋กœ ํ‘œํ˜„ํ•ฉ๋‹ˆ๋‹ค. ### 1.4 ๋น„์šฉ | ํ•ญ๋ชฉ | ์‹ค์ธก | |---|---| | `load_state_json` 1ํšŒ | ~34 ms | | `python3` ๊ธฐ๋™ + `import lib_py.agents.registry` | ~27 ms | | `stop_session.sh` ๊ฐ€ ์ด๋ฏธ ์ˆ˜ํ–‰ํ•˜๋Š” `load_state_json` | **2ํšŒ** (`:97`, `:113`) | `PYTHONPATH` ๋Š” `lib.sh:25` ๊ฐ€ export ํ•˜๋ฏ€๋กœ ๋งจ `python3` ๋กœ ์ž„ํฌํŠธ ๊ฐ€๋Šฅ. venv ์—†๋Š” ์‹œ์Šคํ…œ ํŒŒ์ด์ฌ(3.9.6)์—์„œ `env -i` ๊ฒ€์ฆ ์™„๋ฃŒ. `registry` ๋Š” ์„œ๋“œํŒŒํ‹ฐ ์˜์กด ์—†์Œ(`yaml` ๋ถˆํ•„์š” โ€” ์ƒํƒœ๋Š” JSON ์œผ๋กœ env ์ „๋‹ฌ). ### 1.5 J-1 ์žฌํ˜„ ํŽ˜์ด๋กœ๋“œ: 1ํŒจ๋„ `width=50, height=30` | ๊ฒฝ๋กœ | ๊ฒฐ๊ณผ | |---|---| | `--min-cols 0` | `right` / `single_pane_height_constrained` | | `MAM_MIN_PANE_COLS=0` | **`overflow`** | | `MAM_MIN_COLS=0` | **`overflow`** | | `--min-rows 0` | `down` | | `MAM_MIN_PANE_ROWS=0` | **`overflow`** | | **๋Œ€์กฐ๊ตฐ** `--min-cols 25` vs `MAM_MIN_PANE_COLS=25` | **์–‘์ชฝ ๋™์ผ** (`right`) | ๋Œ€์กฐ๊ตฐ์ด ๊ฒฐํ•จ์„ `or` ๊ด€์šฉ๊ตฌ์˜ falsy-zero ํ•˜๋‚˜๋กœ ๊ตญ์†Œํ™”ํ•ฉ๋‹ˆ๋‹ค. ### 1.6 J-2 ์ž„๊ณ„๊ฐ’ ``` n=3 n//2=1 -> down โ† ํ˜„ํ–‰ d3 ๋‹จ์–ธ. ์ƒํ•œ ๊ฒ€์‚ฌ ๋„๋‹ฌ ๋ถˆ๊ฐ€ n=4 n//2=2 -> overflow n=5 n//2=2 -> down โ† ํŒ๋ณ„ ๊ฐ€๋Šฅํ•œ ์ตœ์†Œ ํ™€์ˆ˜ n=6 n//2=3 -> overflow n=7 n//2=3 -> down ``` ### 1.7 ๋ฌธ์„œ ์‹คํƒœ | ํŒŒ์ผ | ํ˜„์ƒ | |---|---| | `multi-agent-mux-stop/SKILL.md` | `--agent` **0ํšŒ**. ์›Œํฌํ”Œ๋กœ ์˜ˆ์ œ 3๊ฐœ(`:68, :72, :77`) ์ „๋ถ€ ์ƒ๋žต | | `deploy/INSTALL.md:94, :98` | `--agent claude` **์ด๋ฏธ ๋ช…์‹œ** โ€” ์œ ์ผํ•œ ๋ชจ๋ฒ” ์‚ฌ๋ก€ | | `multi-agent-mux-create/SKILL.md:146` | `AGENT=claude # or agy` | | `multi-agent-mux-create/SKILL.md:171` | `must be claude or agy` โ€” ์‹ค๋ฌผ `create_session.sh:86` ์€ 4์ข…์„ ๋ฐ›์Œ | | `multi-agent-mux-resume/SKILL.md:61` | `# or agy or hermes` (cline ๋ˆ„๋ฝ) | | `create_session.sh:4`, `resolve_session_id.sh:4` | ํ—ค๋” ์ฃผ์„ `` | | `update_yaml_resumed.sh:7, :14` | `[--agent claude\|agy]` | `create_session.sh` ๋Š” ์ด๋ฏธ `--agent` ํ•„์ˆ˜ + 4์ข… ๊ฒ€์ฆ(`:83`, `:85-86`). create ์ชฝ์€ **๋ฌธ์„œ ๋™๊ธฐํ™”๋ฟ**์ž…๋‹ˆ๋‹ค. ### 1.8 ๊ธฐ์กด ํ…Œ์ŠคํŠธ ๊ณ„์•ฝ | ํ…Œ์ŠคํŠธ | ์„ธ์…˜๋ช… | ํ˜„ํ–‰ | |---|---|---| | `tests/test_tier1_unit.py:142` | `bad-session-name` | rc=2, `cannot infer agent` | | `tests/test_tier3_integration.py:398` | `bad-name` | rc=2, `cannot infer agent` | ๋‘ ์ด๋ฆ„ ๋ชจ๋‘ ์ƒŒ๋“œ๋ฐ•์Šค ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ(`herdr_sessions: []`)์— ์—†์Šต๋‹ˆ๋‹ค. ยง3 ์„ค๊ณ„ ๊ฒฐ์ •์„ ์ง€๋ฐฐํ•ฉ๋‹ˆ๋‹ค. ### 1.9 (๋ถ€์ˆ˜) `cd โ€ฆ 2>/dev/null || pwd` ๊ฒฐํ•จ ``` line35 result: [/lib.sh] โ†’ ์กด์žฌํ•˜์ง€ ์•Š์Œ, ํ•ญ์ƒ :36 ํด๋ฐฑ correct form : [/Users/.../.agents/skills/lib.sh] ``` ์ž”์กด: `stop_session.sh:35`, `create_session.sh:23`, `resume_session.sh:6`. (`update_yaml_resumed.sh:10` ์€ ์ด๋ฏธ ์ •์ƒ.) `31b2d70` ์˜ R-2 ์™€ ๋™์ผ ๊ฒฐํ•จ. 1์ฐจ ์†Œ์‹ฑ ๊ฒฝ๋กœ๊ฐ€ 100% ์ฃฝ์–ด `${WORKSPACE_ROOT:-$PWD}` ํด๋ฐฑ์—๋งŒ ์˜์กดํ•ฉ๋‹ˆ๋‹ค. --- ### 1.10 **[Rev.2 ์‹ ๊ทœ] Challenge C-1 ๊ฒ€์ฆ** #### 1.10.1 ๊ฐˆ๋ž˜ โ‘  โ€” `checked == 2` ๋กœ ๋‹จ์–ธ์ด ์‹คํŒจํ•œ๋‹ค โ†’ **ํ™•์ธ** Rev.1 T5 ์˜ ๋ธ”๋ก ๋‹จ์œ„ ์ •๊ทœ์‹์„ ํ˜„์žฌ ๋ฌธ์„œ์— ๊ทธ๋Œ€๋กœ ์ ์šฉ: ``` SKILL.md: total fenced bash/sh blocks=3, containing stop_session.sh=1 -> one block holds 3 stop_session.sh invocations; '--agent' present in block: False INSTALL.md: total fenced bash/sh blocks=7, containing stop_session.sh=1 -> one block holds 2 stop_session.sh invocations; '--agent' present in block: True CHECKED = 2 (planner asserted >= 4) ``` `assert checked >= 4` ๋Š” **๊ฒฐ์ •๋ก ์ ์œผ๋กœ ์‹คํŒจ**ํ•ฉ๋‹ˆ๋‹ค. ์ฑŒ๋ฆฐ์ €์˜ ์ง€์ ์ด ์ •ํ™•ํ•ฉ๋‹ˆ๋‹ค. ์ œ๊ฐ€ ยง1.7 ์—์„œ "์˜ˆ์ œ 3๊ฐœ(`:68, :72, :77`)" ๋ฅผ ์„ธ๋ฉด์„œ๋„ ๊ทธ๊ฒƒ์ด **ํ•˜๋‚˜์˜ ํŽœ์Šค ์•ˆ์— ๋“ค์–ด ์žˆ๋‹ค**๋Š” ์‚ฌ์‹ค์„ ํ™•์ธํ•˜์ง€ ์•Š์€ ๊ฒƒ์ด ์›์ธ์ž…๋‹ˆ๋‹ค โ€” ๊ฐœ์ˆ˜๋Š” ์…Œ์ง€๋งŒ **๊ฒฝ๊ณ„๋ฅผ ์„ธ์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค**. #### 1.10.2 ๊ฐˆ๋ž˜ โ‘ก โ€” ๋ธ”๋ก ๋‹จ์œ„ ๋‹จ์–ธ์˜ ์œ„์–‘์„ฑ(False Positive) โ†’ **ํ™•์ธ** INSTALL.md ์‚ฌ๋ณธ์—์„œ **๋‘ ํ˜ธ์ถœ ์ค‘ ํ•˜๋‚˜์—์„œ๋งŒ** `--agent` ๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ๋ฎคํ…Œ์ด์…˜: ``` mutation applied (agent count 2 -> 1) ์„ค๊ณ„ A (๋ธ”๋ก ๋‹จ์œ„, Rev.1 ์›์•ˆ): blocks=1 all pass? True โ† ๋ฎคํ…Œ์ด์…˜ ๋ฏธ๊ฒ€์ถœ ์„ค๊ณ„ C (ํŽœ์Šค+์ปค๋งจ๋“œ, Rev.2 ์ •์ œ์•ˆ): checked=2 missing=1 โ† ๋ฎคํ…Œ์ด์…˜ ๊ฒ€์ถœ ``` ๋ธ”๋ก์— `--agent` ๊ฐ€ **ํ•œ ๋ฒˆ์ด๋ผ๋„** ๋‚˜์˜ค๋ฉด ํ†ต๊ณผํ•ฉ๋‹ˆ๋‹ค. ํšŒ๊ท€๋ฅผ ๋ชป ์žก๋Š” ๊ฐ€๋“œ๋Š” ๊ฐ€๋“œ๊ฐ€ ์•„๋‹ˆ๋ผ ์ฃผ์„์ž…๋‹ˆ๋‹ค. ์ฑŒ๋ฆฐ์ €์˜ ์ง€์ ์ด ์ •ํ™•ํ•ฉ๋‹ˆ๋‹ค. #### 1.10.3 ์ฑŒ๋ฆฐ์ € ์ˆ˜์ •์•ˆ์˜ ์ž”์—ฌ ์‹คํŒจ ๋ชจ๋“œ โ†’ **๋ฌธ์„œ ์ „์ฒด ์Šค์บ”์ด ์‚ฐ๋ฌธ์„ ๋ช…๋ น์œผ๋กœ ์˜ค์ธํ•œ๋‹ค** ์ฑŒ๋ฆฐ์ € ์ˆ˜์ •์•ˆ์€ `doc.read_text()` **์ „์ฒด**์— ์ปค๋งจ๋“œ ์ •๊ทœ์‹์„ ๋Œ๋ฆฝ๋‹ˆ๋‹ค. ์‚ฐ๋ฌธ ์† ํŒŒ์ผ๋ช… ์–ธ๊ธ‰์ด ์žˆ๋Š” ๋ฌธ์„œ๋กœ ์‹ค์ธก: ``` === ์ฑŒ๋ฆฐ์ € ์ˆ˜์ •์•ˆ (๋ฌธ์„œ ์ „์ฒด ์Šค์บ”) === [1] --agent=NO | '`stop_session.sh` does not delete report trees.' โ† ์œ„์–‘์„ฑ [2] --agent=NO | '`stop_session.sh --purge-conversation` note below.' โ† ์œ„์–‘์„ฑ [3] --agent=YES | 'bash .../stop_session.sh --session "$S" --agent "$A"' [4] --agent=NO | 'bash .../stop_session.sh --session "$S"' === ํŽœ์Šค ์Šค์ฝ”ํ”„ + ์ปค๋งจ๋“œ ๋‹จ์œ„ (Rev.2) === [1] --agent=YES | 'bash .../stop_session.sh --session "$S" --agent "$A"' [2] --agent=NO | 'bash .../stop_session.sh --session "$S"' checked=2 ``` ์‚ฐ๋ฌธ ๋‘ ์ค„์ด ๊ฐ๊ฐ `checked += 1` ๋˜๊ณ  `--agent` ๊ฐ€ ์—†์œผ๋ฏ€๋กœ **ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจ**ํ•ฉ๋‹ˆ๋‹ค. ์ด๊ฒƒ์ด ๊ฐ€์„ค์ด ์•„๋‹ˆ๋ผ ์ž„๋ฐ•ํ•œ ๋ฌธ์ œ์ธ ์ด์œ : - ์ด ๊ณ„ํš **ยง4.4 ์ž์ฒด๊ฐ€ stop/SKILL.md ์— ์‚ฐ๋ฌธ ๋ฌธ๋‹จ์„ ์ถ”๊ฐ€**ํ•ฉ๋‹ˆ๋‹ค. - `stop/SKILL.md` ์˜ `## Pitfalls` ยท `## When NOT to use` ์ ˆ์€ ์„ฑ๊ฒฉ์ƒ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฐ๋ฌธ์œผ๋กœ ์–ธ๊ธ‰ํ•˜๊ฒŒ ๋˜๋Š” ์ž๋ฆฌ์ž…๋‹ˆ๋‹ค. - ๋ฌธ์žฅ์„ ํ•˜๋‚˜ ์ผ๋‹ค๊ณ  ์‹คํŒจํ•˜๋Š” ๊ฐ€๋“œ๋Š” ๋‹ค์Œ ์‚ฌ๋žŒ์ด **์ง€์›๋‹ˆ๋‹ค**. ํ˜„์žฌ ๋‘ ๋ฌธ์„œ์—๋Š” ํŽœ์Šค ๋ฐ– ์–ธ๊ธ‰์ด 0๊ฑด์ด๋ผ(SKILL.md 3ํšŒยทINSTALL.md 2ํšŒ ๋ชจ๋‘ ํŽœ์Šค ์•ˆ) ์ฑŒ๋ฆฐ์ € ์ˆ˜์ •์•ˆ๋„ **์ง€๊ธˆ์€** ํ†ต๊ณผํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ฐ€๋“œ์˜ ์กด์žฌ ์ด์œ ๋Š” ๋ฏธ๋ž˜์˜ ํŽธ์ง‘์„ ๊ฒฌ๋””๋Š” ๊ฒƒ์ด๋ฏ€๋กœ, ์ง€๊ธˆ ํ†ต๊ณผํ•˜๋Š” ๊ฒƒ๋งŒ์œผ๋กœ๋Š” ๋ถ€์กฑํ•ฉ๋‹ˆ๋‹ค. #### 1.10.4 ์ •์ œ์•ˆ ๊ฒ€์ฆ โ€” ๊ณ„ํš ยง4.4 ์ ์šฉ ํ›„ `ยง4.4` ๋Œ€๋กœ ํŽธ์ง‘ํ•œ ์‚ฌ๋ณธ(3๊ฐœ ์˜ˆ์ œ์— `--agent "$AGENT"` ์ถ”๊ฐ€ + `stop_session.sh` ๋ฌธ์ž์—ด์„ ํฌํ•จํ•˜์ง€ ์•Š๋Š” ์‚ฐ๋ฌธ ๋ฌธ๋‹จ ์ถ”๊ฐ€)์— ์ •์ œ์•ˆ ์ ์šฉ: ``` SKILL.md checked=3 missing_agent=0 INSTALL.md checked=2 missing_agent=0 ``` ์ด 5๊ฑด, ์ „๊ฑด ํ†ต๊ณผ. ํŽœ์Šค ์Šค์ฝ”ํ”„ ๋•๋ถ„์— **"์‚ฐ๋ฌธ์— ํŒŒ์ผ๋ช…์„ ์“ฐ์ง€ ๋ง๋ผ"๋Š” ์ œ์•ฝ์ด ๊ณ„ํš์—์„œ ์‚ฌ๋ผ์ง‘๋‹ˆ๋‹ค** โ€” ์ด๊ฒƒ์ด ์ฑŒ๋ฆฐ์ € ์ˆ˜์ •์•ˆ ๋Œ€๋น„ ์‹ค์งˆ ์ด๋“์ž…๋‹ˆ๋‹ค. ### 1.11 **[Rev.2 ์‹ ๊ทœ] Observation C-2 ๊ฒ€์ฆ โ€” ๊ทผ๊ฑฐ๋Š” ์ฑŒ๋ฆฐ์ €๊ฐ€ ์ œ์‹œํ•œ ๊ฒƒ๋ณด๋‹ค ๊ฐ•ํ•˜๋‹ค** #### 1.11.1 ์–ด๋А ์ด๋ฆ„์ด ์ •๋ณธ์ธ๊ฐ€ ``` .mam.env.example:133 # MAM_MIN_PANE_COLS=60 .mam.env.example:137 # MAM_MIN_PANE_ROWS=20 .mam.env.example:143 # MAM_MAX_PANE_COLS=3 lib.sh:432 --min-cols "${MAM_MIN_PANE_COLS:-60}" --min-rows "${MAM_MIN_PANE_ROWS:-20}" test_herdr_shim_contract.py:100-101 export MAM_MIN_PANE_COLS=60 / MAM_MIN_PANE_ROWS=20 ``` `MAM_MIN_COLS` / `MAM_MIN_ROWS` / `MAM_MAX_COLS` ๋‹จ์ถ•ํ˜•์€ **`layout.py:191-193` ์•ˆ์—์„œ๋งŒ** ๋“ฑ์žฅํ•ฉ๋‹ˆ๋‹ค. ์ƒ์‚ฐ ์ฝ”๋“œยท๋ฌธ์„œยทํ…œํ”Œ๋ฆฟยทํ…Œ์ŠคํŠธ ์–ด๋””์—๋„ ์—†์Šต๋‹ˆ๋‹ค. `report-8f0cb35f.md:72` ๋Š” ์ •๋ฆฌ ์ž‘์—… ๋‹น์‹œ *"no legacy `MAM_MIN_COLS=`/`MAM_MIN_ROWS=` env-prefix style"* ์„ ํ™•์ธ ์‚ฌํ•ญ์œผ๋กœ ์ ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค โ€” ๋‹จ์ถ•ํ˜•์€ **๋ ˆ๊ฑฐ์‹œ ๋ณ„์นญ**์ž…๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ `_env_int("MAM_MIN_COLS", "MAM_MIN_PANE_COLS")` ๋Š” **๋ ˆ๊ฑฐ์‹œ ๋‹จ์ถ•ํ˜•์„ ๋จผ์ €** ๋ด…๋‹ˆ๋‹ค. #### 1.11.2 ๊ฒฐ๊ณผ: ๋ฌธ์„œํ™”๋œ ์œ ์ผํ•œ ์ด๋ฆ„์ด ์กฐ์šฉํžˆ ๋ฌด์‹œ๋œ๋‹ค ``` env ํ˜„ํ–‰ or 60 return default continue {} 60 60 60 {'MAM_MIN_PANE_COLS': '0'} 60 0 0 {'MAM_MIN_PANE_COLS': '25'} 25 25 25 {'MAM_MIN_COLS': 'foo'} 60 60 60 {'MAM_MIN_COLS': '', 'MAM_MIN_PANE_COLS': '25'} 25 25 25 {'MAM_MIN_COLS': 'foo', 'MAM_MIN_PANE_COLS': '25'} 60 60 25 โ† ์ฐจ์ด {'MAM_MIN_COLS': 'foo', 'MAM_MIN_PANE_COLS': 'bar'} 60 60 60 {'MAM_MIN_COLS': 'foo', 'MAM_MIN_PANE_COLS': '0'} 60 60 0 โ† ์ฐจ์ด ``` ์ฝ์–ด์•ผ ํ•  ๋‘ ๊ฐ€์ง€: 1. **`""` ์™€ `"foo"` ๊ฐ€ ๋‹ค๋ฅด๊ฒŒ ์ทจ๊ธ‰๋ฉ๋‹ˆ๋‹ค.** ๋นˆ ๋ฌธ์ž์—ด์€ ๋‹ค์Œ ํ›„๋ณด๋กœ ๋„˜์–ด๊ฐ€๊ณ (`if raw:` ๊ฐ€ ๊ฑธ๋Ÿฌ๋ƒ„), ๋ฌดํšจ ๋ฌธ์ž์—ด์€ ์ฆ‰์‹œ ํƒˆ์ถœํ•ฉ๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค "์“ธ ์ˆ˜ ์—†๋Š” ๊ฐ’"์ธ๋ฐ ์ฒ˜๋ฆฌ๊ฐ€ ์ •๋ฐ˜๋Œ€์ž…๋‹ˆ๋‹ค. `continue` ๋Š” ์ด ๋น„๋Œ€์นญ์„ ์—†์•ฑ๋‹ˆ๋‹ค. 2. ์ฐจ์ด๊ฐ€ ๋‚˜๋Š” ๋‘ ํ–‰์—์„œ ๋ฌด์‹œ๋˜๋Š” ๊ฐ’์€ **`.mam.env.example` ์ด ๋ฌธ์„œํ™”ํ•œ ๋ฐ”๋กœ ๊ทธ ๋ณ€์ˆ˜**์ž…๋‹ˆ๋‹ค. ์šด์˜์ž๊ฐ€ ํ…œํ”Œ๋ฆฟ๋Œ€๋กœ `MAM_MIN_PANE_COLS=25` ๋ฅผ ์„ค์ •ํ–ˆ๋Š”๋ฐ, ์…ธ ์–ด๋”˜๊ฐ€์— ๋‚จ์€ `MAM_MIN_COLS=foo` ํ•˜๋‚˜ ๋•Œ๋ฌธ์— 60 ์ด ์ ์šฉ๋ฉ๋‹ˆ๋‹ค. 3. **์ฐจ์ด๋Š” ์ •ํ™•ํžˆ 2ํ–‰๋ฟ์ž…๋‹ˆ๋‹ค.** ๋‚˜๋จธ์ง€ 7ํ–‰์€ ์„ธ ๊ตฌํ˜„์ด ์™„์ „ํžˆ ์ผ์น˜ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰ `continue` ๋Š” J-1 ์ˆ˜์ •๊ณผ ์ง๊ตํ•˜๊ณ , ํ–‰๋™ ๋ณ€๊ฒฝ ํ‘œ๋ฉด์ด "์ฒซ ํ›„๋ณด ๋ฌดํšจ + ํ›„์† ํ›„๋ณด ์œ ํšจ" ๋ผ๋Š” ํ•œ ์กฐ๊ฑด์œผ๋กœ ์ขํ˜€์ง‘๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ 1๊ฑด์œผ๋กœ ์™„์ „ํžˆ ๊ณ ์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(T1b). **๊ฒฐ๋ก : C-2 ์ˆ˜์šฉ.** ์ฑŒ๋ฆฐ์ €๋Š” "๋‹ค์ค‘ fallback ์ทจ์ง€์— ๋ถ€ํ•ฉ" ์ด๋ผ๋Š” ์„ค๊ณ„ ๋…ผ๊ฑฐ๋กœ ์ œ๊ธฐํ–ˆ๋Š”๋ฐ, ์‹ค์ธกํ•˜๋ฉด **๋ฌธ์„œํ™”๋œ ์„ค์ •์ด ๋ฌด์‹œ๋˜๋Š” ์‹ค๋™์ž‘ ๊ฒฐํ•จ**์ด๋ผ ๊ทผ๊ฑฐ๊ฐ€ ๋” ๊ฐ•ํ•ฉ๋‹ˆ๋‹ค. Rev.1 ์ด `return default` ๋ฅผ ๊ณ ๋ฅธ ์ด์œ ๋Š” "์˜คํƒ€ ์ž…๋ ฅ์— ๋Œ€ํ•œ ํ–‰๋™ ๋™๋“ฑ์„ฑ ๋ณด์กด" ์ด์—ˆ๊ณ  ๊ทธ ๋ชฉํ‘œ ์ž์ฒด๋Š” ์œ ํšจํ•˜์ง€๋งŒ, ์œ„ ํ‘œ์˜ 5ยท7ํ–‰์ด ๋ณด์—ฌ์ฃผ๋“ฏ **`continue` ๋„ ๊ทธ ๋ชฉํ‘œ๋ฅผ ๋˜‘๊ฐ™์ด ๋งŒ์กฑ**ํ•ฉ๋‹ˆ๋‹ค(๋ชจ๋“  ํ›„๋ณด๊ฐ€ ๋ฌดํšจ๋ฉด `default`). Rev.1 ์€ ๋” ์ข์€ ๋ถˆ๋ณ€์‹์„ ์ง€ํ‚ค๋А๋ผ ๋” ๋‚˜์€ ๊ฒƒ์„ ๋†“์ณค์Šต๋‹ˆ๋‹ค. --- ## 2. ๋ฒ”์œ„ **ํฌํ•จ** | # | ํ•ญ๋ชฉ | |---|---| | S1 | `lib.sh` ์— `resolve_agent_type_from_registry()` ๊ณต์šฉ ํ—ฌํผ ์‹ ์„ค | | S2 | `stop_session.sh` ํด๋ฐฑ์„ S1 ๋กœ ๊ต์ฒด + ํ—ค๋”/`usage()` ๊ฐฑ์‹  | | S3 | `update_yaml_resumed.sh` ์˜ ๋™์ผ ๋ณต์‚ฌ๋ณธ์„ S1 ๋กœ ๊ต์ฒด + ํ—ค๋”/`usage()` ๊ฐฑ์‹  | | S4 | `stop`/`resume`/`create` SKILL.md ๋ฐ 3๊ฐœ ์Šคํฌ๋ฆฝํŠธ ํ—ค๋” ์ฃผ์„ ๋ฌธ์„œ ๋™๊ธฐํ™” | | S5 | J-1: `_env_int(*names, default=None)` ๋ฆฌํŒฉํ„ฐ(+ **C-2 `continue`**) ๋ฐ `main()` ๋ฐฐ์„  | | S6 | ํšŒ๊ท€ ํ…Œ์ŠคํŠธ **8๊ฑด** ์‹ ์„ค + ๊ธฐ์กด J-2 ๊ฐ€๋“œ 1๊ฑด ๋ณด๊ฐ• | | S7 | `IMPROVEMENTS.md` ๋ฐฑ๋กœ๊ทธ ๋“ฑ๋ก ๋ฐ ์™„๋ฃŒ ์นด์šดํŠธ ๊ฐฑ์‹  | | S8 | (๋ถ„๋ฆฌ ์ปค๋ฐ‹) ยง1.9 `cd โ€ฆ && pwd` 3๊ณณ | **์ œ์™ธ** | ํ•ญ๋ชฉ | ์ œ์™ธ ์‚ฌ์œ  | |---|---| | `run_loop.sh:278` ํ†ตํ•ฉ | ํ˜ธ์ถœ 12๊ณณ + `claude` ๊ธฐ๋ณธ๊ฐ’ ์ œ๊ฑฐ๋Š” ํ–‰๋™ ๋ณ€๊ฒฝ โ†’ K-1 | | `reconcile.sh` ํ•ด์„ ๊ฒฝ๋กœ | `3aee63cf` ยง1.2 ์‹ค์ธก ๋ฐ˜์ฆ ์œ ํšจ | | `agent_of_row` ์„ธ๊ทธ๋จผํŠธ ๋งค์นญ | `reconcile.sh` ์ž…์–‘ ํŒ์ •์— ์˜ํ–ฅ โ†’ K-4 | | `max_columns` falsy-zero | ๋ธŒ๋ฆฌํ”„๊ฐ€ min-cols/min-rows ๋งŒ ์ง€๋ชฉ โ†’ K-2 | | **`_env_int` ํ›„๋ณด ์ˆœ์„œ ๋’ค์ง‘๊ธฐ** | ๋ฌธ์„œํ™”๋œ `MAM_MIN_PANE_COLS` ๋ฅผ ์•ž์œผ๋กœ ์˜ฎ๊ธฐ๋Š” ๊ฒƒ์€ **์šฐ์„ ์ˆœ์œ„ ๋ณ€๊ฒฝ**์ด๋ผ C-2 (๋ฌดํšจ๊ฐ’ ๊ฑด๋„ˆ๋›ฐ๊ธฐ)์™€ ๋ณ„๊ฐœ ์‚ฌ์•ˆ โ†’ **K-5** | | ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ์— `agent` ํ•„๋“œ ์“ฐ๊ธฐ | ์“ฐ๋Š” ์ฝ”๋“œ๊ฐ€ 0๊ฑด์ด๊ณ  ์š”๊ตฌ๋˜์ง€ ์•Š์Œ | --- ## 3. ์„ค๊ณ„ ๊ฒฐ์ • โ€” ํด๋ฐฑ์„ **์–ด๋””์—** ๋„ฃ๋Š”๊ฐ€ (Rev.1 ์œ ์ง€) `stop_session.sh` ํ˜„์žฌ ์ˆœ์„œ: ``` :88 --session ๊ฒ€์‚ฌ โ†’ exit 2 :89 YAML ํŒŒ์ผ ์กด์žฌ ๊ฒ€์‚ฌ โ†’ exit 1 :97 resolve_herdr_workspace (load_state_json #1) :101 AGENT ์ ‘๋ฏธ์‚ฌ ์ถ”๋ก  โ†’ exit 2 โ† ๊ต์ฒด ๋Œ€์ƒ :113 MAPPED_DATA: row ์กฐํšŒ (load_state_json #2) :124 row ์—†์Œ โ†’ exit 1 :152 AGENT ์ตœ์ดˆ ์‚ฌ์šฉ ``` **์•ˆ A (๊ธฐ๊ฐ)** โ€” `:113` ๋ธ”๋ก์— ๋ณ‘ํ•ฉ. ํ”„๋กœ์„ธ์Šค 1๊ฐœ ์ ˆ์•ฝ, ์ฝ”๋“œ๋„ ๊ฐ€์žฅ ๊น”๋”. **๊ธฐ๊ฐ ์‚ฌ์œ **: ํ•ด์„์ด row ์กฐํšŒ ๋’ค๋กœ ๋ฐ€๋ ค "๋ฏธ๋“ฑ๋ก + ์ด๋ฆ„ ํ•ด์„ ์‹คํŒจ" ์„ธ์…˜์˜ ์ข…๋ฃŒ ์ฝ”๋“œ๊ฐ€ **2 โ†’ 1** ๋กœ ๋ฐ”๋€๋‹ˆ๋‹ค. ยง1.8 ์˜ ๋‘ ํ…Œ์ŠคํŠธ๊ฐ€ ๊นจ์ง€๊ณ  ํ—ค๋” `:28-30` ์˜ ๊ณ„์•ฝ๋„ ๋ฐ”๋€๋‹ˆ๋‹ค. ์–ป๋Š” ๊ฒƒ์€ 34 ms ๋ฟ์ž…๋‹ˆ๋‹ค. **์•ˆ B (์ฑ„ํƒ)** โ€” `:101` ์ž๋ฆฌ๋ฅผ ๊ทธ๋Œ€๋กœ ๋‘๊ณ  ํ•ด์„๊ธฐ๋งŒ ๊ต์ฒด. | ์„ฑ์งˆ | ๊ฒฐ๊ณผ | |---|---| | ์ข…๋ฃŒ ์ฝ”๋“œ ๊ณ„์•ฝ | **๋ถˆ๋ณ€** (`exit 2`, ๋™์ผ ๋ฉ”์‹œ์ง€) | | ยง1.8 ๊ธฐ์กด ํ…Œ์ŠคํŠธ 2๊ฑด | **์ˆ˜์ • ๋ถˆํ•„์š”** | | `agy-creator-01` | `EXIT2` โ†’ `agy` โœ… | | `my-project-dev-claude`, `foo-cline`, `worker-1-agy` | `EXIT2` โ†’ ์ •์ƒ ํ•ด์„ โœ… | | ๋ฏธ๋ž˜์˜ `agent` ๋ช…์‹œ ํ•„๋“œ | ์ž๋™ ์ง€์› โœ… | | ๋น„์šฉ | `--agent` ์ƒ๋žต ์‹œ์—๋งŒ `load_state_json` 1ํšŒ (~34 ms) | `set -euo pipefail` ์ฃผ์˜: ์‹คํŒจ ๊ฐ€๋Šฅํ•œ ๋ช…๋ น ์น˜ํ™˜์„ ๋Œ€์ž…์— ์“ฐ๋ฏ€๋กœ ๋ฐ˜๋“œ์‹œ `|| AGENT=""` ๋กœ ๊ฐ์Œ‰๋‹ˆ๋‹ค(`test_lib_sh_layout_split_in_set_e_subshell` ์„ ๋ก€). `stderr` ๋Š” ์–ต์ œํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค โ€” ์ •์ƒ ํ•ด์„ ์‹คํŒจ๋Š” `sys.exit(1)` ์ด๋ผ ๋ฌด์ถœ๋ ฅ์ด๊ณ , `PYTHONPATH` ํŒŒ์† ๊ฐ™์€ ์ง„์งœ ์˜ค๋ฅ˜์˜ traceback ์€ ๋ณด์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ์กด ํ…Œ์ŠคํŠธ๋Š” ๋ถ€๋ถ„ ๋ฌธ์ž์—ด ๋‹จ์–ธ์ด๋ผ traceback ์ด ์„ž์—ฌ๋„ ๋ฌด์˜ํ–ฅ์ž…๋‹ˆ๋‹ค. --- ## 4. ๊ตฌํ˜„ ### 4.1 S1 โ€” `lib.sh` ๊ณต์šฉ ํ—ฌํผ `resolve_herdr_session()`(`:989`) ๋ฐ”๋กœ ์•ž์— ์ถ”๊ฐ€. ```bash # resolve_agent_type_from_registry # # ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ(YAML/DB)์— ๊ธฐ๋ก๋œ ์‚ฌ์‹ค๋กœ ์—์ด์ „ํŠธ ์ข…๋ฅ˜๋ฅผ ํ•ด์„ํ•œ๋‹ค. ์šฐ์„ ์ˆœ์œ„๋Š” # lib_py.agents.registry.agent_of_row ์˜ ๊ณ„์•ฝ์„ ๊ทธ๋Œ€๋กœ ๋”ฐ๋ฅธ๋‹ค: # โ‘  row['agent'] ๋ช…์‹œ ํ•„๋“œ # โ‘ก ์„ธ์…˜๋ช… ์ ‘๋ฏธ์‚ฌ (*-{creator,planner,reviewer}- ๋ฐ *-) # โ‘ข pane.cmd (์ •ํ™•ํžˆ ์ผ์น˜ํ•˜๊ฑฐ๋‚˜ .../ ๋ฐ”์ด๋„ˆ๋ฆฌ ๊ฒฝ๋กœ) # ์„ฑ๊ณตํ•˜๋ฉด ์—์ด์ „ํŠธ๋ช…์„ stdout ์— ์ถœ๋ ฅํ•˜๊ณ  0 ์„, ์…‹ ๋‹ค ์‹คํŒจํ•˜๋ฉด ์•„๋ฌด๊ฒƒ๋„ # ์ถœ๋ ฅํ•˜์ง€ ์•Š๊ณ  1 ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋Š” ํ˜ธ์ถœ์ž๊ฐ€ ์†Œ์œ ํ•œ๋‹ค โ€” ๊ฐ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ # ๋ฌธ์„œํ™”ํ•œ ์ข…๋ฃŒ ์ฝ”๋“œ๋ฅผ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋‹ค. # # NOTE: agent_of_row ์˜ match_cmd=True ๋Š” "๋น„-์ž…์–‘ ์กฐํšŒ" ๊ณ„์•ฝ์ด๋‹ค. reconcile.sh # ์ž…์–‘ ๋ฃจํ”„๋Š” ์ด ํ—ฌํผ๋ฅผ ์“ฐ๋ฉด ์•ˆ ๋œ๋‹ค (3aee63cf ยง1.2 ์‹ค์ธก ๋ฐ˜์ฆ). resolve_agent_type_from_registry() { local name="$1" MAM_STATE_JSON="$(load_state_json)" SESSION_NAME="$name" python3 -c " import os, json, sys from lib_py.agents.registry import agent_of_row name = os.environ['SESSION_NAME'] d = json.loads(os.environ.get('MAM_STATE_JSON', '{}')) row = next((s for s in d.get('herdr_sessions', []) if s.get('name') == name), {}) resolved = agent_of_row(row, session_name=name) if not resolved: sys.exit(1) print(resolved) " } ``` **์ด๋ฆ„์„ `resolve_agent_type` ๋กœ ํ•˜์ง€ ์•Š๋Š” ์ด์œ **: `run_loop.sh:278` ์ด ๋™๋ช… ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•˜๋ฉฐ `lib.sh` ๋ฅผ source ํ•ฉ๋‹ˆ๋‹ค. ๋™๋ช…์ด๋ฉด run_loop ์˜ ๋‚˜์ค‘ ์ •์˜๊ฐ€ ์กฐ์šฉํžˆ ๋ฎ์–ด์จ์„œ 12๊ฐœ ํ˜ธ์ถœ ์ง€์ ์ด ์–ด๋А ๊ตฌํ˜„์„ ์“ฐ๋Š”์ง€ ์ฝ์–ด์„œ๋Š” ์•Œ ์ˆ˜ ์—†๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ### 4.2 S2 โ€” `stop_session.sh` `:100-109` ๊ต์ฒด: ```bash # --agent ๋ฏธ์ง€์ • ์‹œ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๊ธฐ๋ก์œผ๋กœ ํ•ด์„ (B-21). # โ‘  row['agent'] โ†’ โ‘ก ์„ธ์…˜๋ช… ์ ‘๋ฏธ์‚ฌ โ†’ โ‘ข pane.cmd ์ˆœ. ์…‹ ๋‹ค ์‹คํŒจํ•˜๋ฉด # ์ข…์ „๊ณผ ๋™์ผํ•˜๊ฒŒ exit 2 (ํ—ค๋” :27-30 ์˜ ์ข…๋ฃŒ ์ฝ”๋“œ ๊ณ„์•ฝ ์œ ์ง€). if [ -z "$AGENT" ]; then AGENT="$(resolve_agent_type_from_registry "$SESSION_NAME")" || AGENT="" [ -n "$AGENT" ] || { echo "ERROR: cannot infer agent from '$SESSION_NAME'; pass --agent" >&2 exit 2 } fi ``` ํ—ค๋” `:15-16`: ``` # --agent โ€” claude | agy | hermes | cline # (๊ถŒ์žฅ: ํ•ญ์ƒ ๋ช…์‹œ. ๋ฏธ์ง€์ • ์‹œ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๊ธฐ๋ก์œผ๋กœ # ํ•ด์„ โ€” agent ํ•„๋“œ โ†’ ์„ธ์…˜๋ช… ์ ‘๋ฏธ์‚ฌ โ†’ pane.cmd; # ์…‹ ๋‹ค ์‹คํŒจํ•˜๋ฉด exit 2) ``` `usage()` `:46-47`: ``` --agent โ€” claude | agy | hermes | cline (recommended: always pass it) (falls back to the registry record: agent field -> session-name suffix -> pane.cmd) ``` `usage()` ์— 4๊ฐœ ์—์ด์ „ํŠธ๋ช…์ด ๋ชจ๋‘ ๋‚จ์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค โ€” `test_comp_stop_usage_matches_parser`(`test_tier2_component.py:711-712`)๊ฐ€ ๋‹จ์–ธํ•ฉ๋‹ˆ๋‹ค. ### 4.3 S3 โ€” `update_yaml_resumed.sh` `:43-52` ๋ฅผ S2 ์™€ ๋™์ผํ•œ ๋ธ”๋ก์œผ๋กœ ๊ต์ฒด(๋ฉ”์‹œ์ง€ยท์ข…๋ฃŒ ์ฝ”๋“œ ๋™์ผ). ํ—ค๋” `:7` / `usage()` `:14` ์˜ `[--agent claude|agy]` โ†’ `[--agent claude|agy|hermes|cline]`. `:10` ์€ ์ด๋ฏธ ์˜ฌ๋ฐ”๋ฅธ ์†Œ์‹ฑ ํ˜•ํƒœ์ด๋ฏ€๋กœ ์†๋Œ€์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ### 4.4 S4 โ€” ๋ฌธ์„œ ๋™๊ธฐํ™” **`multi-agent-mux-stop/SKILL.md`** Pre-flight(`:38-40`): ```bash SESSION_NAME=-creator- # convention AGENT=claude # claude | agy | hermes | cline โ€” always pass it AGENT_SESSIONS_YAML=.mam/agent-sessions.yaml ``` ์›Œํฌํ”Œ๋กœ ์˜ˆ์ œ 3๊ฐœ(`:68, :72, :77`)์— `--agent "$AGENT"` ์ถ”๊ฐ€: ```bash # 1. Stop gracefully (default โ€” captures ID, shuts down safely, status=stopped) bash .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \ --session "$SESSION_NAME" --agent "$AGENT" ``` "Idempotency" ๋ฌธ๋‹จ(`:81`) ์•„๋ž˜์— ์ถ”๊ฐ€: ```markdown **`--agent` is the standard.** Pass it on every invocation. If omitted, the script resolves the agent from the registry record โ€” the row's `agent` field, then the session-name suffix, then `pane.cmd` โ€” and exits 2 if none of the three resolve. The fallback exists for recovery, not as the normal calling convention: a session whose name carries no agent suffix (e.g. `agy-creator-01`) is only resolvable while its registry row survives. ``` > **Rev.1 ์— ์žˆ๋˜ ์ œ์•ฝ ์‚ญ์ œ.** Rev.1 ์€ ์ด ์‚ฐ๋ฌธ์— `stop_session.sh` ๋ฌธ์ž์—ด์„ ์“ฐ์ง€ ๋ง๋ผ๋Š” ์ œ์•ฝ์„ ๊ฑธ์–ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. Rev.2 ์˜ T5 ๊ฐ€ ํŽœ์Šค ์Šค์ฝ”ํ”„์ด๋ฏ€๋กœ **๊ทธ ์ œ์•ฝ์ด ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค**(ยง1.10.3~4). ์‚ฐ๋ฌธ์„ ์ž์œ ๋กญ๊ฒŒ ์“ฐ์‹ญ์‹œ์˜ค. **`multi-agent-mux-resume/SKILL.md:61`** โ€” `AGENT=claude # or agy or hermes` โ†’ `AGENT=claude # claude | agy | hermes | cline โ€” pass it explicitly` **`multi-agent-mux-create/SKILL.md`** - `:146` ๋™์ผ ์ˆ˜์ • - `:171` โ€” ์‹ค๋ฌผ `create_session.sh:86` ๊ณผ ๋™์ผํ•œ `claude, agy, hermes or cline` ๋ฌธ๊ตฌ๋กœ. ๊ฐ™์€ `case`(`:158-172`)์— `hermes`/`cline` arm ์ด ์—†์œผ๋ฏ€๋กœ, **์Šค๋‹ˆํŽซ์„ ์ถ•์•ฝํ•˜๊ณ  ์‹ค๋ฌผ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๊ฐ€๋ฆฌํ‚ค๊ฒŒ ํ•˜๋Š” ์ชฝ์„ ๊ถŒ์žฅ**ํ•ฉ๋‹ˆ๋‹ค. SKILL.md ์Šค๋‹ˆํŽซ์ด ์‹ค๋ฌผ๊ณผ ๊ฐˆ๋ผ์ง€๋Š” ๊ฒƒ ์ž์ฒด๊ฐ€ ์ด๋ฒˆ์— ๊ณ ์น˜๋Š” ๊ฒฐํ•จ๊ตฐ์ž…๋‹ˆ๋‹ค. **์Šคํฌ๋ฆฝํŠธ ํ—ค๋” ์ฃผ์„** โ€” `create_session.sh:4`, `resolve_session_id.sh:4` ์˜ `--agent ` โ†’ `` ### 4.5 S5 โ€” J-1 (+ C-2) `layout.py:175-186`: ```python def _env_int(*names: str, default: Optional[int] = None) -> Optional[int]: """First *valid* int among the env vars in *names*, else `default`. `default` is an explicit parameter rather than an `or` at the call site so a legitimate 0 survives (MAM_MIN_PANE_COLS=0 means 0, not the 60 default). An unparsable value is skipped rather than raised or treated as terminal: a typo in an operator's shell must not take the whole layout call down (lib.sh would silently fall back to 'right'), and must not shadow a later candidate that IS set correctly -- MAM_MIN_COLS is a legacy alias while MAM_MIN_PANE_COLS is the name .mam.env.example documents, so aborting on the first bad value would discard the documented setting. Empty values already fell through; this makes invalid values behave the same way. """ for n in names: raw = os.environ.get(n, "").strip() if raw: try: return int(raw) except ValueError: continue return default ``` `main()` `:191-193`: ```python parser.add_argument("--min-cols", type=int, default=_env_int("MAM_MIN_COLS", "MAM_MIN_PANE_COLS", default=60)) parser.add_argument("--min-rows", type=int, default=_env_int("MAM_MIN_ROWS", "MAM_MIN_PANE_ROWS", default=20)) parser.add_argument("--max-cols", type=int, default=_env_int("MAM_MAX_COLS", "MAM_MAX_PANE_COLS")) ``` `--max-cols` ๋Š” `default=None` ์ด ์˜๋„๋œ ์˜๋ฏธ(๋ฏธ์ง€์ • = ์ƒํ•œ ์—†์Œ)์ด๋ฏ€๋กœ ๊ทธ๋Œ€๋กœ ๋‘ก๋‹ˆ๋‹ค. **`return default` ๊ฐ€ ์•„๋‹ˆ๋ผ `continue` ์—ฌ์•ผ ํ•˜๋Š” ์ด์œ (๋ถˆ๋ณ€์‹ ํ™•์ธ)**: ๋ชจ๋“  ํ›„๋ณด๊ฐ€ ์—†๊ฑฐ๋‚˜ ๋ฌดํšจ์ด๋ฉด ๋ฃจํ”„๊ฐ€ ๋๋‚˜ `return default` ์— ๋„๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰ Rev.1 ์ด ์ง€ํ‚ค๋ ค๋˜ "์˜คํƒ€ ์ž…๋ ฅ์€ ๋ฌธ์„œํ™”๋œ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ํก์ˆ˜๋œ๋‹ค"๋Š” ์„ฑ์งˆ์€ **๊ทธ๋Œ€๋กœ ์œ ์ง€**๋˜๋ฉฐ(ยง1.11.2 ํ‘œ 4ยท7ํ–‰), ๋‹ฌ๋ผ์ง€๋Š” ๊ฒƒ์€ "์ฒซ ํ›„๋ณด ๋ฌดํšจ + ํ›„์† ํ›„๋ณด ์œ ํšจ" ํ•œ ์กฐ๊ฑด๋ฟ์ž…๋‹ˆ๋‹ค. `min_cols=None` ์œผ๋กœ `compute_2xk_layout` ์— ๋“ค์–ด๊ฐ€ `TypeError` ๊ฐ€ ๋‚˜๋Š” ๊ฒฝ๋กœ๋Š” ๋‘ ์•ˆ ๋ชจ๋‘์—์„œ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. `*names` ๋’ค์˜ ํ‚ค์›Œ๋“œ ์ „์šฉ `default` ๋Š” Python 3.9 ์—์„œ ์œ ํšจํ•ฉ๋‹ˆ๋‹ค(์‹œ์Šคํ…œ ์ธํ„ฐํ”„๋ฆฌํ„ฐ 3.9.6 ์‹ค์ธก). `_env_int` ํ˜ธ์ถœ์ž๋Š” `main()` 3๊ณณ๋ฟ์ž…๋‹ˆ๋‹ค. --- ## 5. ํ…Œ์ŠคํŠธ ๊ณ„ํš ์‹ ์„ค **8๊ฑด**, ๊ธฐ์กด ๊ฐ€๋“œ ๋ณด๊ฐ• **1๊ฑด**. ์˜ˆ์ƒ collected: **333 โ†’ 341**. > **Rev.1 ์ž์ฒด ์ •์ •**: Rev.1 ์€ "์‹ ์„ค 8๊ฑด โ†’ 341" ์ด๋ผ๊ณ  ์ ์—ˆ์œผ๋‚˜ ์‹ค์ œ ์—ด๊ฑฐ๋Š” 7๊ฑด์ด์—ˆ์Šต๋‹ˆ๋‹ค(T1 3 + T3 1 + T4 2 + T5 1). Rev.2 ๋Š” C-2 ์ „์šฉ ํ…Œ์ŠคํŠธ T1b ๋ฅผ ๋”ํ•ด ์‹ค์ œ๋กœ 8๊ฑด์ด ๋˜๋ฉฐ, 341 ์ด๋ผ๋Š” ์ˆ˜์น˜๊ฐ€ ๋น„๋กœ์†Œ ๋งž์•„๋–จ์–ด์ง‘๋‹ˆ๋‹ค. ### T1 โ€” J-1 env/flag ๋“ฑ๊ฐ€์„ฑ (`tests/test_layout.py`, 3๊ฑด) ```python _LAYOUT_ENV_VARS = ("MAM_MIN_COLS", "MAM_MIN_PANE_COLS", "MAM_MIN_ROWS", "MAM_MIN_PANE_ROWS", "MAM_MAX_COLS", "MAM_MAX_PANE_COLS") def _run_layout(payload, args=(), env_extra=None): env = {**os.environ, "PYTHONPATH": os.path.abspath(".agents/skills")} for k in _LAYOUT_ENV_VARS: env.pop(k, None) # ํ˜ธ์ถœ์ž ์…ธ์˜ ์˜ค์—ผ ์ฐจ๋‹จ env.update(env_extra or {}) res = subprocess.run([sys.executable, "-m", "lib_py.layout", "--json", *args], input=json.dumps(payload), capture_output=True, text=True, env=env) assert res.returncode == 0, res.stderr return json.loads(res.stdout) # height//2 = 15 < min_rows(20) ๋กœ ์ œ์•ฝ ๋ถ„๊ธฐ ์ง„์ž…, width//2 = 25 ๊ฐ€ min_cols ์™€ ๋น„๊ต๋จ. _ZERO_TRAP = {"result": {"panes": [ {"pane_id": "p1", "rect": {"x": 0, "y": 0, "width": 50, "height": 30}}]}} def test_j1_env_zero_min_cols_matches_flag_zero(): """J-1: MAM_MIN_PANE_COLS=0 must mean 0, not fall through to the 60 default.""" flag = _run_layout(_ZERO_TRAP, ("--min-cols", "0")) assert flag["direction"] == "right" and flag["reason"] == "single_pane_height_constrained" for var in ("MAM_MIN_COLS", "MAM_MIN_PANE_COLS"): assert _run_layout(_ZERO_TRAP, (), {var: "0"}) == flag, var def test_j1_env_zero_min_rows_matches_flag_zero(): flag = _run_layout(_ZERO_TRAP, ("--min-rows", "0")) assert flag["direction"] == "down" and flag["reason"] == "single_pane_split_down" for var in ("MAM_MIN_ROWS", "MAM_MIN_PANE_ROWS"): assert _run_layout(_ZERO_TRAP, (), {var: "0"}) == flag, var def test_j1_nonzero_and_malformed_env_behaviour_unchanged(): """Behaviour neutrality: non-zero env still applies, and a lone typo still lands on the documented default instead of crashing on a None comparison.""" assert _run_layout(_ZERO_TRAP, (), {"MAM_MIN_PANE_COLS": "25"}) == \ _run_layout(_ZERO_TRAP, ("--min-cols", "25")) assert _run_layout(_ZERO_TRAP, (), {"MAM_MIN_PANE_COLS": "abc"}) == _run_layout(_ZERO_TRAP) ``` ### T1b โ€” **[Rev.2 ์‹ ๊ทœ]** C-2: ๋ฌดํšจ๊ฐ’์ด ๋’ค ํ›„๋ณด๋ฅผ ๊ฐ€๋ฆฌ์ง€ ์•Š๋Š”๋‹ค (1๊ฑด) ```python def test_j1b_invalid_alias_does_not_shadow_the_documented_var(): """C-2: MAM_MIN_COLS is a legacy alias checked first; MAM_MIN_PANE_COLS is the name .mam.env.example documents. An unparsable value in the alias must be skipped, not abort the search and discard the documented setting. Empty values already fell through (`if raw:`); this makes invalid values behave the same way. When every candidate is unusable, `default` still wins. """ good = _run_layout(_ZERO_TRAP, (), {"MAM_MIN_PANE_COLS": "25"}) assert good["direction"] == "right" # ๋ณ„์นญ์ด ๊นจ์ ธ ์žˆ์–ด๋„ ๋ฌธ์„œํ™”๋œ ๋ณ€์ˆ˜๊ฐ€ ์ ์šฉ๋œ๋‹ค assert _run_layout(_ZERO_TRAP, (), {"MAM_MIN_COLS": "foo", "MAM_MIN_PANE_COLS": "25"}) == good # 0 ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€ (J-1 ๊ณผ์˜ ์ƒํ˜ธ์ž‘์šฉ) assert _run_layout(_ZERO_TRAP, (), {"MAM_MIN_COLS": "foo", "MAM_MIN_PANE_COLS": "0"}) == \ _run_layout(_ZERO_TRAP, ("--min-cols", "0")) # ๋ชจ๋“  ํ›„๋ณด๊ฐ€ ๋ฌดํšจ๋ฉด ๋ฌธ์„œํ™”๋œ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ํก์ˆ˜ (Rev.1 ๋ถˆ๋ณ€์‹ ๋ณด์กด) assert _run_layout(_ZERO_TRAP, (), {"MAM_MIN_COLS": "foo", "MAM_MIN_PANE_COLS": "bar"}) == _run_layout(_ZERO_TRAP) ``` ๋งˆ์ง€๋ง‰ ๋‹จ์–ธ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค โ€” `continue` ๋กœ ๋ฐ”๊พธ๋ฉด์„œ Rev.1 ์ด ์ง€ํ‚ค๋ ค๋˜ ์„ฑ์งˆ์ด ๊นจ์ง€์ง€ ์•Š์•˜์Œ์„ ๊ฐ™์€ ํ…Œ์ŠคํŠธ ์•ˆ์—์„œ ๋ชป ๋ฐ•์Šต๋‹ˆ๋‹ค. ### T2 โ€” J-2 ์ž„๊ณ„๊ฐ’ ๋ณด๊ฐ• (๊ธฐ์กด `test_headless_max_columns_growth_guard` ํ™•์žฅ, ์‹ ์„ค 0๊ฑด) ```python # n=5 is the first odd n that can discriminate: n//2 == 2 == max_columns, so an # over-correction that also checked the cap on the odd branch would return # overflow here. n=3 has n//2 == 1 and cannot reach the check at all. d5 = compute_2xk_layout(headless(5), max_columns=2) assert d5.direction == "down" and not d5.is_overflow assert d5.reason == "headless_odd_down" ``` ๊ณ„ํš `5e4ef463` ์˜ ๋ฎคํ…Œ์ด์…˜ M6 ์‚ฌ์–‘ ์˜ค๋ฅ˜(์ œ๊ฐ€ `n=3` ์„ ๊ณจ๋ž๊ณ  ๊ทธ ๊ฐ’์œผ๋กœ๋Š” ํŒ๋ณ„ ๋ถˆ๊ฐ€)๋ฅผ ๋‹ซ์Šต๋‹ˆ๋‹ค. ### T3 โ€” `agent_of_row` ๋‹จ์œ„ ๋ณด๊ฐ• (`tests/test_a4_adapter_contract.py`, 1๊ฑด) ```python def test_agent_of_row_pane_cmd_binary_path_and_failure(): # pane.cmd ๊ฐ€ ์ ˆ๋Œ€ ๊ฒฝ๋กœ ํ˜•ํƒœ์—ฌ๋„ ํ•ด์„๋œ๋‹ค assert agent_of_row({'pane': {'cmd': '/usr/local/bin/agy'}}) == 'agy' # ์„ธ ๊ฒฝ๋กœ ๋ชจ๋‘ ์‹คํŒจํ•˜๋ฉด None โ€” ํ˜ธ์ถœ์ž๊ฐ€ ์˜ค๋ฅ˜๋ฅผ ์†Œ์œ ํ•œ๋‹ค assert agent_of_row({}, session_name='bad-session-name') is None # ์ž…์–‘ ์กฐํšŒ์šฉ match_cmd=False ์—์„œ๋Š” pane.cmd ๋ฅผ ๋ณด์ง€ ์•Š๋Š”๋‹ค assert agent_of_row({'name': 'agy-creator-01', 'pane': {'cmd': 'agy'}}, match_cmd=False) is None ``` ### T4 โ€” `stop_session.sh` ํด๋ฐฑ (`tests/test_tier2_component.py`, 2๊ฑด) ๊ธฐ์กด `test_comp_stop_sqlite_state_update` ์˜ `run_mutation` ํŒจํ„ด ์‚ฌ์šฉ(herdr ๋ถ€์žฌ โ†’ "herdr already dead, just updating YAML" ๊ฒฝ๋กœ๋กœ rc=0 ์™„์ฃผ, ์‹ค์ œ ์„ธ์…˜ ๋ฏธ์˜ํ–ฅ). ```python def test_comp_stop_agent_fallback_reads_pane_cmd(mam_sandbox): """B-21: --agent ์ƒ๋žต ์‹œ ์„ธ์…˜๋ช…์— ์—์ด์ „ํŠธ ์ ‘๋ฏธ์‚ฌ๊ฐ€ ์—†์–ด๋„ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ํ–‰์˜ pane.cmd ๋กœ ํ•ด์„๋œ๋‹ค (๋ผ์ด๋ธŒ `agy-creator-01` ํ˜•ํƒœ).""" mutation = """ d['herdr_sessions'] = [{ 'name': 'agy-creator-01', 'status': 'running', 'pane': {'cwd': 'WS_PLACEHOLDER', 'cmd': 'agy'} }] """.replace("WS_PLACEHOLDER", str(mam_sandbox)) run_mutation(mam_sandbox, mutation) script = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-stop" / "scripts" / "stop_session.sh" res = subprocess.run(["bash", str(script), "--session", "agy-creator-01"], capture_output=True, text=True) assert res.returncode == 0, res.stderr assert re.search(r"^\s*agent:\s+agy\s*$", res.stdout, re.M), res.stdout def test_comp_stop_agent_fallback_prefers_explicit_agent_field(mam_sandbox): """์šฐ์„ ์ˆœ์œ„ ๊ณ„์•ฝ: ๋ช…์‹œ `agent` ํ•„๋“œ๊ฐ€ ์„ธ์…˜๋ช… ์ ‘๋ฏธ์‚ฌ์™€ pane.cmd ๋ฅผ ๋ชจ๋‘ ์ด๊ธด๋‹ค.""" mutation = """ d['herdr_sessions'] = [{ 'name': 'x-creator-claude', 'status': 'running', 'agent': 'hermes', 'pane': {'cwd': 'WS_PLACEHOLDER', 'cmd': 'claude'} }] """.replace("WS_PLACEHOLDER", str(mam_sandbox)) run_mutation(mam_sandbox, mutation) script = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-stop" / "scripts" / "stop_session.sh" res = subprocess.run(["bash", str(script), "--session", "x-creator-claude"], capture_output=True, text=True) assert res.returncode == 0, res.stderr assert re.search(r"^\s*agent:\s+hermes\s*$", res.stdout, re.M), res.stdout ``` ๋‘ ๋ฒˆ์งธ๊ฐ€ T4 ๋ฅผ "pane.cmd ๋ฅผ ์ฝ๋Š”๋‹ค" ๊ฐ€ ์•„๋‹ˆ๋ผ **"`agent_of_row` ๊ณ„์•ฝ์„ ํ˜ธ์ถœํ•œ๋‹ค"** ๋กœ ๊ณ ์ •ํ•ฉ๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ๋งŒ ์žˆ์œผ๋ฉด `pane.cmd` ๋งŒ ์ง์ ‘ ์ฝ๋Š” ์–•์€ ๊ตฌํ˜„๋„ ํ†ต๊ณผํ•ฉ๋‹ˆ๋‹ค. **๋ฏธํ•ด๊ฒฐ ๊ณ„์•ฝ(exit 2)** ์€ ์ด๋ฏธ `test_tier1_unit.py:142` ์™€ `test_tier3_integration.py:398` ์ด ์ง€ํ‚ต๋‹ˆ๋‹ค. ๋‘ ํŒŒ์ผ์„ **์ˆ˜์ •ํ•˜์ง€ ์•Š์€ ์ฑ„ ํ†ต๊ณผํ•˜๋Š” ๊ฒƒ**์ด ์•ˆ B ์˜ ์ฆ๊ฑฐ์ด๋ฏ€๋กœ ์ค‘๋ณต ํ…Œ์ŠคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ### T5 โ€” **[Rev.2 ์žฌ์„ค๊ณ„]** ๋ฌธ์„œ ๊ฐ€๋“œ (`tests/test_tier2_component.py`, 1๊ฑด) Rev.1 ์›์•ˆ์€ ยง1.10.1 ์˜ ์‹ค์ธก๋Œ€๋กœ `checked == 2` ๋กœ ํ™•์ • ์‹คํŒจํ•˜๊ณ , ยง1.10.2 ๋Œ€๋กœ ๋‹จ์ผ ํ˜ธ์ถœ ํšŒ๊ท€๋ฅผ ๋†“์นฉ๋‹ˆ๋‹ค. ์ฑŒ๋ฆฐ์ €์˜ **์ปค๋งจ๋“œ ๋‹จ์œ„** ๊ต์ •์„ ์ฑ„ํƒํ•˜๋˜, ยง1.10.3 ์˜ ์‚ฐ๋ฌธ ์œ„์–‘์„ฑ์„ ๋ง‰๊ธฐ ์œ„ํ•ด **ํŽœ์Šค ์Šค์ฝ”ํ”„**๋ฅผ ํ•ฉ์„ฑํ•ฉ๋‹ˆ๋‹ค. ```python # ์ฝ”๋“œ ํŽœ์Šค ์•ˆ์˜ stop_session.sh ํ˜ธ์ถœ์„ '๋ช…๋ น ๋‹จ์œ„'๋กœ ์ž˜๋ผ๋‚ธ๋‹ค. # - ํŽœ์Šค ์Šค์ฝ”ํ”„: ์‚ฐ๋ฌธ ์† `stop_session.sh` ์–ธ๊ธ‰์„ ๋ช…๋ น์œผ๋กœ ์˜ค์ธํ•˜์ง€ ์•Š๋Š”๋‹ค # (Pitfalls / When-NOT-to-use ์ ˆ์€ ์„ฑ๊ฒฉ์ƒ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์‚ฐ๋ฌธ์œผ๋กœ ์–ธ๊ธ‰ํ•œ๋‹ค). # - ๋ช…๋ น ๋‹จ์œ„: ํ•œ ํŽœ์Šค์— ์—ฌ๋Ÿฌ ํ˜ธ์ถœ์ด ๋“ค์–ด ์žˆ์–ด๋„ ๊ฐ๊ฐ์„ ๋”ฐ๋กœ ๊ฒ€์ฆํ•œ๋‹ค # (๋ธ”๋ก ๋‹จ์œ„๋กœ ๋ณด๋ฉด ๊ทธ์ค‘ ํ•˜๋‚˜๋งŒ --agent ๋ฅผ ๊ฐ€์ ธ๋„ ํ†ต๊ณผํ•ด ๋ฒ„๋ฆฐ๋‹ค). _FENCE_RE = re.compile(r"```(?:bash|sh)\n(.*?)```", re.S) _STOP_CALL_RE = re.compile(r"(?:bash\s+)?\S*stop_session\.sh[^\n\\]*(?:\\\n[^\n\\]*)*") def test_comp_docs_stop_examples_pass_agent(): """B-21 ๋ฌธ์„œ ๊ณ„์•ฝ: ๋ฌธ์„œ์˜ ๋ชจ๋“  stop_session.sh ์˜ˆ์ œ๋Š” --agent ๋ฅผ ๋„˜๊ธด๋‹ค. ๋ฌธ์„œ ๋ณ€๊ฒฝ์€ ๋ฎคํ…Œ์ด์…˜ ๊ฐ๋„๊ฐ€ ์—†์œผ๋ฏ€๋กœ ์ด ๊ฐ€๋“œ๊ฐ€ ํ‘œ์ค€์˜ ์œ ์ผํ•œ ์ง‘ํ–‰ ์žฅ์น˜๋‹ค.""" repo = Path(__file__).resolve().parent.parent expected = { # ๋ฌธ์„œ๋ณ„ ์ตœ์†Œ ์˜ˆ์ œ ์ˆ˜ โ€” ์˜ˆ์ œ๋ฅผ ์ง€์›Œ ๊ฐ€๋“œ๋ฅผ ๋ฌด๋ ฅํ™”ํ•˜๋Š” ๊ฒƒ์„ ๋ง‰๋Š”๋‹ค repo / ".agents/skills/multi-agent-mux-stop/SKILL.md": 3, repo / "deploy/INSTALL.md": 2, } for doc, floor in expected.items(): seen = 0 for block in _FENCE_RE.findall(doc.read_text()): for m in _STOP_CALL_RE.finditer(block): snippet = m.group(0) seen += 1 assert "--agent" in snippet, \ f"{doc.name}: stop_session.sh example without --agent:\n{snippet}" assert seen >= floor, f"{doc.name}: expected >= {floor} examples, saw {seen}" ``` Rev.1/์ฑŒ๋ฆฐ์ €์•ˆ ๋Œ€๋น„ ์„ธ ๊ฐ€์ง€๊ฐ€ ๋‹ค๋ฆ…๋‹ˆ๋‹ค. | | Rev.1 ์›์•ˆ | ์ฑŒ๋ฆฐ์ € ์ˆ˜์ •์•ˆ | **Rev.2** | |---|---|---|---| | ๊ฒ€์ฆ ๋‹จ์œ„ | ์ฝ”๋“œ ๋ธ”๋ก | ๋ช…๋ น | ๋ช…๋ น | | ์Šค์บ” ๋ฒ”์œ„ | ํŽœ์Šค | **๋ฌธ์„œ ์ „์ฒด** | ํŽœ์Šค | | ๊ฐœ์ˆ˜ ํ•˜ํ•œ | ์ „์—ญ `>= 4` (**์‹คํŒจ**) | ์ „์—ญ `>= 5` | **๋ฌธ์„œ๋ณ„** (3 / 2) | ์ „์—ญ ์นด์šดํŠธ๋ฅผ ๋ฌธ์„œ๋ณ„๋กœ ์ชผ๊ฐ  ์ด์œ : ์ „์—ญ์ด๋ฉด SKILL.md ์˜ˆ์ œ 1๊ฐœ๊ฐ€ ์‚ฌ๋ผ์ ธ๋„ INSTALL.md ๊ฐ€ 6๊ฐœ๋ฉด ํ†ต๊ณผํ•ฉ๋‹ˆ๋‹ค. ๋ฌธ์„œ๋ณ„ ํ•˜ํ•œ์€ ์‹คํŒจ๋ฅผ ๋ฐœ์ƒ ์ง€์ ์— ๊ตญ์†Œํ™”ํ•ฉ๋‹ˆ๋‹ค. **์‹ค์ธก ํ™•์ธ** (ยง1.10.4): ยง4.4 ์ ์šฉ ํ›„ ์‚ฌ๋ณธ์—์„œ `SKILL.md checked=3 missing=0`, `INSTALL.md checked=2 missing=0`. ### T6 โ€” ํšŒ๊ท€ ๋ฌด์˜ํ–ฅ ํ™•์ธ `bash -n`: `lib.sh`, `stop_session.sh`, `update_yaml_resumed.sh`, `create_session.sh`, `resume_session.sh`, `resolve_session_id.sh`. `py_compile`: `lib_py/layout.py`. ์‹œ์Šคํ…œ ํŒŒ์ด์ฌ **3.9.6** ์ž„ํฌํŠธ ํ™•์ธ. ### ํ…Œ์ŠคํŠธ ํŒŒ์ผ ์‚ฌ์ „ ์กฐ๊ฑด 2๊ฑด 1. `test_tier2_component.py:296` ์˜ `FEATURE 3: Stop Session (4 Test Cases)` ์ฃผ์„ ๊ฐœ์ˆ˜ ๊ฐฑ์‹ (โ†’ 7). ๊ฐ™์€ ์ข…๋ฅ˜์˜ ๋“œ๋ฆฌํ”„ํŠธ๋ฅผ ์ƒˆ๋กœ ๋งŒ๋“ค์ง€ ์•Š๋„๋ก. 2. `test_tier2_component.py` ๋Š” `Path` ๋Š” ์ž„ํฌํŠธํ•˜์ง€๋งŒ **`re` ๋Š” ์ž„ํฌํŠธํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค**(`:1-10`). T4/T5 ๊ฐ€ `re` ๋ฅผ ์“ฐ๋ฏ€๋กœ `import re` ์ถ”๊ฐ€ ํ•„์š”. `test_layout.py` ๋Š” T1/T1b ๊ฐ€ ์“ฐ๋Š” `os/json/subprocess/sys` ๋ฅผ ๋ชจ๋‘ ์ด๋ฏธ ์ž„ํฌํŠธํ•˜๊ณ  ์žˆ์–ด ์ถ”๊ฐ€ ๋ถˆํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. --- ## 6. ๋ฎคํ…Œ์ด์…˜ ๋งคํŠธ๋ฆญ์Šค ๊ฒฉ๋ฆฌ ์‚ฌ๋ณธ(`rsync`)์— ์ ์šฉํ•ด ์ง€์ • ํ…Œ์ŠคํŠธ๊ฐ€ **FAIL** ํ•˜๋Š”์ง€ ํ™•์ธ. | # | ๋ฎคํ…Œ์ด์…˜ | FAIL ํ•ด์•ผ ํ•˜๋Š” ํ…Œ์ŠคํŠธ | |---|---|---| | M1 | `stop_session.sh` ํด๋ฐฑ์„ ์˜› `case` ๋ธ”๋ก์œผ๋กœ ๋ณต์› | `test_comp_stop_agent_fallback_reads_pane_cmd` | | M2 | ํ—ฌํผ์—์„œ `agent_of_row(row, โ€ฆ)` โ†’ `agent_of_row({}, session_name=name)` | ์œ„ + `โ€ฆprefers_explicit_agent_field` | | M3 | ํ—ฌํผ์— `match_cmd=False` ์ถ”๊ฐ€ | `โ€ฆreads_pane_cmd` **๋งŒ** (๋‘ ํ…Œ์ŠคํŠธ๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ ์„ฑ์งˆ์„ ์žก์Œ์„ ์ฆ๋ช…) | | M4 | `_env_int(โ€ฆ, default=60)` โ†’ `_env_int(โ€ฆ) or 60` | `test_j1_env_zero_min_cols_matches_flag_zero` | | M5 | `_env_int` ์˜ `except ValueError: continue` โ†’ `return None` | `test_j1_nonzero_and_malformed_env_behaviour_unchanged` (rcโ‰ 0) | | **M5b** | **[Rev.2]** `except ValueError: continue` โ†’ `return default` | `test_j1b_invalid_alias_does_not_shadow_the_documented_var` | | M6 | ํ—ค๋“œ๋ฆฌ์Šค ํ™€์ˆ˜ ๋ถ„๊ธฐ์—๋„ `max_columns` ๊ฒ€์‚ฌ ์ถ”๊ฐ€ (๊ณผ์ž‰ ๊ต์ •) | `test_headless_max_columns_growth_guard` (์‹ ์„ค `d5` ๋‹จ์–ธ) | | M7 | `SKILL.md` ์˜ˆ์ œ **ํ•œ ๊ณณ**์—์„œ `--agent` ์‚ญ์ œ | `test_comp_docs_stop_examples_pass_agent` | | **M7b** | **[Rev.2]** `INSTALL.md` ์˜ **๋‘ ํ˜ธ์ถœ ์ค‘ ํ•˜๋‚˜**์—์„œ๋งŒ `--agent` ์‚ญ์ œ | ๋™์ผ (ยง1.10.2 ์—์„œ ์ด๋ฏธ ์„ ์‹ค์ธก: Rev.1 ์„ค๊ณ„๋Š” ๋ฏธ๊ฒ€์ถœ, Rev.2 ์„ค๊ณ„๋Š” `missing=1` ๊ฒ€์ถœ) | | **M7c** | **[Rev.2]** `SKILL.md` ์›Œํฌํ”Œ๋กœ ์˜ˆ์ œ 1๊ฐœ๋ฅผ ํ†ต์งธ๋กœ ์‚ญ์ œ | ๋™์ผ (`seen >= 3` ํ•˜ํ•œ) | | M8 | `update_yaml_resumed.sh` ํด๋ฐฑ์„ ์˜› `case` ๋ธ”๋ก์œผ๋กœ ๋ณต์› | โ€” **๊ฐ€๋“œ ์—†์Œ** | **M8 ์„ ์ •์งํ•˜๊ฒŒ ๋‚จ๊น๋‹ˆ๋‹ค.** `update_yaml_resumed.sh` ์˜ ํด๋ฐฑ์€ ์œ ์ผํ•œ ์ƒ์‚ฐ ํ˜ธ์ถœ์ž์ธ `resume_session.sh:66, :129` ๊ฐ€ ํ•ญ์ƒ `--agent "$AGENT"` ๋ฅผ ๋ช…์‹œํ•˜๋ฏ€๋กœ **๊ทธ ๊ฒฝ๋กœ์—์„œ ๋„๋‹ฌ ๋ถˆ๊ฐ€**ํ•ฉ๋‹ˆ๋‹ค. ์ง์ ‘ ํ˜ธ์ถœ ์‹œ์—๋งŒ ์‚ด์•„๋‚ฉ๋‹ˆ๋‹ค. ๋„๋‹ฌ ๋ถˆ๊ฐ€ ๊ฒฝ๋กœ๋ฅผ ์œ„ํ•ด ๋ณ„๋„ ํ”ฝ์Šค์ฒ˜๋ฅผ ์„ธ์šฐ๋Š” ๋Œ€์‹  S3 ๋Š” "์ค‘๋ณต ์ œ๊ฑฐ"๋กœ ์ •๋‹นํ™”ํ•˜๊ณ  ๊ฐ€๋“œ ์—†์Œ์„ ๋ช…์‹œํ•ฉ๋‹ˆ๋‹ค. ๋ฆฌ๋ทฐ์–ด๊ฐ€ ์ด ํŒ๋‹จ์— ์ด์˜๊ฐ€ ์žˆ์œผ๋ฉด T4 ์™€ ๋™ํ˜•์˜ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€๊ฐ€ ์˜ณ์€ ์ฒ˜๋ฐฉ์ž…๋‹ˆ๋‹ค. **M5 ์™€ M5b ๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ ํ…Œ์ŠคํŠธ๋ฅผ ๊นจ๋Š” ๊ฒƒ**์ด C-2 ๋ฐ˜์˜์˜ ๊ฒ€์ฆ ์กฐ๊ฑด์ž…๋‹ˆ๋‹ค. M5(=`None` ๋ณต๊ท€)๋Š” ํฌ๋ž˜์‹œ ๊ฒฝ๋กœ๋ฅผ, M5b(=Rev.1 ์•ˆ์œผ๋กœ ๋ณต๊ท€)๋Š” ๋ณ„์นญ ์„€๋„์ž‰์„ ๊ฐ๊ฐ ์žก์Šต๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค ์žกํžˆ์ง€ ์•Š์œผ๋ฉด T1b ๊ฐ€ ์˜๋ฏธ ์—†๋Š” ํ…Œ์ŠคํŠธ๋ผ๋Š” ๋œป์ž…๋‹ˆ๋‹ค. --- ## 7. ์ปค๋ฐ‹ ๋ถ„ํ•  | # | ์ปค๋ฐ‹ | ๋‚ด์šฉ | |---|---|---| | 1 | `feat(lib,stop,resume): resolve --agent from the registry via agent_of_row (B-21)` | S1 + S2 + S3 + T3 + T4 | | 2 | `docs(skills): standardize explicit --agent across stop/resume/create guides (B-21)` | S4 + T5 | | 3 | `fix(layout): make _env_int take an explicit default and skip invalid values (J-1)` | S5 + T1 + T1b | | 4 | `test(layout): cover the headless growth-guard threshold at n=5 (J-2)` | T2 | | 5 | `docs(improvements): register J-1/J-2/B-21 and refresh the completed count` | S7 | | 6 | `fix(scripts): repair the dead lib.sh sourcing path in stop/create/resume` | S8 (ยง1.9) | ์ปค๋ฐ‹ 1~4 ๋Š” ๊ฐ๊ฐ ๋…๋ฆฝ revert ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ์ปค๋ฐ‹ 6 ์€ ยง1.9 ๊ฐ€ ๋ธŒ๋ฆฌํ”„ ๋ฒ”์œ„ ๋ฐ–์˜ ๋ณ„๊ฐœ ์‚ฌ์•ˆ์ด๋ฏ€๋กœ ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค โ€” ๋ฆฌ๋ทฐ์–ด๊ฐ€ ๋ฒ”์œ„ ์ดํƒˆ๋กœ ํŒ๋‹จํ•˜๋ฉด ์ด ์ปค๋ฐ‹๋งŒ ๋“œ๋กญํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. ์ปค๋ฐ‹ 3 ์˜ ์ œ๋ชฉ์ด Rev.1 ์—์„œ ๋ฐ”๋€Œ์—ˆ์Šต๋‹ˆ๋‹ค(`โ€ฆ and skip invalid values` ์ถ”๊ฐ€). C-2 ๊ฐ€ J-1 ๊ณผ ๋‹ค๋ฅธ ์„ฑ์งˆ์˜ ๋ณ€๊ฒฝ์ด๋ฏ€๋กœ ์ œ๋ชฉ์ด ๊ทธ ์‚ฌ์‹ค์„ ๋‹ด์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. --- ## 8. `IMPROVEMENTS.md` ๊ฐฑ์‹  (S7) J-1 / J-2 ๋Š” ํ˜„์žฌ `IMPROVEMENTS.md` ์— **๋“ฑ๋ก๋ผ ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค**(`55d1a1d9` ๋ฆฌ๋ทฐ ๋ณด๊ณ ์„œ์—๋งŒ ์กด์žฌ). **ID ์ถฉ๋Œ ๊ฒฝ๊ณ **: `IMPROVEMENTS.md:301` ์˜ `C-1`("Kanban ๋ฌธ์„œ 29ํšŒ ์–ธ๊ธ‰ vs ์‹ค์ œ ๊ตฌํ˜„ 0๊ฑด")๊ณผ `:37` ์ด ์ฐธ์กฐํ•˜๋Š” `C-1`(๋ ˆ์ด์•„์›ƒ ํ—ค๋“œ๋ฆฌ์Šค `max_columns`)์€ **์„œ๋กœ ๋‹ค๋ฅธ ๋‘ ๊ณผ์ œ๊ฐ€ ๊ฐ™์€ ID** ๋ฅผ ์”๋‹ˆ๋‹ค. ์‹ ๊ทœ๋Š” `J-1`/`J-2`/`B-21` ์„ ์”๋‹ˆ๋‹ค. ๊ธฐ์กด ์ถฉ๋Œ์€ K-3. ๊ฐฑ์‹  ํ•ญ๋ชฉ: 1. `:3` ์ตœ์ข… ๊ฐฑ์‹ ์ผ 2. `:6` ์ด ์ถ”์  ๋ฏธํ•ด๊ฒฐ ๊ณผ์ œ ์นด์šดํŠธ 3. `:7` ์™„๋ฃŒ ๊ณผ์ œ **29 โ†’ 30** ๋ฐ ๋ชฉ๋ก์— `B-21` ์ถ”๊ฐ€ 4. `:37` B-20 ํ›„์† ์ •๋ฆฌ ์ค„์— J-1/J-2 ํ•ด์†Œ ํ•œ ์ค„ 5. ยง2 ์— `B-21` ์ ˆ ์‹ ์„ค โ€” ํ˜„์ƒ(๋ผ์ด๋ธŒ `agy-creator-01` ์ด `--agent` ์—†์ด `exit 2`), ์›์ธ(ํ•ด์„๊ธฐ 4์ค‘ํ™”), ์กฐ์น˜, ํšŒ๊ท€ ๊ฐ€๋“œ 6. ยง6.2 ๋กœ๋“œ๋งต ํ‘œ์— ์™„๋ฃŒ ํ–‰ 7. ยง6.3 ํŒŒ์ผ ์†Œ์œ ๊ถŒ ์Šฌ๋กฏ ํ‘œ ๊ฐฑ์‹  --- ## 9. ํ›„์† ๋ฐฑ๋กœ๊ทธ (์ด๋ฒˆ ๋ฒ”์œ„ ๋ฐ–, ๋“ฑ๋ก๋งŒ) | ID | ๋‚ด์šฉ | ๊ทผ๊ฑฐ | |---|---|---| | **K-1** | `run_loop.sh:278` `resolve_agent_type` ํ†ตํ•ฉ | ยง1.1 โ€” ํ•ด์„ ์‹คํŒจ๋ฅผ `claude` ๋กœ ํก์ˆ˜. cline ์„ธ์…˜์— claude ์ข…๋ฃŒํ‚ค๋ฅผ ๋ณด๋‚ด๋Š” ์˜ค๋ถ„๋ฅ˜๊ฐ€ ๊ตฌ์กฐ์ ์œผ๋กœ ๊ฐ€๋Šฅ. ํ˜ธ์ถœ 12๊ณณ์ด๋ผ ๋ณ„๋„ ๊ณ„ํš ํ•„์š” | | **K-2** | `compute_2xk_layout` ์˜ `if max_columns and โ€ฆ` falsy-zero | `--max-cols 0`("์—ด 0๊ฐœ")์ด "์ƒํ•œ ์—†์Œ"์œผ๋กœ ํก์ˆ˜๋จ. J-1 ๊ณผ ๋™์ผ ๋ถ€๋ฅ˜ | | **K-3** | `IMPROVEMENTS.md` ์˜ `C-1` ID ์ถฉ๋Œ ์ •๋ฆฌ | ยง8 | | **K-4** | `agent_of_row` ์— ํ•˜์ดํ”ˆ ์„ธ๊ทธ๋จผํŠธ ๋งค์นญ ์ถ”๊ฐ€ ์—ฌ๋ถ€ | `orc-hermes-main` ๋ฅ˜ ๋ฏธํ•ด๊ฒฐ. `reconcile.sh` ์ž…์–‘ ํŒ์ • ์˜ํ–ฅ โ†’ ์‹ค์ธก ์„ ํ–‰ | | **K-5** | **[Rev.2 ์‹ ๊ทœ]** `_env_int` ํ›„๋ณด **์ˆœ์„œ** ์žฌ๊ฒ€ํ†  | ยง1.11.1 โ€” ๋ฌธ์„œํ™”๋˜์ง€ ์•Š์€ ๋ ˆ๊ฑฐ์‹œ `MAM_MIN_COLS` ๊ฐ€ `.mam.env.example` ์ด ๋ฌธ์„œํ™”ํ•œ `MAM_MIN_PANE_COLS` ๋ณด๋‹ค **์šฐ์„ **ํ•ฉ๋‹ˆ๋‹ค. C-2(๋ฌดํšจ๊ฐ’ ๊ฑด๋„ˆ๋›ฐ๊ธฐ)๋Š” ์ด ์ˆœ์„œ ๋ฌธ์ œ๋ฅผ ์™„ํ™”ํ•  ๋ฟ ํ•ด์†Œํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค ์œ ํšจํ•œ ๊ฐ’์ด๋ฉด ์—ฌ์ „ํžˆ ๋ ˆ๊ฑฐ์‹œ๊ฐ€ ์ด๊น๋‹ˆ๋‹ค. ์ˆœ์„œ ๋ณ€๊ฒฝ์€ ํ–‰๋™ ๋ณ€๊ฒฝ์ด๋ฏ€๋กœ ๋ณ„๋„ ํ•ญ๋ชฉ | --- ## 10. ๊ฒ€์ฆ ์ ˆ์ฐจ (Creator ์‹คํ–‰) ```bash # 1) ๊ตฌ๋ฌธ for f in .agents/skills/lib.sh \ .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \ .agents/skills/multi-agent-mux-resume/scripts/update_yaml_resumed.sh \ .agents/skills/multi-agent-mux-resume/scripts/resume_session.sh \ .agents/skills/multi-agent-mux-resume/scripts/resolve_session_id.sh \ .agents/skills/multi-agent-mux-create/scripts/create_session.sh; do bash -n "$f" || echo "FAIL $f" done python3 -m py_compile .agents/skills/lib_py/layout.py # 2) J-1 ์ง์ ‘ ํ™•์ธ (0 ์ด ์‚ด์•„๋‚จ๋Š”๊ฐ€) P='{"result":{"panes":[{"pane_id":"p1","rect":{"x":0,"y":0,"width":50,"height":30}}]}}' printf '%s' "$P" | PYTHONPATH=.agents/skills MAM_MIN_PANE_COLS=0 python3 -m lib_py.layout --json printf '%s' "$P" | PYTHONPATH=.agents/skills python3 -m lib_py.layout --json --min-cols 0 # โ†’ ๋‘ ์ถœ๋ ฅ์ด ์™„์ „ํžˆ ๋™์ผํ•˜๊ณ  direction=right # 3) C-2 ์ง์ ‘ ํ™•์ธ (๋ฌดํšจ ๋ณ„์นญ์ด ๋ฌธ์„œํ™”๋œ ๋ณ€์ˆ˜๋ฅผ ๊ฐ€๋ฆฌ์ง€ ์•Š๋Š”๊ฐ€) printf '%s' "$P" | PYTHONPATH=.agents/skills \ env MAM_MIN_COLS=foo MAM_MIN_PANE_COLS=25 python3 -m lib_py.layout --json # โ†’ direction=right (์ˆ˜์ • ์ „์—๋Š” overflow) # 4) ์ „์ฒด ์Šค์œ„ํŠธ (๋ฒ ์ด์Šค๋ผ์ธ 333 โ†’ ๊ธฐ๋Œ€ 341) .venv/bin/python -m pytest tests/ -q # 5) ๋ฐฐํฌ ์‹ ์„ ๋„ (๊ธฐ์กด 31๊ฑด ์œ ์ง€) .venv/bin/python -m pytest tests/test_deploy_freshness.py -q # 6) ๋ฎคํ…Œ์ด์…˜ M1~M7c (๊ฒฉ๋ฆฌ ์‚ฌ๋ณธ์—์„œ) ``` **๊ธˆ์ง€ ์‚ฌํ•ญ**: `tests/test_tier1_unit.py:142` ์™€ `tests/test_tier3_integration.py:398` ์€ **์ˆ˜์ •ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค**. ๋‘ ๊ฑด์ด ๋ฌด์ˆ˜์ •์œผ๋กœ PASS ํ•˜๋Š” ๊ฒƒ์ด ์•ˆ B ์˜ ์ข…๋ฃŒ ์ฝ”๋“œ ๊ณ„์•ฝ ๋ณด์กด์„ ์ž…์ฆํ•˜๋Š” ์ฆ๊ฑฐ์ž…๋‹ˆ๋‹ค. ๊ณ ์ณ์•ผ ํ†ต๊ณผํ•œ๋‹ค๋ฉด ๊ตฌํ˜„์ด ์•ˆ A ๋กœ ํ˜๋Ÿฌ๊ฐ„ ๊ฒƒ์ด๋ฏ€๋กœ ๋˜๋Œ๋ ค์•ผ ํ•ฉ๋‹ˆ๋‹ค. **๋ผ์ด๋ธŒ ์„ธ์…˜ ๋ณดํ˜ธ**: T4 ๋Š” `mam_sandbox` ์•ˆ์—์„œ๋งŒ ๋™์ž‘ํ•˜๋ฉฐ ์‹ค์ œ `.mam/agent-sessions.yaml` ์„ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ์ค‘ `stop_session.sh` ๋ฅผ ์‹ค ์›Œํฌ์ŠคํŽ˜์ด์Šค์—์„œ ์ˆ˜๋™ ์‹คํ–‰ํ•˜์ง€ ๋งˆ์‹ญ์‹œ์˜ค โ€” `canary-projects-multi-agent-mux-creator-claude` ๊ฐ€ ์ด ์„ธ์…˜์ž…๋‹ˆ๋‹ค. --- ## 11. ๊ทœ๋ชจ ์ถ”์ • | ํŒŒ์ผ | ๋ณ€๊ฒฝ | |---|---| | `.agents/skills/lib.sh` | +22 (ํ—ฌํผ 1๊ฐœ) | | `stop_session.sh` | +8 / โˆ’10, ํ—ค๋”ยทusage +6 | | `update_yaml_resumed.sh` | +8 / โˆ’9, ํ—ค๋”ยทusage +2 | | `lib_py/layout.py` | +10 / โˆ’6 (docstring ํ™•์žฅ ํฌํ•จ) | | `multi-agent-mux-stop/SKILL.md` | +12 | | `multi-agent-mux-resume/SKILL.md` | +1 / โˆ’1 | | `multi-agent-mux-create/SKILL.md` | +2 / โˆ’2 | | `create_session.sh` / `resolve_session_id.sh` | ํ—ค๋” ๊ฐ +1 / โˆ’1 | | `tests/test_layout.py` | +62 (T1 45 + T1b 17) | | `tests/test_a4_adapter_contract.py` | +9 | | `tests/test_tier2_component.py` | +58 | | `IMPROVEMENTS.md` | +20 | | (์ปค๋ฐ‹ 6) 3๊ฐœ ์Šคํฌ๋ฆฝํŠธ ์†Œ์‹ฑ ์ค„ | +3 / โˆ’3 | ์ด **์•ฝ +215 / โˆ’35 ์ค„**, ํŒŒ์ผ 12๊ฐœ. ๊ทœ๋ชจ **์†Œ~์ค‘**. --- ## 12. ์ฑŒ๋ฆฐ์ €์—๊ฒŒ C-1 ์€ ๊ณ„ํš๋Œ€๋กœ ์งœ๋ฉด ํ™•์ • ์‹คํŒจํ•˜๋Š” ๊ฒฐํ•จ์ด์—ˆ๊ณ , ๋‘ ๊ฐˆ๋ž˜ ๋ชจ๋‘ ์ •ํ™•ํ–ˆ์Šต๋‹ˆ๋‹ค. ํŠนํžˆ ๊ฐˆ๋ž˜ โ‘ก(๋ถ€๋ถ„ ๋ฌธ์ž์—ด ์œ„์–‘์„ฑ)๋Š” **ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ฌด๋„ ๋ˆˆ์น˜์ฑ„์ง€ ๋ชปํ•˜๋Š”** ์ข…๋ฅ˜๋ผ ๋” ๊ฐ’์–ด์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ยง1.10.2 ์—์„œ ๋ฎคํ…Œ์ด์…˜์œผ๋กœ ์žฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. ์ˆ˜์ •์•ˆ์„ ๊ทธ๋Œ€๋กœ ์ฑ„ํƒํ•˜์ง€ ์•Š์€ ๋ถ€๋ถ„์€ ํ•œ ๊ณณ์ž…๋‹ˆ๋‹ค โ€” ๋ฌธ์„œ ์ „์ฒด ์Šค์บ”์ด ์‚ฐ๋ฌธ ์† ํŒŒ์ผ๋ช… ์–ธ๊ธ‰์„ ๋ช…๋ น์œผ๋กœ ์˜ค์ธํ•ฉ๋‹ˆ๋‹ค(ยง1.10.3, ์œ„์–‘์„ฑ 2๊ฑด ์‹ค์ธก). ์ด ๊ณ„ํš ยง4.4 ์ž์ฒด๊ฐ€ SKILL.md ์— ์‚ฐ๋ฌธ์„ ์ถ”๊ฐ€ํ•˜๋ฏ€๋กœ ์ž„๋ฐ•ํ•œ ๋ฌธ์ œ์˜€์Šต๋‹ˆ๋‹ค. ์ปค๋งจ๋“œ ๋‹จ์œ„๋ผ๋Š” **ํ•ต์‹ฌ ๊ต์ •์€ ๊ทธ๋Œ€๋กœ ์ฑ„ํƒ**ํ•˜๊ณ  ํŽœ์Šค ์Šค์ฝ”ํ”„๋ฅผ ์–น์—ˆ์Šต๋‹ˆ๋‹ค. C-2 ๋Š” ์ œ๊ธฐํ•˜์‹  ๊ทผ๊ฑฐ(๋‹ค์ค‘ fallback ์ทจ์ง€)๋ณด๋‹ค ๊ฐ•ํ•œ ๊ทผ๊ฑฐ๊ฐ€ ์‹ค์ธก์—์„œ ๋‚˜์™”์Šต๋‹ˆ๋‹ค. `MAM_MIN_COLS` ๋Š” `layout.py` ๋ฐ– ์–ด๋””์—๋„ ์—†๋Š” ๋ ˆ๊ฑฐ์‹œ ๋ณ„์นญ์ด๊ณ , ๊ฐ€๋ ค์ง€๋Š” `MAM_MIN_PANE_COLS` ๋Š” `.mam.env.example` ์ด ๋ฌธ์„œํ™”ํ•œ **์œ ์ผํ•œ** ์ด๋ฆ„์ž…๋‹ˆ๋‹ค. ๊ฒŒ๋‹ค๊ฐ€ ํ˜„ํ–‰ ์ฝ”๋“œ๋Š” `""` ๋Š” ๊ฑด๋„ˆ๋›ฐ๊ณ  `"foo"` ๋Š” ํƒˆ์ถœํ•˜๋Š” ๋น„๋Œ€์นญ์„ ๊ฐ–๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ˆ˜์šฉํ•˜๊ณ  ์ „์šฉ ํ…Œ์ŠคํŠธ T1b ๋ฅผ ์‹ ์„คํ–ˆ์Šต๋‹ˆ๋‹ค.