# 리뷰 및 보완 구현 계획서 Rev.2 — A-1 / A-5 / .mam.env

**Job**: `4dbf4feb` | **Role**: Planner | **작성일**: 2026-08-05
**대체 대상**: Rev.1 (`e691297c`) — 본 문서가 우선한다
**반영 피드백**: Creator `agy` Challenge Report `3daf49ab`
**리뷰 대상 코드**: `68eff79..8dcb2b2` + 미커밋 워킹트리 1건 (변동 없음)

Rev.1의 §1~§11은 아래에서 명시적으로 수정하지 않은 한 그대로 유효하다.
본 문서는 **R-3 / F4 하나**를 재설계하고, 그 과정에서 발견한 결함 1건을 추가한다.

---

## 0. 이의제기 판정

`agy`의 주장을 넷으로 분해해 각각 실행으로 검증했다.

| # | `agy`의 주장 | 판정 | 근거 |
|---|---|---|---|
| 1 | `derive_session_name`은 `agent_type`을 요구하는데 `resolve_herdr_session`에는 그 정보가 없어 **폴백 로직이 붕괴**한다 | **반증(사유), 인정(증상)** | 슬러그는 agent 인자 유무와 **무관하게 동일**하다(E-A). 붕괴하는 진짜 원인은 정보 부재가 아니라 `set -u` 하의 **인자 개수**이며, `${2:-}` 한 글자로 해소된다 |
| 2 | herdr 세션명은 워크스페이스 수준이어야 하고, create의 `sed 's/-creator-.*//'`가 그 증거다 | **인정** | 옳다. 전용 헬퍼를 두는 **형태(shape)는 채택**한다 |
| 3 | 대안 — `derive_workspace_slug` 신설 후 양쪽에서 직접 호출 | **구현 기각** | 제시된 구현이 `create_session.sh`와 **4개 경로 전수 불일치**(E-B). F4가 없애려던 이중 규칙을 **세 번째 규칙**으로 되살린다 |
| 4 | 그 헬퍼를 `resolve_herdr_session` 폴백에서 `derive_workspace_slug "$WORKSPACE"`로 호출 | **설계 기각** | `resolve_herdr_session` 스코프에는 `$WORKSPACE`도 **없다**(E-C). `${1:-$PWD}` 기본값이 오늘의 cwd 의존을 그대로 물려받아, 같은 세션명이 호출 위치마다 다른 herdr 세션으로 해석된다(E-D) |

**요약**: `agy`는 **옳은 형태를 틀린 이유로, 틀린 구현과 함께** 제안했다.
헬퍼 도입은 채택한다. 다만 규칙을 새로 쓰는 대신 **기존 규칙을 추출**해야 하고,
빠진 파라미터는 `$AGENT`가 아니라 `$WORKSPACE`다 — `agy`는 자신이 진단한 결여를
자기 처방에서 그대로 반복했다.

---

## 1. 신규 측정 증거

### E-A — agent 인자는 슬러그에 아무 영향이 없다

```
$ derive_session_name "$WS" claude | sed 's/-creator-.*//'   ->  [parent-dir-my-project]
$ derive_session_name "$WS"        | sed 's/-creator-.*//'   ->  [parent-dir-my-project]
$ derive_session_name "$WS" ""     | sed 's/-creator-.*//'   ->  [parent-dir-my-project]
```

`derive_session_name`은 `printf '%s-creator-%s' "$slug" "$agent"`로 끝난다.
슬러그는 **workspace 경로만으로** 계산되고 agent는 접미사에만 쓰인다.
`sed`가 그 접미사를 잘라내므로 agent가 비어 있어도 결과가 같다.
따라서 "에이전트 타입 정보 부재로 폴백이 붕괴한다"는 인과는 성립하지 않는다.

**다만 붕괴 자체는 실재한다 — 원인이 다르다.**

```
$ set -u; derive_session_name "$WS"
  ABORT: .agents/skills/lib.sh: line 595: $2: unbound variable
```

그리고 `resolve_herdr_session`을 호출하는 스크립트는 **8개 전부** `set -euo pipefail`이다:

```
create_session.sh  run_loop.sh  resume_session.sh  reconcile.sh
resolve_session_id.sh  update_yaml_resumed.sh  status.sh  stop_session.sh
```

