diff --git a/.agents/reports/canary-projects-multi-agent-mux-creator-claude/report-1b18eb9a.md b/.agents/reports/canary-projects-multi-agent-mux-creator-claude/report-1b18eb9a.md new file mode 100644 index 0000000..db17eb7 --- /dev/null +++ b/.agents/reports/canary-projects-multi-agent-mux-creator-claude/report-1b18eb9a.md @@ -0,0 +1,124 @@ +# ๐Ÿ” ๊ต์ฐจ ์ฝ”๋“œ ๋ฆฌ๋ทฐ (2์ฐจ) โ€” Job `1b18eb9a` + +- **์—ญํ• **: Reviewer +- **๋Œ€์ƒ**: `--herdr-session` / `--herdr-server` ํ‘œ์ค€ํ™” ๊ตฌํ˜„๋ถ„ โ€” ์›Œํ‚น ํŠธ๋ฆฌ 6ํŒŒ์ผ (`+245 / โˆ’56`) +- **๊ธฐ์ค€ ์ปค๋ฐ‹**: `f7e1513` / ๋ฏธ์ถ”์  ํŒŒ์ผ 0๊ฑด +- **์ง์ „ ํŒ์ •**: `2d3fef82` **NOT PASS** (์ฐจ๋‹จ 2๊ฑด B-1ยทB-2, ๊ถŒ๊ณ  2๊ฑด) + +--- + +## 1. ๊ฒฐ๋ก  + +์ง์ „ ๋ฆฌ๋ทฐ์˜ ์ฐจ๋‹จ 2๊ฑด๊ณผ ๊ถŒ๊ณ  2๊ฑด์ด **์ „๋ถ€ ํ•ด์†Œ**๋๊ณ , ๊ฐ๊ฐ์— **๋ฎคํ…Œ์ด์…˜์œผ๋กœ ๊ฐ๋„๊ฐ€ ํ™•์ธ๋˜๋Š” ํšŒ๊ท€ ๊ฐ€๋“œ**๊ฐ€ ๋ถ™์—ˆ์Šต๋‹ˆ๋‹ค. ์ „์ฒด ์Šค์œ„ํŠธ **346 passed / ์‹คํŒจ 0**. + +P3 ๊ด€์ฐฐ 2๊ฑด๋งŒ ๋‚จ์Šต๋‹ˆ๋‹ค. ์–ด๋А ์ชฝ๋„ ๊ฒฐํ•จ์„ ๊ฐ€๋ฆฌ์ง€ ์•Š์•„ ํ†ต๊ณผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค(์ตœ์ข… ํƒœ๊ทธ๋Š” ๋ณด๊ณ ์„œ ๋งˆ์ง€๋ง‰ ์ค„). + +--- + +## 2. ์ง์ „ ์ง€์  ๋Œ€๋น„ ์ดํ–‰ + +| ์ง์ „ ํ•ญ๋ชฉ | ์ดํ–‰ | ๊ฐ€๋“œ | +|---|---|---| +| **B-1** resume ์ฃผ ๊ฒฝ๋กœ๊ฐ€ `--herdr-session` ๋ฏธ์ „๋‹ฌ | โœ… `resume_session.sh:136-138` ์ด ์กฐ๊ธฐ ์ข…๋ฃŒ ๋ถ„๊ธฐ(`:72-74`)์™€ ๋™์ผํ•˜๊ฒŒ ์ „๋‹ฌ | **H1 ๊ฒ€์ถœ** | +| **B-2** `setdefault` ๋กœ ๊ธฐ์กด ํ–‰์— ๋ฌดํšจ | โœ… `HERDR_SERVER_OPT_EXPLICIT` ๋กœ ๋ช…์‹œ/๋ฐฑํ•„ ๊ตฌ๋ถ„, `start`/`attach`/`kill_command` ๊นŒ์ง€ ๊ฐฑ์‹  | **H2 ๊ฒ€์ถœ** | +| **ยง4.1** dry-run ์ด ํ”Œ๋ž˜๊ทธ ๋ฌด์‹œ๋ฅผ ๊ตฌ๋ถ„ ๋ชป ํ•จ | โœ… ์ถœ๋ ฅ์— `herdr_session=${HERDR_SESSION_NAME:-default}` ์ถ”๊ฐ€ + ๋‘ ํ”Œ๋ž˜๊ทธ ๊ฐ๊ฐ ๋‹จ์–ธ | **H4 ๊ฒ€์ถœ** | +| **ยง4.2** ๊ฐ์‚ฌ ์ง€์‹œ๋œ ์„ธ ๊ฐ€๋“œ์— ์ปค๋ฒ„๋ฆฌ์ง€ 0 | โœ… `test_comp_create_herdr_session_default_preserved` ์‹ ์„ค | **H3 ๊ฒ€์ถœ** | +| **N-2** create usage ํ…Œ์ŠคํŠธ๊ฐ€ ํŒŒ์„œ ๋ฏธ์‹คํ–‰ | โœ… ์ˆ˜์šฉ ๊ฒฝ๋กœ(`--herdr-session` + dry-run rc=0)์™€ ๊ฑฐ๋ถ€ ๊ฒฝ๋กœ(`--invalid-flag-xyz` rc=2) ์–‘๋ฐฉํ–ฅ ์ถ”๊ฐ€ | โ€” | +| **N-3** `HERDR_SERVER_NAME` ์ง€์› ์—ฌ๋ถ€ ๋ชจํ˜ธ | โœ… SKILL.md ์— *"legacy env alias: `HERDR_SERVER_NAME`"* ๋ช…๊ธฐ | โ€” | + +B-2 ์˜ ์ฒ˜๋ฐฉ์€ ์ œ๊ฐ€ ์ œ์•ˆํ•œ ๊ฒƒ๋ณด๋‹ค ๋‚ซ์Šต๋‹ˆ๋‹ค. `if is_explicit or not target.get('herdr_session')` ๋Š” **`herdr_session: null` ์ธ ํ–‰๊นŒ์ง€ ๋ฐฑํ•„**ํ•ฉ๋‹ˆ๋‹ค โ€” `setdefault` ๋Š” ํ‚ค๊ฐ€ ์กด์žฌํ•˜๊ธฐ๋งŒ ํ•˜๋ฉด `None` ๋„ ๋ณด์กดํ•ด ๋ฒ„๋ฆฌ๋˜ ๊ตฌ๋ฉ์ด์—ˆ๋Š”๋ฐ, ์ด ํ˜•ํƒœ๊ฐ€ ๊ทธ๊ฒƒ๋„ ํ•จ๊ป˜ ๋‹ซ์Šต๋‹ˆ๋‹ค. ๋˜ `start_command`/`attach_command`/`kill_command` 3์ข…์„ ํ•จ๊ป˜ ๊ฐฑ์‹ ํ•ด, ํ–‰์˜ ๋ผ์šฐํŒ… ์ •๋ณด๊ฐ€ ๋ถ€๋ถ„์ ์œผ๋กœ๋งŒ ๊ฐฑ์‹ ๋˜๋Š” ์ƒํƒœ๋ฅผ ๋งŒ๋“ค์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +--- + +## 3. ๊ฒ€์ฆ ๊ฒฐ๊ณผ + +| ๊ฒ€์ฆ | ๊ฒฐ๊ณผ | +|---|---| +| ์ „์ฒด ์Šค์œ„ํŠธ | **346 passed / 462.39s / ์‹คํŒจ 0** | +| ์ˆ˜์ง‘ ์ˆ˜ | 341 โ†’ **346** (์‹ ์„ค 5๊ฑด) | +| `bash -n` 4๊ฐœ ๋ณ€๊ฒฝ ์Šคํฌ๋ฆฝํŠธ | 4/4 OK | +| ์‹ ์„ค 5๊ฑด ๋Œ€์กฐ๊ตฐ | 5 passed | +| ๋ฎคํ…Œ์ด์…˜ | **H1~H5 ์ „๋ถ€ ์ง€์ • ํ…Œ์ŠคํŠธ ๊ฒ€์ถœ** | + +1์ฐจ ๋ฆฌ๋ทฐ์—์„œ ๊ด€์ฐฐ๋๋˜ `test_o2_18_orphan_steal_lock_recovered` ํ”Œ๋ ˆ์ดํฌ๋Š” ์ด๋ฒˆ ์‹คํ–‰์—์„œ ์žฌํ˜„๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค(์„ ์žฌ ๋ถ€ํ•˜ ๋ฏผ๊ฐ ์ด์Šˆ, ยง5 N-1). + +--- + +## 4. ๋ฎคํ…Œ์ด์…˜ ๋งคํŠธ๋ฆญ์Šค + +๊ฒฉ๋ฆฌ `rsync` ์‚ฌ๋ณธ. ๋Œ€์กฐ๊ตฐ 5/5 ํ†ต๊ณผ. + +| # | ๋ฎคํ…Œ์ด์…˜ | ๊ฒฐ๊ณผ | +|---|---|---| +| **H1** | `resume_session.sh:136-138` ์—์„œ `--herdr-session` ์ œ๊ฑฐ (B-1 ๋˜๋Œ๋ฆผ) | `resume_herdr_session_propagation` **FAILED** | +| **H2** | `is_explicit or not target.get(...)` โ†’ `setdefault` (B-2 ๋˜๋Œ๋ฆผ) | `resume_herdr_session_propagation` **FAILED** | +| **H3** | `create_session.sh` ์˜ `HERDR_SERVER_OPT` ๊ฐ€๋“œ ๋˜๋Œ๋ฆผ | `default_preserved` **FAILED** / ๋‚˜๋จธ์ง€ 2๊ฑด PASSED | +| **H4** | dry-run ์ถœ๋ ฅ์—์„œ `herdr_session=` ์ œ๊ฑฐ | `cli_parsing_dry_run` **FAILED** | +| **H5** | create ํŒŒ์„œ๊ฐ€ ๊ฐ’์„ ๋ฒ„๋ฆผ (`shift 2` ๋งŒ) | `cli_parsing_dry_run` ยท `default_preserved` ยท `yaml_propagation` **3๊ฑด FAILED** | + +**H3 ์ด ์ •ํ™•ํžˆ ํ•˜๋‚˜๋งŒ ๊นจ๋Š” ๊ฒƒ**์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์ง์ „ ๋ฆฌ๋ทฐ์—์„œ ์‹ค์ธกํ–ˆ๋“ฏ ๊ทธ ๊ฐ€๋“œ๋“ค์˜ ํ–‰๋™ ๋ณ€ํ™”๋Š” ๋ฆฌํ„ฐ๋Ÿด ๊ฐ’ `default` **ํ•˜๋‚˜๋ฟ**์ด๋ฏ€๋กœ, `default_preserved` ๋งŒ ์‹คํŒจํ•˜๊ณ  `cli_parsing_dry_run`ยท`yaml_propagation` ์ด ํ†ต๊ณผํ•˜๋Š” ๊ฒƒ์ด **์ •ํ™•ํ•œ ๊ฐ๋„**์ž…๋‹ˆ๋‹ค. ๊ณผ์ž‰ ๊ฒฐํ•ฉ ์—†์ด ๋”ฑ ๊ทธ ์„ฑ์งˆ๋งŒ ์žก์Šต๋‹ˆ๋‹ค. + +**H1 ์€ ์ œ๊ฐ€ ์ฐจ๋‹จํ–ˆ๋˜ ๋ฐ”๋กœ ๊ทธ ํšŒ๊ท€**์ž…๋‹ˆ๋‹ค. ์ด์ œ ์žกํž™๋‹ˆ๋‹ค. + +--- + +## 5. ํ”Œ๋ž˜๊ทธ ์—†๋Š” resume ๊ฒฝ๋กœ ํ™•์ธ (์‹ ๊ทœ ๊ฒ€ํ† ) + +`resume_session.sh` ๋Š” ์ด์ œ `--herdr-session "$HERDR_SESSION_NAME"` ์„ **๋ฌด์กฐ๊ฑด** ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ํ”Œ๋ž˜๊ทธ๋ฅผ ์ฃผ์ง€ ์•Š์•„๋„ ๊ฐ’์ด `resolve_herdr_session(...)` ๊ฒฐ๊ณผ๋กœ ์ฑ„์›Œ์ ธ ๋„˜์–ด๊ฐ€๋ฏ€๋กœ, ์ž์‹์—์„œ `HERDR_SERVER_OPT_EXPLICIT` ์ด **ํ•ญ์ƒ 1** ์ด ๋ฉ๋‹ˆ๋‹ค. ๋ช…์‹œ/๋ฐฑํ•„ ๊ตฌ๋ถ„์ด ์ด ํ˜ธ์ถœ์ž์—์„œ๋Š” ๋ฌด์˜๋ฏธํ•ด์ง€๋Š” ์…ˆ์ด๋ผ, ์ž˜๋ชป๋œ ๊ธฐ๋ก์„ ๋งŒ๋“œ๋Š”์ง€ ์‹ค์ธกํ–ˆ์Šต๋‹ˆ๋‹ค. + +| ํ–‰์˜ `herdr_session` | ํ”Œ๋ž˜๊ทธ ์—†์ด resume ํ›„ | ํŒ์ • | +|---|---|---| +| `RECORDED-X` | `RECORDED-X` (attach_command ๋„ ์ผ์น˜) | ๋ฉฑ๋“ฑ โœ… | +| `default` | `mam-` | **์ •์ •** โœ… | +| (ํ‚ค ์—†์Œ) | `mam-` | ๋ฐฑํ•„ โœ… | + +2ํ–‰์ด ์œ ์ผํ•œ ํ–‰๋™ ๋ณ€ํ™”์ž…๋‹ˆ๋‹ค. ํ–‰์ด `default` ๋ฅผ ๊ธฐ๋กํ•˜๊ณ  ์žˆ์œผ๋ฉด `resolve_herdr_session` ์€ (`val != 'default'` ์กฐ๊ฑด ๋•Œ๋ฌธ์—) ๊ทธ ๊ฐ’์„ ๊ฑด๋„ˆ๋›ฐ๊ณ  ํด๋ฐฑ์œผ๋กœ ๋‚ด๋ ค๊ฐ€๋ฏ€๋กœ, **์‹ค์ œ ์Šคํฐ์€ ์ด๋ฏธ `mam-` ๋กœ ์ด๋ค„์ง‘๋‹ˆ๋‹ค**. ์ฆ‰ ๊ธฐ๋ก์„ `mam-` ๋กœ ๋ฐ”๊พธ๋Š” ๊ฒƒ์€ ํ–‰์„ **ํ˜„์‹ค๊ณผ ์ผ์น˜์‹œํ‚ค๋Š” ์ •์ •**์ด์ง€ ์˜ค์ž‘๋™์ด ์•„๋‹™๋‹ˆ๋‹ค. ์„ธ ๊ฒฝ์šฐ ๋ชจ๋‘ ๋ฌธ์ œ์—†์Šต๋‹ˆ๋‹ค. + +--- + +## 6. ๊ด€์ฐฐ ์‚ฌํ•ญ (P3 โ€” ๋น„์ฐจ๋‹จ) + +### ๐ŸŸก O-1: `update_yaml_resumed.sh` ์‹ ๊ทœ ํ–‰ ๋ถ„๊ธฐ์˜ `herdr_server` ์ถ”๊ฐ€์— ๊ฐ€๋“œ๊ฐ€ ์—†๋‹ค + +`target is None` ๋ถ„๊ธฐ์— `'herdr_server': server_name` ์ด ์ถ”๊ฐ€๋๋Š”๋ฐ, ์ด ์ค„์„ ์ œ๊ฑฐํ•ด๋„ ๊ฒ€์ถœ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +``` +H6 (์‹ ๊ทœ ํ–‰ dict ์—์„œ 'herdr_server' ์ œ๊ฑฐ) + tier2 33 passed + tier1 + tier3 + tier4 + uuid_target + ws_scope 69 passed + โ†’ 102๊ฑด ์ „๋ถ€ ํ†ต๊ณผ, ๋ฏธ๊ฒ€์ถœ +``` + +์‹ ์„ค resume ํ…Œ์ŠคํŠธ๊ฐ€ **๊ธฐ์กด ํ–‰**์„ ์‹ฌ์–ด ๋†“๊ณ  ์‹œ์ž‘ํ•˜๋ฏ€๋กœ ์‹ ๊ทœ ํ–‰ ๊ฒฝ๋กœ๋ฅผ ํƒ€์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ์— ์—†๋Š” ์„ธ์…˜์„ resume ํ•  ๋•Œ๋งŒ ๋„๋‹ฌํ•˜๋Š” ์ข์€ ๊ฒฝ๋กœ์ด๊ณ , ์ถ”๊ฐ€๋œ ํ•„๋“œ๋Š” ๊ธฐ์กด ํ•„๋“œ ์˜†์— ๋ณ„์นญ์„ ํ•˜๋‚˜ ๋” ๋‘๋Š” **์ˆœ์ˆ˜ ๊ฐ€์‚ฐ ๋ณ€๊ฒฝ**์ด๋ผ ํšŒ๊ท€ ์œ„ํ—˜์ด ๋‚ฎ์Šต๋‹ˆ๋‹ค. ์ฐจ๋‹จํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. + +์ฒ˜๋ฐฉ์ด ํ•„์š”ํ•˜๋ฉด ๊ธฐ์กด resume ํ…Œ์ŠคํŠธ์—์„œ `herdr_sessions: []` ๋กœ ์‹œ์ž‘ํ•˜๋Š” ์ผ€์ด์Šค 1๊ฑด์ด๋ฉด ์ถฉ๋ถ„ํ•ฉ๋‹ˆ๋‹ค. + +### ๐ŸŸก O-2: `create_session.sh:216` ๊ฐ€๋“œ๋Š” ์—ฌ์ „ํžˆ ๋ฌด๋™์ž‘ + +```bash +if [ -z "$HERDR_SERVER_OPT" ]; then + RESOLVED_SERVER="$(resolve_herdr_workspace "$SESSION_NAME" "$WORKSPACE")" + export HERDR_SESSION_NAME="${HERDR_SESSION_NAME:-$RESOLVED_SERVER}" +fi +``` + +`--herdr-session` ์ด ์ฃผ์–ด์ง€๋ฉด `:78-79` ์—์„œ ์ด๋ฏธ `HERDR_SESSION_NAME` ์ด ๋น„์–ด ์žˆ์ง€ ์•Š์œผ๋ฏ€๋กœ `${VAR:-...}` ๊ฐ€ ๋ฐœ๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ฆ‰ ์ด ๊ฐ€๋“œ์˜ ์œ ์ผํ•œ ์‹คํšจ๋Š” **`resolve_herdr_workspace` ์„œ๋ธŒํ”„๋กœ์„ธ์Šค ํ˜ธ์ถœ 1ํšŒ๋ฅผ ๊ฑด๋„ˆ๋›ฐ๋Š” ๊ฒƒ**์ž…๋‹ˆ๋‹ค. ๋ธŒ๋ฆฌํ”„๊ฐ€ ๊ฐ์‚ฌ๋ฅผ ์ง€์‹œํ•œ ์„ธ ์ง€์  ์ค‘ ํ•˜๋‚˜์ด๋ฏ€๋กœ "๊ฐ์‚ฌํ–ˆ๊ณ  ๋ฌด๋™์ž‘์ž„์„ ํ™•์ธํ–ˆ๋‹ค" ๋Š” ์‚ฌ์‹ค ์ž์ฒด๊ฐ€ ๊ธฐ๋ก๋  ๊ฐ€์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค โ€” ๋‹ค๋งŒ ๋ฐฉ์–ด์ ์œผ๋กœ ๋‚จ๊ฒจ ๋‘๋Š” ๊ฒƒ์ด ํ•ด๋กญ์ง€ ์•Š๊ณ , ๋ฏธ๋ž˜์— `:78-79` ๊ฐ€ ๋ฐ”๋€Œ๋ฉด ์‹คํšจ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์ œ๊ฑฐ๋ฅผ ๊ถŒํ•˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. + +### ์ด์›” (๋ฒ”์œ„ ๋ฐ–, ์„ ์žฌ) + +| ID | ๋‚ด์šฉ | +|---|---| +| **N-1** | `test_o2_18_orphan_steal_lock_recovered` ๋ถ€ํ•˜ ๋ฏผ๊ฐ ํ”Œ๋ ˆ์ดํฌ โ€” `acquire_bg()` ์˜ ๊ณ ์ • `time.sleep(0.3)` ์„ ๋งˆ์ปค ํด๋ง์œผ๋กœ ๊ต์ฒดํ•˜๋ฉด ํ•ด์†Œ. ์ด๋ฒˆ ๋ณ€๊ฒฝ๊ณผ ๋ฌด๊ด€ | +| **N-4** | `README.md:98,100` / `README.ko.md:80,82` ๊ฐ€ ๊ตฌ `herdr -L ` ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์„œ์ˆ  โ€” `IMPROVEMENTS.md:304` ๊ธฐ์ค€ ์ด๋ฏธ ์™„๋ฃŒ๋œ ์ „ํ™˜์ด๋ฏ€๋กœ ์„ ์žฌ ๋“œ๋ฆฌํ”„ํŠธ. SKILL.md ๋งŒ ์ •๋ฆฌ๋˜์–ด ๋ฌธ์„œ ํ‘œ๋ฉด ๊ฐ„ ๋ถˆ์ผ์น˜๊ฐ€ ๋‚จ์•„ ์žˆ์Œ | + +--- + +## 7. ์ดํ‰ + +์ฐจ๋‹จ 2๊ฑด์ด ๋ชจ๋‘ ๋‹ซํ˜”๊ณ , ๋” ์ค‘์š”ํ•˜๊ฒŒ๋Š” **๊ฐ๊ฐ์— ๊ฐ๋„๊ฐ€ ์‹ค์ฆ๋œ ๊ฐ€๋“œ๊ฐ€ ๋ถ™์—ˆ์Šต๋‹ˆ๋‹ค**. ์ง์ „ ๋ฆฌ๋ทฐ์—์„œ "์‹ ์„ค 3๊ฑด ์ค‘ ์‹คํšจ๋Š” 1๊ฑด" ์ด์—ˆ๋˜ ์ƒํƒœ๊ฐ€ ์ด๋ฒˆ์—๋Š” ์‹ ์„ค 5๊ฑด ์ค‘ 4๊ฑด์ด ๋ฎคํ…Œ์ด์…˜์„ ์žก์Šต๋‹ˆ๋‹ค(usage ํ…Œ์ŠคํŠธ๋Š” ์„ฑ๊ฒฉ์ƒ ๋ฌธ์„œ ๋‹จ์–ธ์ด๋ผ ์ œ์™ธ). + +B-2 ์˜ ์ฒ˜๋ฐฉ์€ ์ œ๊ฐ€ ์ œ์•ˆํ•œ ํ˜•ํƒœ๋ณด๋‹ค ๋‚ซ์Šต๋‹ˆ๋‹ค โ€” `herdr_session: null` ๊ตฌ๋ฉ๊ณผ ๋ผ์šฐํŒ… ๋ช…๋ น 3์ข…์˜ ๋ถ€๋ถ„ ๊ฐฑ์‹  ๋ฌธ์ œ๋ฅผ ํ•จ๊ป˜ ๋‹ซ์•˜์Šต๋‹ˆ๋‹ค. + +๋‚จ์€ 2๊ฑด์€ P3 ์ž…๋‹ˆ๋‹ค. O-1 ์€ ์ข์€ ๊ฒฝ๋กœ์˜ ๊ฐ€์‚ฐ ๋ณ€๊ฒฝ์ด๋ผ ํšŒ๊ท€ ์œ„ํ—˜์ด ๋‚ฎ๊ณ , O-2 ๋Š” ๋ฐฉ์–ด์  ๋ฌด๋™์ž‘ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. ๋‹ค์Œ ์ปค๋ฐ‹์— ์ •๋ฆฌํ•˜๊ฑฐ๋‚˜ ๊ทธ๋Œ€๋กœ ๋‘ฌ๋„ ๋ฌด๋ฐฉํ•ฉ๋‹ˆ๋‹ค. + +[VERDICT: PASS] diff --git a/.agents/reports/canary-projects-multi-agent-mux-creator-cline/report-40944efc.md b/.agents/reports/canary-projects-multi-agent-mux-creator-cline/report-40944efc.md new file mode 100644 index 0000000..8147b47 --- /dev/null +++ b/.agents/reports/canary-projects-multi-agent-mux-creator-cline/report-40944efc.md @@ -0,0 +1,264 @@ +# Cross-Code Review โ€” Job 40944efc + +- **Reviewer**: cline +- **Target**: `--herdr-session` (alias `--herdr-server`) standardization across 6 files (`+245 / โˆ’56`) +- **Base commit**: working tree (unstaged diff) +- **Date**: 2026-08-24 + +--- + +## ยง0. Verdict Summary + +| Check | Result | +|---|---| +| `bash -n` (4 changed shell scripts) | 4/4 OK | +| Changeset-specific tests (6) | 6/6 PASS | +| Full pytest suite (parallel run) | 346 passed, 0 failed (462.39s) | +| `--herdr-session` parsing consistency (4 scripts) | Consistent | +| `HERDR_SESSION_NAME` not clobbered when explicit | Verified (3 guard sites) | +| Companion script forwarding (resume โ†’ update_yaml) | Both call sites forward | +| Backward compat (`--herdr-server`, `HERDR_SERVER_NAME`) | Retained as alias/fallback | +| SKILL.md documentation | Updated, duplicate block removed | + +**Previous N-1 (resume post-spawn not forwarding `--herdr-session`): FIXED.** + +--- + +## ยง1. create_session.sh โ€” Guard Hardening + +### 1.1 Three guard sites verified + +All three sites now wrap the clobbering logic in `if [ -z "$HERDR_SERVER_OPT" ]; then โ€ฆ fi`, so an explicitly provided `--herdr-session` value is never overwritten: + +| Site | Location | Behavior when `--herdr-session` explicit | +|---|---|---| +| โ‘  ws_slug default | ~line 135 | Skipped โ€” `HERDR_SESSION_NAME` preserved | +| โ‘ก spawn() internal | ~line 174 | Skipped โ€” `HERDR_SESSION_NAME` preserved | +| โ‘ข post-spawn resolve | ~line 211 | Skipped โ€” `resolve_herdr_workspace` not called | + +**Line 78-80** (pre-guard): `export HERDR_SESSION_NAME="$HERDR_SERVER_OPT"` is set immediately after arg parsing, before any guard can interfere. โœ… + +### 1.2 Dry-run output + +```bash +echo "[dry-run] would spawn: herdr session '$SESSION_NAME' in $WORKSPACE (agent=$AGENT, herdr_session=${HERDR_SESSION_NAME:-default})" +``` +Correctly surfaces the resolved `herdr_session` value. โœ… + +### 1.3 YAML serialization + +`atomic_dump_yaml` receives `HERDR_SESSION_NAME="${HERDR_SESSION_NAME:-default}"` as an env var (line ~289). Inside the Python heredoc: + +```python +server_name = os.environ.get('HERDR_SESSION_NAME', 'default') # line ~297 +โ€ฆ +'herdr_session': server_name, # line ~309 +'herdr_server': server_name, # line ~310 +'start_command': f'HERDR_SESSION_NAME={server_name} herdr agent attach {name}', +'attach_command': f'HERDR_SESSION_NAME={server_name} herdr agent attach {name}', +'kill_command': f'HERDR_SESSION_NAME={server_name} herdr kill-session -t {name}', +``` + +All 5 fields (`herdr_session`, `herdr_server`, `start_command`, `attach_command`, `kill_command`) are derived from `server_name`. โœ… + +### 1.4 `--herdr-session default` edge case + +Test `test_comp_create_herdr_session_default_preserved` passes `--herdr-session default` and asserts `herdr_session == "default"` in YAML. The guard `if [ -z "$HERDR_SERVER_OPT" ]` is false (since `HERDR_SERVER_OPT="default"` is non-empty), so the ws_slug override is skipped and the literal `"default"` is preserved. โœ… +--- + +## ยง2. resume_session.sh โ€” Both Call Sites Forward `--herdr-session` + +### 2.1 Argument parsing + +```bash +--herdr-session|--herdr-server) HERDR_SERVER_OPT="$2"; shift 2 ;; +``` +Consistent with create/stop. `HERDR_SERVER_OPT=""` initialized โ†’ `set -u` safe. โœ… + +### 2.2 Export logic (lines 57-62) + +```bash +if [ -n "$HERDR_SERVER_OPT" ]; then + export HERDR_SESSION_NAME="$HERDR_SERVER_OPT" +else + HERDR_SESSION_NAME="$(resolve_herdr_session "$SESSION_NAME" "$WORKSPACE")" + export HERDR_SESSION_NAME +fi +``` +Explicit value takes priority; otherwise resolves from registry. โœ… + +### 2.3 Forwarding to update_yaml_resumed.sh โ€” BOTH call sites + +| Call site | Lines | Forwards `--herdr-session`? | +|---|---|---| +| Already-running path | 72-74 | โœ… `--herdr-session "$HERDR_SESSION_NAME"` | +| Post-spawn path | 136-138 | โœ… `--herdr-session "$HERDR_SESSION_NAME"` | + +**This fixes the N-1 from the prior review (job f03021cf)** where the post-spawn call at line 136 did not forward the flag. Both paths now propagate the resolved session name to the YAML updater. โœ… + +--- + +## ยง3. update_yaml_resumed.sh โ€” `HERDR_SERVER_OPT_EXPLICIT` Mechanism + +### 3.1 Explicit-tracking env var (lines 42-49) + +```bash +if [ -n "$HERDR_SERVER_OPT" ]; then + export HERDR_SESSION_NAME="$HERDR_SERVER_OPT" + export HERDR_SERVER_OPT_EXPLICIT="1" # explicit flag passed +else + HERDR_SESSION_NAME="$(resolve_herdr_session "$SESSION_NAME" "${WORKSPACE:-}")" + export HERDR_SESSION_NAME + export HERDR_SERVER_OPT_EXPLICIT="0" # resolved, not explicit +fi +``` + +This is a new, clean mechanism that distinguishes "user explicitly passed `--herdr-session`" from "value was resolved from registry/env". โœ… + +### 3.2 Propagation to Python heredoc (line 95) + +```bash +atomic_dump_yaml "$AGENT_SESSIONS_YAML" \ + โ€ฆ PANE_PID="$PANE_PID" CHILD_PID="$CHILD_PID" \ + HERDR_SERVER_OPT_EXPLICIT="${HERDR_SERVER_OPT_EXPLICIT:-0}" <<'PYEOF' +``` +The env var is forwarded to the Python subprocess. โœ… + +### 3.3 Else-branch conditional overwrite (lines 130-139) + +For an **existing** target row: + +```python +sn = os.environ.get('HERDR_SESSION_NAME') +is_explicit = os.environ.get('HERDR_SERVER_OPT_EXPLICIT') == '1' +if sn: + if is_explicit or not target.get('herdr_session'): + target['herdr_session'] = sn + target['herdr_server'] = sn + target['start_command'] = f'HERDR_SESSION_NAME={sn} herdr agent attach {name}' + target['attach_command'] = f'HERDR_SESSION_NAME={sn} herdr agent attach {name}' + target['kill_command'] = f'HERDR_SESSION_NAME={sn} herdr kill-session -t {name}' +``` + +| Scenario | `is_explicit` | `target['herdr_session']` exists | Action | +|---|---|---|---| +| Explicit `--herdr-session NEW` | 1 | yes (OLD) | **Overwrites** to NEW โœ… | +| Explicit `--herdr-session NEW` | 1 | no | Overwrites to NEW โœ… | +| Resolved (no flag) | 0 | yes | **Preserves** existing โœ… | +| Resolved (no flag) | 0 | no | **Backfills** from resolved sn โœ… | +| Resolved, sn absent | 0 | โ€” | Skips (no-op) โœ… | + +This is a significant improvement over the previous `setdefault`-only approach. When explicit, it always overwrites (fixing the orphan-registry edge case). When not explicit, it preserves the existing value and only backfills if missing. โœ… + +### 3.4 New-target path (lines 111-129) + +When the target row doesn't exist, a new entry is created with `server_name = os.environ.get('HERDR_SESSION_NAME', default_server)` and all 5 fields populated. โœ… + +--- + +## ยง4. stop_session.sh โ€” Consistent Parsing + +### 4.1 Argument parsing (line 77) + +```bash +--herdr-session|--herdr-server) HERDR_SERVER_OPT="$2"; shift 2 ;; +``` +Identical pattern to the other 3 scripts. โœ… + +### 4.2 Export logic (lines 104-109) + +```bash +if [ -n "$HERDR_SERVER_OPT" ]; then + export HERDR_SESSION_NAME="$HERDR_SERVER_OPT" +else + HERDR_SESSION_NAME="$(resolve_herdr_workspace "$SESSION_NAME" "${WORKSPACE:-$WORKSPACE_ROOT}")" + export HERDR_SESSION_NAME +fi +``` +Consistent with resume's pattern. โœ… + +### 4.3 Usage/docs updated + +Both the header comment (line 4, 19) and `usage()` (lines 44-45, 52) document `--herdr-session` with the `--herdr-server` alias. โœ… + +--- + +## ยง5. SKILL.md โ€” Documentation + +- **Title**: Renamed "Herdr Server Isolation (๊ฒฉ๋ฆฌ ์„œ๋ฒ„)" โ†’ "Herdr Session Isolation (๊ฒฉ๋ฆฌ ์„ธ์…˜)" โœ… +- **Primary names**: `HERDR_SESSION_NAME` env var and `--herdr-session` flag documented as standard โœ… +- **Alias note**: "(opt-in; alias: `--herdr-server`; legacy env alias: `HERDR_SERVER_NAME`)" โœ… +- **Duplicate "Recommended Alias" block removed**: The previous version had a misplaced/repeated paragraph. This is now cleaned up. โœ… +- **Wording fix**: "this now maps to" โ†’ "this maps to" (removed erroneous "now") โœ… +- **Migration examples**: Updated to use `HERDR_SESSION_NAME` and `--herdr-session` โœ… + +--- + +## ยง6. Backward Compatibility + +| Legacy mechanism | Status | Evidence | +|---|---|---| +| `--herdr-server` flag | Retained as alias in all 4 scripts | `--herdr-session\|--herdr-server)` parser case | +| `HERDR_SERVER_NAME` env var | Retained as fallback in `lib.sh` | `resolve_herdr_workspace`: `os.environ.get('HERDR_SESSION_NAME', '') or os.environ.get('HERDR_SERVER_NAME', '')` (line 1043) | +| `reconcile.sh` env fallback | Retained | `elif 'HERDR_SERVER_NAME' in os.environ:` (line 386-387) | +| Existing tests using `HERDR_SERVER_NAME` | Still pass | `test_tier1_unit.py`, `test_tier3_integration.py`, `test_workspace_scope.py` โ€” all in the 346 passed | + +Zero functionality loss. A user who has `HERDR_SERVER_NAME` exported or uses `--herdr-server` will see identical behavior. โœ… +--- + +## ยง7. Test Coverage + +### 7.1 Changeset-specific tests (6 total: 5 new + 1 modified) + +| Test | Feature | Status | Runtime | +|---|---|---|---| +| `test_comp_create_usage_matches_parser` | Create: usage docs + parser | PASS | 2.09s | +| `test_comp_create_herdr_session_cli_parsing_dry_run` | Create: `--herdr-session` + `--herdr-server` dry-run | PASS | 2.34s | +| `test_comp_create_herdr_session_default_preserved` | Create: `--herdr-session default` preserved | PASS | (batch 24.20s) | +| `test_comp_create_herdr_session_yaml_propagation` | Create: YAML field propagation (5 fields) | PASS | (batch 24.20s) | +| `test_comp_resume_herdr_session_propagation` | Resume: NEW overwrites OLD (N-1 fix) | PASS | 5.35s | +| `test_comp_stop_usage_matches_parser` (modified) | Stop: `--herdr-session` parser acceptance | PASS | 1.16s | + +### 7.2 Coverage assessment + +- **CLI parsing**: Both `--herdr-session` and `--herdr-server` tested in dry-run mode โœ… +- **Usage/parser matching**: Create + stop both verify usage() advertises flags that the parser accepts โœ… +- **YAML propagation**: `herdr_session`, `herdr_server`, `start_command`, `attach_command`, `kill_command` all asserted โœ… +- **Default preservation**: `--herdr-session default` edge case covered โœ… +- **Resume overwrite**: Explicit `--herdr-session NEW` overwriting `OLD` in existing row โ€” directly tests the N-1 fix โœ… + +### 7.3 Full suite + +A parallel full-suite run (by the claude reviewer) completed: **346 passed, 0 failed** (462.39s). This includes all changeset-specific tests plus tier1/tier3/tier4/integration/e2e suites. โœ… + +--- + +## ยง8. Non-blocking Observations + +### N-1 (FIXED โ€” no longer an issue) + +The previous review (job f03021cf) noted that `resume_session.sh` line 136-137 (post-spawn `update_yaml_resumed.sh` call) did not forward `--herdr-session`. **This is now fixed**: both call sites (already-running at line 72 and post-spawn at line 136) forward `--herdr-session "$HERDR_SESSION_NAME"`. Additionally, `update_yaml_resumed.sh` now uses the `HERDR_SERVER_OPT_EXPLICIT` mechanism to force-overwrite the existing row's `herdr_session`/`herdr_server`/commands when the flag is explicit. The new test `test_comp_resume_herdr_session_propagation` directly verifies this. โœ… + +### N-2 (pre-existing, out of scope) + +`deploy/install_mam.sh` (line 330), `deploy/install.sh` (line 524), and potentially `README.ko.md` still use `HERDR_SERVER_NAME` as the primary env var name in user-facing instructions. These are pre-existing references not introduced by this changeset and are out of scope. The legacy alias still works via `lib.sh`'s fallback, so there is no functional impact โ€” only documentation consistency. + +### N-3 (environmental, not a changeset defect) + +The environment has dozens of orphaned `reconcile.sh --subscribe --idle-timeout 0` daemon processes from prior test runs, plus a live herdr server. This slowed independent test execution but did not affect results โ€” the parallel full suite (346 passed) and all individually-run changeset tests confirm correctness. + +--- + +## ยง9. Conclusion + +This changeset is a clean, well-tested standardization of `--herdr-session` across the multi-agent-mux skill scripts. Key strengths: + +1. **Correctness**: All 3 guard sites in `create_session.sh` properly protect explicit values from being clobbered. +2. **Consistency**: All 4 scripts use the same `--herdr-session|--herdr-server) HERDR_SERVER_OPT="$2"` parsing pattern and the same `if [ -n "$HERDR_SERVER_OPT" ]` export logic. +3. **N-1 fix**: The previous review's blocking observation (resume post-spawn not forwarding `--herdr-session`) is fully addressed โ€” both call sites now forward, and `update_yaml_resumed.sh` uses `HERDR_SERVER_OPT_EXPLICIT` to force-overwrite when explicit. +4. **Backward compatibility**: `--herdr-server` flag and `HERDR_SERVER_NAME` env var are retained as aliases/fallbacks with zero functionality loss. +5. **Test coverage**: 6 changeset-specific tests (5 new + 1 modified) cover CLI parsing, usage/parser matching, default preservation, YAML field propagation, and resume overwrite. Full suite: 346 passed, 0 failed. + +No blocking issues found. No design-level rework needed. + +[VERDICT: PASS] \ No newline at end of file diff --git a/.agents/skills/multi-agent-mux-create/SKILL.md b/.agents/skills/multi-agent-mux-create/SKILL.md index 00402de..242d2e7 100644 --- a/.agents/skills/multi-agent-mux-create/SKILL.md +++ b/.agents/skills/multi-agent-mux-create/SKILL.md @@ -63,23 +63,23 @@ If any check fails โ†’ abort with a non-zero exit and report the reason (automat - contents: herdr new-session with `claude` inside, auto-handles trust/bypass dialogs - see `/agent_sessions.md` for the canonical wrapper template -## Herdr Server Isolation (๊ฒฉ๋ฆฌ ์„œ๋ฒ„) +## Herdr Session Isolation (๊ฒฉ๋ฆฌ ์„ธ์…˜) When running multiple agent sessions alongside other workflows (e.g., cmux, background workers, manual herdr sessions), sharing the default herdr server can lead to session name conflicts, monitoring clutter, and accidental destruction of user sessions via global commands. -To prevent this, you can run this skill inside an **isolated herdr server** using the `HERDR_SERVER_NAME` environment variable or the `--herdr-server ` flag (opt-in). +To prevent this, you can run this skill inside an **isolated herdr session** using the `HERDR_SESSION_NAME` environment variable or the `--herdr-session ` flag (opt-in; alias: `--herdr-server`; legacy env alias: `HERDR_SERVER_NAME`). -Under the hood this now maps to a real, separate herdr **session** (`herdr --session ` โ€” its own socket, its own `agent list`/`workspace list`, completely invisible to the default session and vice versa), not just a workspace label inside the same server. `lib.sh`'s shim bootstraps the named session's server headlessly (`herdr --session server`, backgrounded) the first time it's needed, and scopes every subsequent herdr call to it automatically โ€” this headless bootstrap is what lets it work even when the skill itself is running from inside another herdr-managed pane (a plain interactive `herdr --session ` launch is blocked there by herdr's "nested herdr is disabled" guard; headless `server` mode isn't). +Under the hood this maps to a real, separate herdr **session** (`herdr --session ` โ€” its own socket, its own `agent list`/`workspace list`, completely invisible to the default session and vice versa), not just a workspace label inside the same server. `lib.sh`'s shim bootstraps the named session's server headlessly (`herdr --session server`, backgrounded) the first time it's needed, and scopes every subsequent herdr call to it automatically โ€” this headless bootstrap is what lets it work even when the skill itself is running from inside another herdr-managed pane (a plain interactive `herdr --session ` launch is blocked there by herdr's "nested herdr is disabled" guard; headless `server` mode isn't). ### How to use 1. **Via Environment Variable**: ```bash - export HERDR_SERVER_NAME=multi-agent-canary - # All subsequent commands (create, status, stop, etc.) will run in the isolated 'multi-agent-canary' herdr server. + export HERDR_SESSION_NAME=multi-agent-canary + # All subsequent commands (create, status, stop, etc.) will run in the isolated 'multi-agent-canary' herdr session. ``` 2. **Via Option Flag**: ```bash - bash scripts/create_session.sh --workspace /path/to/project --agent claude --role developer --herdr-server multi-agent-canary + bash scripts/create_session.sh --workspace /path/to/project --agent claude --role developer --herdr-session multi-agent-canary ``` 3. **Submit Job Integration**: You can automatically register a delegated job with a prompt when creating a session: @@ -92,25 +92,10 @@ Under the hood this now maps to a real, separate herdr **session** (`herdr --ses bash scripts/create_session.sh --workspace /path/to/project --agent claude --role developer --onboard ``` -### Recommended Alias -You can set an alias in your shell to easily query sessions on the isolated server: -To prevent this, you can run this skill inside an **isolated herdr session** using the `HERDR_SESSION_NAME` environment variable or the `--herdr-session ` flag (opt-in). - -```bash -# Explicit custom session -export HERDR_SESSION_NAME=multi-agent-canary -bash .agents/skills/multi-agent-mux-create/scripts/create_session.sh \ - --workspace /path/to/project --agent claude --role Developer - -# Or via flag -bash .agents/skills/multi-agent-mux-create/scripts/create_session.sh \ - --workspace /path/to/project --agent claude --role Developer --herdr-session multi-agent-canary -``` - Why use `--herdr-session`? - By default, all skills target `default` herdr session socket โ€” fine for single-workspace use. -- By using an isolated session via `HERDR_SESSION_NAME`, your agent sessions are completely separated from your default user workspace, ensuring 0% interference โ€” this is now backed by a genuinely separate `herdr` session/socket, not merely a workspace label. +- By using an isolated session via `HERDR_SESSION_NAME` (or `--herdr-session`), your agent sessions are completely separated from your default user workspace, ensuring 0% interference โ€” this is backed by a genuinely separate `herdr` session/socket, not merely a workspace label. - To deliberately tear down an *entire* isolated group at once (all its workspaces and agents), use `herdr session stop ` followed by `herdr session delete ` โ€” this only affects that named session, never the default one. --- @@ -192,7 +177,8 @@ After spawn, append a new `herdr_sessions[]` entry to `.mam/agent-sessions.yaml` status: running herdr_session_created_at: 2026-06-17T...Z # ISO 8601 UTC herdr_session_epoch: - herdr_server: # Isolated server name (default: 'default') + herdr_session: # Isolated session name (default: 'mam-') + herdr_server: # Alias for herdr_session pane: index: 0 pid: @@ -205,13 +191,13 @@ After spawn, append a new `herdr_sessions[]` entry to `.mam/agent-sessions.yaml` plan: account: version: - start_command: "HERDR_SERVER_NAME= herdr new-session -d -s -x 140 -y 40 -c " - attach_command: "HERDR_SERVER_NAME= herdr agent attach " - kill_command: "HERDR_SERVER_NAME= herdr kill-session -t " + start_command: "HERDR_SESSION_NAME= herdr new-session -d -s -x 140 -y 40 -c " + attach_command: "HERDR_SESSION_NAME= herdr agent attach " + kill_command: "HERDR_SESSION_NAME= herdr kill-session -t " # All three require `source .agents/skills/lib.sh` first โ€” `new-session`/`kill-session` - # are tmux-compat pseudo-commands the shim translates, and `HERDR_SERVER_NAME` is what + # are tmux-compat pseudo-commands the shim translates, and `HERDR_SESSION_NAME` is what # the shim reads to route to the right isolated herdr *session* (real `herdr` has no - # env-var-based scoping of its own; `herdr_server: default` needs no prefix at all). + # env-var-based scoping of its own; `herdr_session: default` needs no prefix at all). ``` `cmd_full` per agent (this is the actual command line in the pane, not the resume command): diff --git a/.agents/skills/multi-agent-mux-create/scripts/create_session.sh b/.agents/skills/multi-agent-mux-create/scripts/create_session.sh index f532e48..cb48ace 100755 --- a/.agents/skills/multi-agent-mux-create/scripts/create_session.sh +++ b/.agents/skills/multi-agent-mux-create/scripts/create_session.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # create_session.sh โ€” multi-agent-mux-create ์˜ ๋ถ€์† ์Šคํฌ๋ฆฝํŠธ # Usage: -# bash create_session.sh --workspace --agent --role [--session ] [--wrapper] +# bash create_session.sh --workspace --agent --role [--session ] [--herdr-session ] [--wrapper] # # ๋™์ž‘: # 1) preflight: herdr/claude/agy ๊ฐ€์šฉ์„ฑ, workspace ์กด์žฌ @@ -35,7 +35,8 @@ Options: --session NAME herdr session name (default: derived from workspace) --wrapper force use of ~/.local/bin/ wrapper even if not present --dry-run print commands without executing - --herdr-server NAME specify isolated herdr server name + --herdr-session NAME specify isolated herdr session name (alias: --herdr-server) + --herdr-server NAME specify isolated herdr session name (legacy alias) --submit-job PROMPT submit a job to multi-agent-mux-delegate-job registry with the given prompt --onboard automatically submit a project alignment/orientation job to the new agent --no-onboard disable automatic onboarding job submission @@ -136,8 +137,10 @@ LOCAL_BIN="${LOCAL_BIN:-$HOME/.local/bin}" WRAPPER="$LOCAL_BIN/$SESSION_NAME" ws_slug="$(derive_workspace_slug "$WORKSPACE")" -if [ -z "${HERDR_SESSION_NAME:-}" ] || [ "$HERDR_SESSION_NAME" = "default" ]; then - export HERDR_SESSION_NAME="$ws_slug" +if [ -z "$HERDR_SERVER_OPT" ]; then + if [ -z "${HERDR_SESSION_NAME:-}" ] || [ "$HERDR_SESSION_NAME" = "default" ]; then + export HERDR_SESSION_NAME="$ws_slug" + fi fi # Resolve absolute path of the agent command to prevent herdr PATH inheritance issues (especially on macOS) @@ -170,8 +173,10 @@ if [ -z "$CMD_FULL" ]; then fi spawn() { - if [ -z "${HERDR_SESSION_NAME:-}" ] || [ "$HERDR_SESSION_NAME" = "default" ]; then - export HERDR_SESSION_NAME="$ws_slug" + if [ -z "$HERDR_SERVER_OPT" ]; then + if [ -z "${HERDR_SESSION_NAME:-}" ] || [ "$HERDR_SESSION_NAME" = "default" ]; then + export HERDR_SESSION_NAME="$ws_slug" + fi fi case "$AGENT" in claude) @@ -192,7 +197,7 @@ spawn() { } if [ "$DRY_RUN" = "1" ]; then - echo "[dry-run] would spawn: herdr session '$SESSION_NAME' in $WORKSPACE (agent=$AGENT)" + echo "[dry-run] would spawn: herdr session '$SESSION_NAME' in $WORKSPACE (agent=$AGENT, herdr_session=${HERDR_SESSION_NAME:-default})" exit 0 fi @@ -208,8 +213,10 @@ cleanup_herdr_on_error() { } trap cleanup_herdr_on_error EXIT -RESOLVED_SERVER="$(resolve_herdr_workspace "$SESSION_NAME" "$WORKSPACE")" -export HERDR_SESSION_NAME="${HERDR_SESSION_NAME:-$RESOLVED_SERVER}" +if [ -z "$HERDR_SERVER_OPT" ]; then + RESOLVED_SERVER="$(resolve_herdr_workspace "$SESSION_NAME" "$WORKSPACE")" + export HERDR_SESSION_NAME="${HERDR_SESSION_NAME:-$RESOLVED_SERVER}" +fi # TUI ์ค€๋น„ ๋Œ€๊ธฐ if ! wait_for_tui_ready "$SESSION_NAME" "$AGENT"; then diff --git a/.agents/skills/multi-agent-mux-resume/scripts/resume_session.sh b/.agents/skills/multi-agent-mux-resume/scripts/resume_session.sh index d5ed58c..a5fa5b7 100755 --- a/.agents/skills/multi-agent-mux-resume/scripts/resume_session.sh +++ b/.agents/skills/multi-agent-mux-resume/scripts/resume_session.sh @@ -9,17 +9,19 @@ source "$LIB_SH" usage() { cat < --agent --session [--dry-run] +Usage: $0 --workspace --agent --session [options] Options: - --dry-run Simulates resume flow (resolves binary, environment) without writing - any updates to YAML or DB. Safe to execute inside active write transactions. + --herdr-session NAME specify isolated herdr session name (alias: --herdr-server) + --dry-run Simulates resume flow (resolves binary, environment) without writing + any updates to YAML or DB. Safe to execute inside active write transactions. EOF } WORKSPACE="" AGENT="" SESSION_NAME="" +HERDR_SERVER_OPT="" DRY_RUN=0 @@ -28,6 +30,7 @@ while [ $# -gt 0 ]; do --workspace) WORKSPACE="$2"; shift 2 ;; --agent) AGENT="$2"; shift 2 ;; --session) SESSION_NAME="$2"; shift 2 ;; + --herdr-session|--herdr-server) HERDR_SERVER_OPT="$2"; shift 2 ;; --dry-run) DRY_RUN=1; shift ;; -h|--help) usage; exit 0 ;; *) echo "ERROR: unknown arg: $1" >&2; exit 2 ;; @@ -51,8 +54,12 @@ if [ -z "$UUID" ]; then exit 1 fi -HERDR_SESSION_NAME="$(resolve_herdr_session "$SESSION_NAME" "$WORKSPACE")" -export HERDR_SESSION_NAME +if [ -n "$HERDR_SERVER_OPT" ]; then + export HERDR_SESSION_NAME="$HERDR_SERVER_OPT" +else + HERDR_SESSION_NAME="$(resolve_herdr_session "$SESSION_NAME" "$WORKSPACE")" + export HERDR_SESSION_NAME +fi # 2. If herdr is alive, print warning or attach. if herdr has-session -t "$SESSION_NAME" 2>/dev/null; then @@ -63,7 +70,8 @@ if herdr has-session -t "$SESSION_NAME" 2>/dev/null; then echo "herdr '$SESSION_NAME' already running." # Just update YAML to make sure it's set to running bash "$(dirname "${BASH_SOURCE[0]}")/update_yaml_resumed.sh" \ - --session "$SESSION_NAME" --uuid "$UUID" --agent "$AGENT" --workspace "$WORKSPACE" + --session "$SESSION_NAME" --uuid "$UUID" --agent "$AGENT" --workspace "$WORKSPACE" \ + --herdr-session "$HERDR_SESSION_NAME" exit 0 fi @@ -126,6 +134,7 @@ sleep 2 # 5. Update agent-sessions.yaml: status running, last_visible_status bash "$(dirname "${BASH_SOURCE[0]}")/update_yaml_resumed.sh" \ - --session "$SESSION_NAME" --uuid "$UUID" --agent "$AGENT" --workspace "$WORKSPACE" + --session "$SESSION_NAME" --uuid "$UUID" --agent "$AGENT" --workspace "$WORKSPACE" \ + --herdr-session "$HERDR_SESSION_NAME" echo "Successfully resumed $SESSION_NAME ($AGENT)" diff --git a/.agents/skills/multi-agent-mux-resume/scripts/update_yaml_resumed.sh b/.agents/skills/multi-agent-mux-resume/scripts/update_yaml_resumed.sh index 593e665..a251731 100755 --- a/.agents/skills/multi-agent-mux-resume/scripts/update_yaml_resumed.sh +++ b/.agents/skills/multi-agent-mux-resume/scripts/update_yaml_resumed.sh @@ -11,7 +11,7 @@ source "$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/lib.sh" usage() { cat < --uuid [--agent claude|agy|hermes|cline] +Usage: $0 --session --uuid [--agent claude|agy|hermes|cline] [--herdr-session ] EOF } @@ -20,6 +20,7 @@ UUID="" AGENT="" WORKSPACE="" ROLE="" +HERDR_SERVER_OPT="" while [ $# -gt 0 ]; do case "$1" in @@ -28,6 +29,7 @@ while [ $# -gt 0 ]; do --agent) AGENT="$2"; shift 2 ;; --workspace) WORKSPACE="$2"; shift 2 ;; --role) ROLE="$2"; shift 2 ;; + --herdr-session|--herdr-server) HERDR_SERVER_OPT="$2"; shift 2 ;; -h|--help) usage; exit 0 ;; *) echo "ERROR: unknown arg: $1" >&2; exit 2 ;; esac @@ -37,8 +39,14 @@ done [ -n "$UUID" ] || { echo "ERROR: --uuid required" >&2; exit 2; } [ -f "$AGENT_SESSIONS_YAML" ] || { echo "ERROR: $AGENT_SESSIONS_YAML not found" >&2; exit 1; } -HERDR_SESSION_NAME="$(resolve_herdr_session "$SESSION_NAME" "${WORKSPACE:-}")" -export HERDR_SESSION_NAME +if [ -n "$HERDR_SERVER_OPT" ]; then + export HERDR_SESSION_NAME="$HERDR_SERVER_OPT" + export HERDR_SERVER_OPT_EXPLICIT="1" +else + HERDR_SESSION_NAME="$(resolve_herdr_session "$SESSION_NAME" "${WORKSPACE:-}")" + export HERDR_SESSION_NAME + export HERDR_SERVER_OPT_EXPLICIT="0" +fi # --agent ๋ฏธ์ง€์ • ์‹œ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๊ธฐ๋ก์œผ๋กœ ํ•ด์„ (B-21). # โ‘  row['agent'] โ†’ โ‘ก ์„ธ์…˜๋ช… ์ ‘๋ฏธ์‚ฌ โ†’ โ‘ข pane.cmd ์ˆœ. ์…‹ ๋‹ค ์‹คํŒจํ•˜๋ฉด @@ -84,7 +92,7 @@ for s in d.get('herdr_sessions', []): atomic_dump_yaml "$AGENT_SESSIONS_YAML" \ SESSION_NAME="$SESSION_NAME" UUID="$UUID" AGENT="$AGENT" NOW_ISO="$NOW_ISO" \ NOW_EPOCH="$NOW_EPOCH" TARGET_WORKSPACE="${WORKSPACE:-$WORKSPACE_ROOT}" ROLE="$ROLE" \ - PANE_PID="$PANE_PID" CHILD_PID="$CHILD_PID" <<'PYEOF' + PANE_PID="$PANE_PID" CHILD_PID="$CHILD_PID" HERDR_SERVER_OPT_EXPLICIT="${HERDR_SERVER_OPT_EXPLICIT:-0}" <<'PYEOF' name = os.environ['SESSION_NAME'] uuid = os.environ['UUID'] agent = os.environ['AGENT'] @@ -111,6 +119,7 @@ if target is None: 'herdr_session_created_at': now, 'herdr_session_epoch': epoch, 'herdr_session': server_name, + 'herdr_server': server_name, 'delegate_job_id': None, 'pane': {'index': 0, 'pid': int(pane_pid) if pane_pid.isdigit() else 0, 'cmd': agent, 'cwd': ws_root}, 'start_command': f'HERDR_SESSION_NAME={server_name} herdr agent attach {name}', @@ -118,6 +127,16 @@ if target is None: 'kill_command': f'HERDR_SESSION_NAME={server_name} herdr kill-session -t {name}', } d.setdefault('herdr_sessions', []).append(target) +else: + sn = os.environ.get('HERDR_SESSION_NAME') + is_explicit = os.environ.get('HERDR_SERVER_OPT_EXPLICIT') == '1' + if sn: + if is_explicit or not target.get('herdr_session'): + target['herdr_session'] = sn + target['herdr_server'] = sn + target['start_command'] = f'HERDR_SESSION_NAME={sn} herdr agent attach {name}' + target['attach_command'] = f'HERDR_SESSION_NAME={sn} herdr agent attach {name}' + target['kill_command'] = f'HERDR_SESSION_NAME={sn} herdr kill-session -t {name}' target['status'] = 'running' target.pop('terminated_at', None) diff --git a/.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh b/.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh index 59c7dd4..5943502 100755 --- a/.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh +++ b/.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # stop_session.sh โ€” multi-agent-mux-stop ์˜ ๋ถ€์† ์Šคํฌ๋ฆฝํŠธ # Usage: -# bash stop_session.sh --session [--agent claude|agy|hermes|cline] \ +# bash stop_session.sh --session [--agent claude|agy|hermes|cline] [--herdr-session ] \ # [--reason ] [--purge-conversation] [--yes] # # ๋™์ž‘: ํ•ญ์ƒ graceful stop ์ž…๋‹ˆ๋‹ค. send-keys ๋กœ ์ •์ƒ ์ข…๋ฃŒ๋ฅผ ์œ ๋„ํ•˜๊ณ  @@ -16,6 +16,7 @@ # (๊ถŒ์žฅ: ํ•ญ์ƒ ๋ช…์‹œ. ๋ฏธ์ง€์ • ์‹œ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๊ธฐ๋ก์œผ๋กœ # ํ•ด์„ โ€” agent ํ•„๋“œ โ†’ ์„ธ์…˜๋ช… ์ ‘๋ฏธ์‚ฌ โ†’ pane.cmd; # ์…‹ ๋‹ค ์‹คํŒจํ•˜๋ฉด exit 2) +# --herdr-session โ€” isolated herdr session name (alias: --herdr-server) # --reason โ€” ์ƒํƒœ ์ „์ด ์‚ฌ์œ  (stop_reason). ๊ธฐ๋ณธ๊ฐ’ manual_stop # --purge-conversation โ€” ๋””์Šคํฌ์˜ conversation artifact ๊นŒ์ง€ ์‚ญ์ œ. # status=terminated, resumable=false ๋กœ ์ „์ดํ•˜๋ฉฐ @@ -40,14 +41,15 @@ source "$_lib_sh" usage() { cat < [--agent claude|agy|hermes|cline] [--reason ] - [--purge-conversation] [--yes] +Usage: $0 --session [--agent claude|agy|hermes|cline] [--herdr-session ] + [--reason ] [--purge-conversation] [--yes] Arguments: --session โ€” target session name (required) --agent โ€” claude | agy | hermes | cline (recommended: always pass it) (falls back to the registry record: agent field -> session-name suffix -> pane.cmd) + --herdr-session โ€” specify isolated herdr session name (alias: --herdr-server) --reason โ€” stop_reason field (default: manual_stop) --purge-conversation โ€” also delete on-disk conversation artifacts; status becomes terminated and resume is impossible @@ -60,6 +62,7 @@ EOF SESSION_NAME="" AGENT="" +HERDR_SERVER_OPT="" PURGE=0 YES=0 CAPTURE_ID=1 @@ -71,6 +74,7 @@ while [ $# -gt 0 ]; do case "$1" in --session) SESSION_NAME="$2"; shift 2 ;; --agent) AGENT="$2"; shift 2 ;; + --herdr-session|--herdr-server) HERDR_SERVER_OPT="$2"; shift 2 ;; --purge-conversation) PURGE=1; shift ;; --yes) YES=1; shift ;; --reason) REASON="$2"; shift 2 ;; @@ -97,8 +101,12 @@ if [ "$PURGE" = "1" ]; then trap 'rm -f "$WORKSPACE_ROOT/.mam/purging-$SESSION_NAME"' EXIT fi -HERDR_SESSION_NAME="$(resolve_herdr_workspace "$SESSION_NAME" "${WORKSPACE:-$WORKSPACE_ROOT}")" -export HERDR_SESSION_NAME +if [ -n "$HERDR_SERVER_OPT" ]; then + export HERDR_SESSION_NAME="$HERDR_SERVER_OPT" +else + HERDR_SESSION_NAME="$(resolve_herdr_workspace "$SESSION_NAME" "${WORKSPACE:-$WORKSPACE_ROOT}")" + export HERDR_SESSION_NAME +fi # --agent ๋ฏธ์ง€์ • ์‹œ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๊ธฐ๋ก์œผ๋กœ ํ•ด์„ (B-21). # โ‘  row['agent'] โ†’ โ‘ก ์„ธ์…˜๋ช… ์ ‘๋ฏธ์‚ฌ โ†’ โ‘ข pane.cmd ์ˆœ. ์…‹ ๋‹ค ์‹คํŒจํ•˜๋ฉด diff --git a/tests/test_tier2_component.py b/tests/test_tier2_component.py index 9c307e4..6e8cd5f 100644 --- a/tests/test_tier2_component.py +++ b/tests/test_tier2_component.py @@ -30,7 +30,7 @@ def get_mqtt_common(mam_sandbox): # ============================================================================== -# FEATURE 1: Create Session (5 Test Cases) +# FEATURE 1: Create Session (9 Test Cases) # ============================================================================== def test_comp_create_schema_validation(mam_sandbox): @@ -133,8 +133,122 @@ def test_comp_create_sqlite_tables_created(mam_sandbox, mock_herdr, mock_agents) conn.close() +def test_comp_create_usage_matches_parser(mam_sandbox, mock_herdr, mock_agents): + """Verify that create_session.sh usage documents --herdr-session and parser accepts it.""" + script = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-create" / "scripts" / "create_session.sh" + res = subprocess.run(["bash", str(script), "--help"], capture_output=True, text=True) + assert res.returncode == 0 + assert "--herdr-session" in res.stdout + assert "--herdr-server" in res.stdout + for agent in ("claude", "agy", "hermes", "cline"): + assert agent in res.stdout + + # Test parser acceptance of valid flags vs unknown arg rejection + r1 = subprocess.run([ + "bash", str(script), + "--workspace", str(mam_sandbox), + "--agent", "claude", + "--role", "Creator", + "--herdr-session", "test-sess", + "--dry-run" + ], capture_output=True, text=True) + assert r1.returncode == 0 + assert "unknown arg" not in r1.stderr + + r2 = subprocess.run([ + "bash", str(script), + "--invalid-flag-xyz" + ], capture_output=True, text=True) + assert r2.returncode == 2 + assert "unknown arg" in r2.stderr + + +def test_comp_create_herdr_session_cli_parsing_dry_run(mam_sandbox, mock_herdr, mock_agents): + """Verify that --herdr-session and --herdr-server are parsed cleanly in --dry-run mode.""" + script = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-create" / "scripts" / "create_session.sh" + res1 = subprocess.run([ + "bash", str(script), + "--workspace", str(mam_sandbox), + "--agent", "claude", + "--role", "Creator", + "--herdr-session", "test-isolated-sess", + "--dry-run" + ], capture_output=True, text=True) + assert res1.returncode == 0 + assert "[dry-run] would spawn:" in res1.stdout + assert "herdr_session=test-isolated-sess" in res1.stdout + + res2 = subprocess.run([ + "bash", str(script), + "--workspace", str(mam_sandbox), + "--agent", "claude", + "--role", "Creator", + "--herdr-server", "test-isolated-srv", + "--dry-run" + ], capture_output=True, text=True) + assert res2.returncode == 0 + assert "[dry-run] would spawn:" in res2.stdout + assert "herdr_session=test-isolated-srv" in res2.stdout + + +def test_comp_create_herdr_session_default_preserved(mam_sandbox, mock_herdr, mock_agents): + """Verify --herdr-session default is honored and not overwritten by workspace slug.""" + script = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-create" / "scripts" / "create_session.sh" + res = subprocess.run([ + "bash", str(script), + "--workspace", str(mam_sandbox), + "--agent", "claude", + "--role", "Creator", + "--session", "custom-proj-default-claude", + "--herdr-session", "default" + ], capture_output=True, text=True) + assert res.returncode == 0, res.stderr + + yaml_path = mam_sandbox / ".mam" / "agent-sessions.yaml" + import yaml + with open(yaml_path) as f: + data = yaml.safe_load(f) + sessions = data.get("herdr_sessions", []) + assert len(sessions) == 1 + s = sessions[0] + assert s["name"] == "custom-proj-default-claude" + assert s["herdr_session"] == "default" + assert s["herdr_server"] == "default" + assert "HERDR_SESSION_NAME=default" in s["start_command"] + assert "HERDR_SESSION_NAME=default" in s["attach_command"] + assert "HERDR_SESSION_NAME=default" in s["kill_command"] + + +def test_comp_create_herdr_session_yaml_propagation(mam_sandbox, mock_herdr, mock_agents): + """Verify HERDR_SESSION_NAME propagation into start_command / herdr_session YAML field.""" + script = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-create" / "scripts" / "create_session.sh" + res = subprocess.run([ + "bash", str(script), + "--workspace", str(mam_sandbox), + "--agent", "claude", + "--role", "Creator", + "--session", "custom-proj-creator-claude", + "--herdr-session", "isolated-suite-01" + ], capture_output=True, text=True) + assert res.returncode == 0, res.stderr + + yaml_path = mam_sandbox / ".mam" / "agent-sessions.yaml" + import yaml + with open(yaml_path) as f: + data = yaml.safe_load(f) + sessions = data.get("herdr_sessions", []) + assert len(sessions) == 1 + s = sessions[0] + assert s["name"] == "custom-proj-creator-claude" + assert s["herdr_session"] == "isolated-suite-01" + assert s["herdr_server"] == "isolated-suite-01" + assert "HERDR_SESSION_NAME=isolated-suite-01" in s["start_command"] + assert "HERDR_SESSION_NAME=isolated-suite-01" in s["attach_command"] + assert "HERDR_SESSION_NAME=isolated-suite-01" in s["kill_command"] + + # ============================================================================== -# FEATURE 2: Resume Session (5 Test Cases) +# FEATURE 2: Resume Session (6 Test Cases) # ============================================================================== def test_comp_resume_config_restore(mam_sandbox): @@ -293,6 +407,51 @@ d['herdr_sessions'] = [{ assert res.stdout.strip() == "scanned-uuid" +def test_comp_resume_herdr_session_propagation(mam_sandbox, mock_herdr, mock_agents): + """Verify that resume_session.sh with --herdr-session updates existing row's herdr_session.""" + conv_id = "11111111-2222-3333-4444-555555555555" + key = str(mam_sandbox).replace('/', '-').replace('_', '-') + proj_dir = mam_sandbox / ".claude" / "projects" / key + proj_dir.mkdir(parents=True, exist_ok=True) + (proj_dir / f"{conv_id}.jsonl").write_text(f'{{"sessionId": "{conv_id}"}}') + + # Seed a stopped session with OLD herdr_session + mutation = f""" +d['herdr_sessions'] = [{{ + 'name': 'test-proj-creator-claude', + 'status': 'stopped', + 'herdr_session': 'OLD-HERDR-SESSION', + 'herdr_server': 'OLD-HERDR-SESSION', + 'claude_session_id_own': '{conv_id}', + 'pane': {{'cwd': '{str(mam_sandbox)}', 'cmd': 'claude'}} +}}] +""" + run_mutation(mam_sandbox, mutation) + + script = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-resume" / "scripts" / "resume_session.sh" + res = subprocess.run([ + "bash", str(script), + "--workspace", str(mam_sandbox), + "--agent", "claude", + "--session", "test-proj-creator-claude", + "--herdr-session", "NEW-HERDR-SESSION" + ], capture_output=True, text=True) + assert res.returncode == 0, res.stderr + + yaml_path = mam_sandbox / ".mam" / "agent-sessions.yaml" + import yaml + with open(yaml_path) as f: + data = yaml.safe_load(f) + sessions = data.get("herdr_sessions", []) + assert len(sessions) == 1 + s = sessions[0] + assert s["status"] == "running" + assert s["herdr_session"] == "NEW-HERDR-SESSION" + assert s["herdr_server"] == "NEW-HERDR-SESSION" + assert "HERDR_SESSION_NAME=NEW-HERDR-SESSION" in s["attach_command"] + assert "HERDR_SESSION_NAME=NEW-HERDR-SESSION" in s["kill_command"] + + # ============================================================================== # FEATURE 3: Stop Session (7 Test Cases) # ============================================================================== @@ -780,7 +939,8 @@ def test_comp_stop_usage_matches_parser(mam_sandbox): for flag, args in (("--reason", ["--reason", "x"]), ("--purge-conversation", ["--purge-conversation"]), ("--yes", ["--yes"]), - ("--agent", ["--agent", "hermes"])): + ("--agent", ["--agent", "hermes"]), + ("--herdr-session", ["--herdr-session", "isolated-sess"])): r = subprocess.run(["bash", str(script), "--session", VALID] + args, capture_output=True, text=True) assert "unknown arg" not in r.stderr, f"usage() advertises {flag} but parser rejects it: {r.stderr}"