11 KiB
구현 계획서 (Refined v4): MAM 세션 생성 · UUID 추출 · 다단계 무결성 검증 아키텍처
본 문서는 Reviewer의 코드 리뷰 피드백(대상: Job
03ae0809가 리뷰한 구현 diff, 이 계획서의 v3, 원본은9bde0402→de45d5e0→fd0b8737)을 반영해 정교화한 버전이다. v3 대비 변경점은 0장에 요약한다.
0. Reviewer 피드백 반영 변경 이력 (v3 → v4)
| # | Reviewer 지적 사항 | 판정 | v4 조치 |
|---|---|---|---|
| 1 | (치명적) resume_session.sh --dry-run이 reconcile.sh의 atomic_dump_yaml 트랜잭션(BEGIN IMMEDIATE 배타 락 보유 중) 내부에서 서브프로세스로 호출되는데, resume_session.sh의 "herdr 이미 생존" 분기(2단계)는 --dry-run으로 전혀 게이트되지 않고 update_yaml_resumed.sh → atomic_dump_yaml을 통해 같은 DB에 또 다른 배타 락을 시도 — 자기 자신과의 락 경합으로 매번 ~60초 스톨 후 실패, 게다가 herdr가 살아있는 정상 케이스(주 사용 경로)에서 항상 발생 |
타당함, 전면 수용 — v2(de45d5e0)가 "resume_session.sh --dry-run을 서브프로세스로 호출"을 설계할 때 그 호출 지점이 이미 같은 DB에 락을 쥐고 있는 트랜잭션 내부라는 사실을 반영하지 못한 설계 공백으로 인정 |
2.3장을 전면 재작성: resume_session.sh --dry-run을 어떤 분기에서도 절대 쓰기를 수행하지 않도록 원칙을 명문화(2.3.4). 구체적으로 "herdr 이미 생존" 분기도 DRY_RUN 체크를 통과하도록 재설계(3.4-6) — 이는 "dry-run은 어떤 경로로도 상태를 변경하지 않는다"는 불변조건을 처음부터 지켰어야 했던 근본 설계 원칙의 누락이었음을 자체 인정 |
| 2 | (경미) rc==0/rc==2 분기 코드 중복(8곳) |
타당함, 권고사항으로 수용 | 3.3에 공통 헬퍼 함수 추출을 정식 리팩터링 항목으로 추가(우선순위는 낮음, 블로킹 아님) |
1~2.2장 — v3와 동일, 변경 없음
온보딩 기본화, stage-4의 3-분기 표(verify_tui_viewport 0/1/2 처리), verify_session_uuid의 mode 구분은 Reviewer가 이번 구현에서 정확히 반영되었음을 확인했으므로 변경 없이 유지한다.
2.3 Resume 실행-경로 사전검증 — "Dry-run은 어떤 분기로도 쓰지 않는다" 원칙 추가 (Reviewer 발견 1 반영)
2.3.1 문제의 근본 원인 재확인
de45d5e0(v2)에서 resume_session.sh --dry-run을 설계할 때, 스크립트의 5단계 흐름 중 1~4단계만 dry-run 대상으로 명시했다:
"1단계... 2단계에서 herdr가 이미 살아있는 경우, dry-run은 '이미 실행 중'으로 보고하고 성공 처리(실제 resume 시에도 이 경로는 spawn을 타지 않으므로 동일 로직)... 5단계(
_herdr new-session이하)는 실행하지 않는다."
이 서술은 "2단계는 spawn을 타지 않으니 dry-run에서도 안전하게 그대로 실행해도 된다"는 판단이었다 — spawn 여부만 기준으로 안전성을 판단했고, "실제 파일/DB 쓰기가 발생하는지"는 별도로 검토하지 않았다. 그러나 2단계는 spawn하지 않는 대신 update_yaml_resumed.sh를 통해 실제 YAML/DB를 갱신한다 — 이것이 이번에 발견된 결함의 정확한 근본 원인이다. "dry-run"이라는 이름의 함의(상태를 바꾸지 않는 시뮬레이션)를 스크립트의 모든 분기에 대해 일관되게 지키지 못한 것이 진짜 설계 공백이며, 이는 우연히 이번 라운드에서야(reconcile.sh가 이 dry-run을 이미 락을 쥔 트랜잭션 안에서 호출하기 시작하면서) 관측 가능한 증상(데드락)으로 드러난 것뿐, 결함 자체는 v2 설계 시점부터 존재했다.
2.3.2 신규 원칙: Dry-run은 어떤 코드 경로로도 절대 쓰지 않는다
resume_session.sh --dry-run은 다음을 만족해야 한다:
- 5단계(spawn)뿐 아니라 2단계("이미 생존" 분기)도 포함해,
DRY_RUN=1일 때는 스크립트의 어떤 분기도agent-sessions.yaml/.db에 쓰기를 수행하지 않는다. - 이는 우연이 아니라 의미적으로도 올바르다: "herdr가 이미 살아있다"는 것은 실제 운영 모드에서도 spawn을 하지 않고 그저 YAML을 최신 상태로 동기화하는 부가 작업일 뿐,
resume_session.sh --dry-run이 검증하려는 대상(스폰 경로의 유효성 — 바이너리 resolution, isolation 설정,CMD_FULL조립)과 무관하다. 즉 "이미 살아있으면 검증할 스폰 경로 자체가 없다"는 뜻이므로, dry-run은 이 경우 그냥 "이미 실행 중 — 검증 대상 없음"으로 보고하고 종료하는 것이 개념적으로도 정확하다.
2.3.3 갱신된 5단계 흐름 (dry-run 게이팅 명시)
- UUID 해석 — 변경 없음.
- herdr 생존 확인:
DRY_RUN=1이면:echo "[dry-run] herdr '$SESSION_NAME' already running — nothing to validate",update_yaml_resumed.sh호출 생략,exit 0.DRY_RUN=0(실제 모드)이면: 기존 그대로update_yaml_resumed.sh호출 후exit 0. 3~4. isolation 설정 해석, 바이너리 resolution/실행권한 검증,CMD_FULL조립 — v2/v3와 동일, dry-run 여부와 무관하게 항상 실행(이 부분은 원래도 쓰기가 없었으므로 문제 없음, 재확인만).
- spawn —
DRY_RUN=1이면 생략(기존과 동일).
2.3.4 원칙의 재사용성
이 "dry-run은 어떤 분기로도 쓰지 않는다"는 불변조건은 이번 스크립트에 국한되지 않고, 향후 이 MAM 코드베이스에 추가되는 모든 --dry-run 플래그에 적용되는 일반 설계 규칙으로 승격한다. 리뷰에서 지적된 대로 "스크립트 레벨 --dry-run"(예: reconcile.sh 자신의 --dry-run, env_python 사용)과 "resume_session.sh --dry-run"처럼 이름은 같지만 의미/구현이 다른 두 플래그가 혼동을 야기했던 점도 있으므로, 4장에 이 네이밍 중복에 대한 후속 확인 항목을 추가한다.
3. 리팩터링 로드맵 — 갱신 사항
3.3 reconcile.sh — v3와 동일, 변경 없음
2-패스 재설계(트랜잭션 밖에서 서브프로세스 실행)는 채택하지 않는다 — 2.3.2/2.3.3의 수정만으로 데드락의 근본 원인(자식 프로세스의 쓰기 시도)이 제거되므로, reconcile.sh 자체의 구조(드리프트 C를 atomic_dump_yaml 트랜잭션 안에서 실행하고 그 안에서 resume_session.sh --dry-run을 서브프로세스로 호출)는 그대로 유지해도 안전하다. 다만 다음 항목을 추가한다:
7. (경미, Reviewer 발견 2 반영) rc==0/rc==2 분기의 pin/resume-검증/상태갱신 로직이 거의 동일하므로, reconcile.sh 내에 공통 헬퍼(예: _pin_and_verify_resume(s, agent, cwd, uuid, degraded=False) 형태의 로컬 함수)로 추출해 4개 에이전트 × 2개 분기의 중복을 제거한다. 기능 변경 없음, 순수 리팩터링이므로 우선순위는 5.3(문서/정리)에 배치.
3.4 resume_session.sh 변경 — v3에 항목 추가
v3의 3.4절 1~5항은 그대로 유지한다. 추가로:
6. 2단계("herdr 이미 생존") 분기를 DRY_RUN 체크로 감싼다 (2.3.3 참조). 의사코드:
if herdr has-session -t "$SESSION_NAME" 2>/dev/null; then
if [ "${DRY_RUN:-0}" = "1" ]; then
echo "[dry-run] herdr '$SESSION_NAME' already running — nothing to validate"
exit 0
fi
echo "herdr '$SESSION_NAME' already running."
bash ".../update_yaml_resumed.sh" --session "$SESSION_NAME" --uuid "$UUID" --agent "$AGENT"
exit 0
fi
이것으로 DRY_RUN=1일 때 스크립트의 어떤 코드 경로도 agent-sessions.yaml/.db에 쓰지 않게 되어, reconcile.sh가 이미 보유한 배타 락과 충돌할 방법 자체가 사라진다(자식 프로세스가 애초에 그 락을 요청하지 않으므로).
4. 조사 필요/후속 확인 항목 (갱신)
- (v3 유지) agy/hermes/cline TUI 뷰포트 신호 실측 필요.
- (v3 유지, 이번에도 미반영 확인 시 재점검)
stop_session.sh리팩터링 — 다음 라운드 최우선. - (v3 유지)
verify_session_uuid의mode파라미터가 함수 시그니처 변경을 수반하는 리팩터링이라는 점 — 이미 이번 구현에서 반영 완료되었으므로 이 항목은 해소됨(v4에서 제거). - (신규) "스크립트 레벨
--dry-run"(reconcile.sh,env_python기반, 락 없음)과 "resume_session.sh --dry-run"(이번에 "쓰기 없음"이 보장되도록 수정)처럼 이름이 같은 플래그가 서로 다른 스크립트에서 의미상 미묘하게 다른 계약(전자는 원래도 안전, 후자는 이번에 안전하게 고침)을 갖게 되었다 — 향후 혼동 방지를 위해 각 스크립트의--help/주석에 "이 플래그는 어떤 코드 경로로도 쓰기를 하지 않음을 보장한다"는 문구를 명시할 것을 권고(문서 정리, 5.3).
5. Implementer 라운드 우선순위 체크리스트 (갱신)
5.1 필수 (이번에 새로 추가된 머지 차단 사유)
resume_session.sh의 "herdr 이미 생존" 분기(2단계)를DRY_RUN체크로 감싸 어떤 경로로도 쓰지 않도록 수정 (2.3.3/3.4-6, Reviewer 발견 1).
5.2 다음 라운드 필수 (v3에서 이어짐, 변경 없음)
stop_session.sh를verify_session_uuid()/workspace_key재사용 구조로 리팩터링.
5.3 문서/정리 (기능에 영향 없음)
reconcile.sh의rc==0/rc==2코드 중복을 공통 헬퍼로 추출 (Reviewer 발견 2).--dry-run류 플래그의 "쓰기 없음 보장" 계약을 각 스크립트 문서에 명시.
이전 v3의 5.1(하드코딩 nvm 경로 제거, verify_tui_viewport 3-분기, mode 파라미터)과 5.3(SKILL.md 갱신, dead code 제거)은 이번 구현 라운드에서 모두 반영 완료 확인되었으므로 체크리스트에서 제거한다.
6. 결론
Reviewer가 발견한 치명적 결함(resume dry-run이 reconcile.sh 자신의 트랜잭션과 락 경합을 일으키는 문제)을 전면 수용해, "dry-run은 어떤 코드 경로로도 절대 쓰지 않는다"는 불변조건을 resume_session.sh의 모든 분기(특히 이전에 간과되었던 "herdr 이미 생존" 분기)에 명시적으로 적용하도록 설계를 수정했다. 이 원칙은 spawn 경로 검증이라는 dry-run의 본래 목적과도 의미적으로 정확히 부합한다(이미 살아있는 세션은 검증할 스폰 경로가 없으므로 그냥 "검증 대상 없음"으로 보고하는 것이 옳다). 2-패스 트랜잭션 재설계 같은 더 무거운 대안은 이 단순한 수정만으로 문제가 완전히 해소되므로 채택하지 않았다. 경미한 코드 중복 지적(발견 2)은 문서/정리 항목으로 반영했다. 코드는 아직 5.1 항목 반영 전 상태이며, 이 한 가지 수정 후 재검증을 거쳐야 unanimous PASS를 다시 요청할 수 있다.
[VERDICT: PASS]