즉 실제 위험은 **셸 엄격 모드에서의 인자 개수**이고, `local agent="${2:-}"` 로 끝난다.
`agy`의 결론(직접 호출하지 말라)은 방어 가능하나, 제시한 이유는 틀렸고
그 이유를 근거로 설계를 바꾸면 엉뚱한 곳을 고치게 된다.

### E-B — `agy`가 제시한 구현은 4개 경로 전수 불일치

챌린지 리포트의 함수를 **원문 그대로** 옮겨 `create_session.sh`의 실제 산출물과 대조했다.

```
  workspace                create_session.sh writes      agy derive_workspace_slug
  parent_dir/my_project    mam-parent-dir-my-project      mam-parentdir-myproject      ** MISMATCH **
  Upper_Case/Web_App       mam-upper-case-web-app         mam-uppercase-webapp         ** MISMATCH **
  /tmp                     mam-workspace-tmp              mam--tmp                     ** MISMATCH **
  /                        mam-workspace-root             mam--                        ** MISMATCH **
```

원인 두 가지:

1. **밑줄 처리가 반대다.** `derive_session_name`은 `tr '_' '-'`로 **변환**하는데,
   `agy`의 구현은 `tr -cd 'a-z0-9-'`로 **삭제**한다. `my_project`가
   `my-project`가 아니라 `myproject`가 된다.
2. **경계 가드가 없다.** `derive_session_name`은 부모가 `/`·`.`·빈 문자열일 때
   `workspace`를, 작업 디렉터리가 그럴 때 `root`를 대입하고 선행 하이픈을 제거한다.
   `agy`의 구현에는 이 가드가 전부 없어 `/tmp`에서 `mam--tmp`,
   루트에서 `mam--`라는 **사실상 이름이 아닌 문자열**을 만든다.

R-3은 "규칙이 두 개라 서로 다르다"는 결함이다.
이 처방은 **세 번째 규칙을 추가해 세 개로 만든다.** 고치려던 문제를 악화시킨다.

### E-C — 폴백에 없는 파라미터는 `$AGENT`가 아니라 `$WORKSPACE`다

```
lib.sh:555  resolve_herdr_session() {
lib.sh:556    local session_name="$1"
              # 인자는 세션명 하나. workspace도 agent도 없다.
```

`agy`의 제안 `derive_workspace_slug "$WORKSPACE"`는 이 스코프에서 **정의되지 않은 변수**를 쓴다.
그래서 그들의 헬퍼는 `local ws="${1:-$PWD}"`로 조용히 `$PWD`를 대신 쓴다 —
그 순간 자신이 지적한 "정보 결여"를 그대로 재현한다.

호출자별 workspace 보유 현황(실측):

```
  create_session.sh        has --workspace   L190 resolve_herdr_workspace "$SESSION_NAME"
  resume_session.sh        has --workspace   L47  resolve_herdr_session   "$SESSION_NAME"
  update_yaml_resumed.sh   has --workspace   L40  resolve_herdr_session   "$SESSION_NAME"
  stop_session.sh          no  --workspace   L85  resolve_herdr_workspace "$SESSION_NAME"
```

**4곳 중 3곳은 workspace를 이미 갖고 있으면서 넘기지 않고 있다.**
`stop_session.sh`만 없는데, 그조차 L117에서 `TARGET_CWD`를 읽으므로 **순서만 바꾸면** 확보된다.

### E-D — cwd 폴백은 같은 세션명을 호출 위치마다 다르게 해석한다

미등록 세션명 하나를 세 디렉터리에서 조회했다.

```
  cwd=/Users/…/canary_projects/multi-agent-mux   -> mam-multi-agent-mux
  cwd=/tmp                                        -> mam-tmp
  cwd=/…/scratchpad                               -> mam-scratchpad
```

동일 입력, 세 가지 답이다. 그리고 첫 줄은 R-3을 라이브로 재확인해 준다 —
이 워크스페이스에서 create가 만드는 이름은 `mam-canary-projects-multi-agent-mux`인데
폴백은 `mam-multi-agent-mux`를 낸다.

`agy`의 `${1:-$PWD}`는 이 동작을 **그대로 보존**한다.

---

## 2. 신규 결함

