18 KiB
리뷰 및 보완 구현 계획서 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 **
원인 두 가지:
- 밑줄 처리가 반대다.
derive_session_name은tr '_' '-'로 변환하는데,agy의 구현은tr -cd 'a-z0-9-'로 삭제한다.my_project가my-project가 아니라myproject가 된다. - 경계 가드가 없다.
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의 형태를 채택하되 두 가지를 바꾼다.
- 규칙을 새로 쓰지 않고 추출한다.
derive_workspace_slug는derive_session_name의 정규화 블록을 그대로 옮긴 것이어야 하고,derive_session_name은 그 헬퍼를 호출해 접미사만 붙이도록 재작성한다. 그래야 규칙이 물리적으로 하나가 된다. 두 함수가 "같은 규칙을 따르기로 합의"하는 구조는 R-3이 이미 실패를 증명했다. - 추측하지 않고 전달받는다.
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]