| ID | 결함 | 증거 | 등급 |
|---|---|---|---|
| **R-12** | `resolve_herdr_session`의 폴백이 **호출자의 cwd를 워크스페이스로 추측**한다. 같은 세션명이 호출 위치에 따라 다른 herdr 세션으로 해석되어, stop/resume이 생성된 적 없는 세션을 대상으로 삼는다. R-3(규칙 두 개)보다 근본적이다 — 규칙을 통일해도 **입력이 틀리면 결과는 여전히 틀리다** | E-D, E-C | **높음** |

Rev.1 대비 총계: **12건** (치명 3, 높음 4, 중간 5).

---

## 3. 재설계 — R-3 / F4

### 3.1 설계 원칙

`agy`의 형태를 채택하되 두 가지를 바꾼다.

1. **규칙을 새로 쓰지 않고 추출한다.** `derive_workspace_slug`는
   `derive_session_name`의 정규화 블록을 **그대로 옮긴 것**이어야 하고,
   `derive_session_name`은 그 헬퍼를 호출해 접미사만 붙이도록 재작성한다.
   그래야 규칙이 물리적으로 하나가 된다. 두 함수가 "같은 규칙을 따르기로 합의"하는 구조는
   R-3이 이미 실패를 증명했다.
2. **추측하지 않고 전달받는다.** `resolve_herdr_session`에 선택적 두 번째 인자
   `[workspace]`를 추가하고, workspace를 아는 호출자는 반드시 넘긴다.
   **모를 때는 cwd로 추측하지 않고 `default`를 반환하며 stderr에 경고한다.**
   틀린 세션을 조용히 가리키는 것보다 `default`가 안전하다 — 최소한 관측 가능하다.

### 3.2 추출안 검증

제안한 추출 구현을 실제로 작성해 5개 경로에서 대조했다.

```
  parent_dir/my_project     create=parent-dir-my-project      extracted=parent-dir-my-project      MATCH
  Upper_Case/Web_App        create=upper-case-web-app         extracted=upper-case-web-app         MATCH
  /tmp                      create=workspace-tmp              extracted=workspace-tmp              MATCH
  /                         create=workspace-root             extracted=workspace-root             MATCH
  canary_projects/multi-…   create=canary-projects-multi-…    extracted=canary-projects-multi-…    MATCH

  derive_session_name       : parent-dir-my-project-creator-claude
  derive_workspace_slug+sfx : parent-dir-my-project-creator-claude
```

**5/5 일치**, 그리고 `derive_session_name`이 `derive_workspace_slug` + `-creator-<agent>`로
정확히 분해된다. 이 형태면 `create_session.sh`의 `sed`도 사라진다 —
`agy`가 지적한 냄새의 근본 제거다.

### 3.3 `set -u` 대응

`derive_session_name`의 `local agent="$2"`를 `local agent="${2:-}"`로 바꾼다(E-A).
추출 후에도 이 함수는 남으므로(호출자 다수) 방어는 필요하다.
`agy`가 감지한 증상에 대한 **정확한 크기의 수정**이다.

### 3.4 채택하지 않은 것

- **`tr -cd 'a-z0-9-'` 방식** — 밑줄을 삭제해 기존 이름과 어긋난다(E-B).
- **`${1:-$PWD}` 기본값** — cwd 추측을 영속화한다(E-D). 명시 전달 또는 `default`.
- **`derive_session_name`을 그대로 호출하고 `sed`로 자르는 방식(Rev.1 F4 원안)** —
  동작은 하지만(E-A) 문자열 조작이 남고 `set -u` 지뢰를 유지한다.
  `agy`의 §2 지적이 이 부분에서는 맞다.

---

## 4. Rev.1 대비 변경

| 항목 | Rev.1 | Rev.2 |
|---|---|---|
| F4 | `resolve_herdr_session` 폴백이 `derive_session_name` 재사용 | **F4a/F4b/F4c로 분할.** 추출 헬퍼 + workspace 파라미터 + cwd 추측 제거 |
| 결함 수 | 11건 | **12건** (R-12 추가) |
| 차단 항목 | BK-A, BK-B | **BK-A, BK-B, BK-C** |
| V-6 | 두 슬러그가 같은 문자열을 낸다 | 유지 + V-11~V-15 추가 |

---

## 5. 수정된 커밋 계획 (변경분만)

Rev.1의 F1·F2·F3·F5~F9는 그대로다. F4만 분할한다.

| # | 커밋 | 내용 | 선행 |
|---|---|---|---|
| **F4a** | `refactor(lib): extract derive_workspace_slug as the single naming rule` | `derive_session_name`의 정규화 블록을 헬퍼로 추출하고, `derive_session_name`은 그 헬퍼 + 접미사로 재작성. `local agent="${2:-}"` 방어 포함. **동작 변화 0 — 순수 리팩터** | F2 |
| **F4b** | `feat(lib): let callers pass the workspace to resolve_herdr_session` | 선택적 2번째 인자 추가. 폴백이 `derive_workspace_slug "$ws"` 사용. `create/resume/update_yaml_resumed`가 보유 중인 workspace 전달. `stop_session.sh`는 `TARGET_CWD` 조회를 L85 앞으로 옮겨 전달 | **F4a** |
| **F4c** | `fix(lib): stop guessing the workspace from the caller's cwd` | R-12. workspace 미지정 시 `default` 반환 + stderr 경고 1회. **F4b와 같은 커밋에 넣지 않는다** — 전달 경로가 먼저 완성되어야 이 변경이 안전하다 | **F4b** |

`create_session.sh:153`의 `derive_session_name … | sed 's/-creator-.*//'`도 F4a에서
`derive_workspace_slug "$WORKSPACE"`로 교체한다.

### 순서 근거

- **F4a는 순수 리팩터**여야 한다. 동작 변경과 섞으면 5/5 일치(§3.2)를 회귀로 검증할 수 없다.
- **F4a → F4b**: 헬퍼가 없으면 전달할 대상이 없다.
- **F4b → F4c**(BK-C): 전달 경로가 완성되기 전에 cwd 추측을 없애면,
  아직 workspace를 넘기지 않는 호출자가 전부 `default`로 떨어져 **stop이 세션을 못 찾는다** —
  R-1과 정확히 같은 고아 pane 증상을 새로 만든다.
- F4 계열 전체는 **F2 이후**다. rename이 끝나기 전에 이름 규칙을 건드리면
  어느 층에서 깨졌는지 분간할 수 없다.

---

## 6. 추가 테스트

Rev.1 V-1~V-10은 유효하다(V-6은 아래 V-11로 강화).

| ID | 검증 |
|---|---|
| **V-11** | `derive_workspace_slug`와 `create_session.sh`의 실제 산출물이 **5개 경로 전수 일치**: 밑줄, 대문자, `/tmp`, `/`, 실제 저장소. §3.2 매트릭스를 회귀로 고정 |
| **V-12** | `derive_session_name "$WS" "$agent"` == `derive_workspace_slug "$WS"` + `-creator-$agent` (분해 항등식) |
| **V-13** | `set -u` 하에서 `derive_session_name "$WS"`가 **중단되지 않는다** — F4a 이전 반드시 실패 (E-A 재현) |
| **V-14** | 동일 세션명을 서로 다른 cwd 3곳에서 조회해도 **같은 결과**를 낸다 — F4c 이전 반드시 실패 (E-D 재현) |
| **V-15** | workspace 미지정 시 `default` + stderr 경고. cwd 기반 추측 문자열이 나오지 않는다 |
| **V-16** | `grep -rn "sed 's/-creator" .agents/skills` 결과 0 (문자열 조작 제거 확인) |

**신규 6건.** V-13·V-14는 수정 전 반드시 실패해야 한다.
**V-11은 `agy`의 구현이 통과하지 못하는 테스트**이며(E-B 4/4 불일치),
어떤 구현이든 이 테스트를 먼저 세우면 규칙이 셋으로 늘어나는 것을 막는다.

---

## 7. 추가 DoD 게이트

Rev.1 게이트 A~G에 더한다.

| 게이트 | 조건 |
|---|---|
| **H** | F4a 커밋의 diff가 **동작 변경 0**임을 V-11/V-12로 입증 (리팩터 순수성) |
| **I** | `grep -rn 'basename' .agents/skills/lib.sh .agents/skills/*/scripts` 결과에 **워크스페이스 슬러그를 만드는 두 번째 구현이 없다** |
| **J** | F4c 이후 `resolve_herdr_session`이 workspace 없이 호출되는 지점이 0 (있다면 그 호출자가 `default`를 받아도 무해함을 명시) |

---

## 8. 추가 리스크

| ID | 리스크 | 완화 |
|---|---|---|
| **RK-F** | F4c가 workspace를 넘기지 않는 잔여 호출자를 `default`로 떨어뜨려 R-1과 같은 고아 pane을 유발 | BK-C 순서 + 게이트 J + V-15. F4b에서 전 호출자 전달을 완료한 뒤에만 F4c 착수 |
| **RK-G** | F4a 리팩터가 미묘하게 이름을 바꿔 **기존에 만들어진 herdr 세션과 어긋난다** | V-11의 5경로 전수 대조를 F4a **이전에 먼저 작성**해 현재 값을 스냅샷으로 고정. 리팩터는 그 스냅샷을 깨지 않아야 함 |

---

## 9. 차단 항목 (갱신)

BK-A, BK-B는 Rev.1과 동일하다. 하나 추가한다.

> **BK-C — cwd 추측 제거(F4c)는 workspace 전달 완료(F4b) 이후에만.**
> 순서를 뒤집으면 아직 workspace를 넘기지 않는 호출자가 전부 `default`를 받아
> 살아있는 세션을 찾지 못한다. 이는 Rev.1 R-1이 만든 고아 pane과 **동일한 증상**을
> 새 경로로 재생산하는 것이다. 두 커밋을 합치는 것도 금지한다 —
> 합치면 F4b의 전달 경로가 올바른지 독립적으로 검증할 수 없다.

**차단 항목은 BK-A, BK-B, BK-C 3건이다.**

---

## 10. `agy`에 대한 평가

이번 이의제기는 **Rev.1 F4의 실질적 약점을 짚었다.** `derive_session_name`을 직접 호출하고
`sed`로 접미사를 깎아내는 방식은 확실히 나쁜 형태이고, 전용 워크스페이스 슬러그 헬퍼가
옳다는 §2 지적은 그대로 채택했다. `create_session.sh`가 `sed`를 쓰는 이유를
개념적 불일치의 증거로 읽은 것도 정확한 독해다.

동시에 검증이 빠진 부분도 분명하다.

- 핵심 논거인 "agent 정보 부재로 폴백 붕괴"는 **한 번 실행해 보면 반증된다**(E-A).
  슬러그는 agent와 무관하다. 실제 붕괴 원인은 `set -u` 인자 개수이며,
  이를 오진한 탓에 처방이 필요 이상으로 커졌다.
- 제시한 구현은 **자기 목적을 달성하지 못한다**. 4개 경로 전수 불일치이고(E-B),
  특히 밑줄을 삭제해 `my_project → myproject`를 만든다 —
  R-3이 지적한 바로 그 종류의 불일치를 새로 만든다.
- 가장 아쉬운 점은 **자기가 진단한 결여를 자기 처방에서 반복**했다는 것이다.
  `derive_workspace_slug "$WORKSPACE"`의 `$WORKSPACE`는 그 스코프에 없고,
  헬퍼의 `${1:-$PWD}`가 그 사실을 조용히 덮는다(E-C). 빠진 파라미터는 `$AGENT`가 아니라
  `$WORKSPACE`였고, 그것을 끝까지 따라갔다면 R-12에 스스로 도달했을 것이다.

결과적으로 이 라운드에서 **R-12가 발굴되었고, F4의 형태가 개선되었다.**
그 두 가지는 이의제기가 없었다면 나오지 않았다.

---

## 11. Planner 경계

본 문서는 **설계·리뷰 산출물**이며 저장소 코드는 한 줄도 수정하지 않았다.
모든 프로브(`agy` 제안 함수의 원문 재현, 추출안 프로토타입, cwd 3분기 대조)는
`/tmp` 하위 임시 디렉터리에서만 실행했고 종료 시 제거했다.
라이브 herdr 세션에는 이번 라운드에서 접근하지 않았다.

워킹트리의 `M .agents/skills/lib.sh`는 **여전히 미커밋 상태**이며 리뷰 대상이다
(Rev.1 R-6 / BK-A — 변동 없음).
`*.tmp` 잔재는 이번 job 시작 시점에도 또 관측되었다
(`…31385_22852.tmp` — **4회 연속 재발**, Rev.1 B-6).

`MULTI_AGENT_RULES.md` §1에 따라 **구현은 Creator, 커밋은 GM 소관**이다.

**차단 항목은 BK-A, BK-B, BK-C 3건이며, F1 커밋이 여전히 모든 작업의 선행 조건이다.**

[AGREEMENT: REACHED]
