diff --git a/docs/contracts/issue-5/implementation_plan.md b/docs/contracts/issue-5/implementation_plan.md
index 45f56fb..7eb9a02 100644
--- a/docs/contracts/issue-5/implementation_plan.md
+++ b/docs/contracts/issue-5/implementation_plan.md
@@ -23,7 +23,7 @@
|---|---|---|---|---|
| **Phase 0**
*(현재)* | **사전 계약 고정 및 Fixture 수집** | • CLI, 이벤트, 리포트, 락 계약 기준선 문서화
• Herdr 0.8.2 네이티브 캡처 및 복합 Watcher 시나리오 2계층 분리
• 6개 백엔드 실측 증거 행렬 수집 | • 510개 전체 테스트 수집 및 회귀 0-Fail (100% 통과)
• Fixture 스키마 및 증거 경계 검증 통과
• 프로덕션 코드 무변경 diff 검증 | 미추적 산출물 즉시 삭제 (`rm -rf docs/contracts/issue-5 tests/fixtures/issue_5 tests/test_issue5_phase0_contracts.py`) |
| **Phase 1**
*(완료 v4.3.0)* | **`lib_py/delegation/` 추출** | • `lib.sh` L1833 이하 위임 헬퍼를 파이썬 서비스 클래스로 추출
• `start_watchdog`을 내부 명칭 `ensure_subscriber_daemon`으로 명명(기존 함수 별칭 보존)
• 장수 프로세스 단일 MQTT 연결 재사용 프로토타입 | • CLI/non-fatal/HMAC/seq/디스크 폴백 회귀 100% 통과
• 데몬 수명 및 PID 계약 일치 확인
• **자동완료 정책 변경 없음** | `MAM_DELEGATION_PY=0`으로 보존된 레거시 인라인 본체(`_*_legacy`)로 즉시 분기 (재배포·revert 불필요; 레거시 본체 제거는 Phase 4) |
-| **Phase 2a** | **`lib_py/loop/` 코어 & Shadow Watcher** | • `engine.py` (단일 전이 주체), `watcher.py` (`herdr agent wait` 래핑) 구현
• 모든 Writer에 대한 단일 CAS 트랜잭션 경계 구현
• 자동완료를 수행하지 않고 "Would-Complete" 로깅만 수행하는 Shadow 모드 가동 | • 락 내 비동기/I/O 배제 검증
• 소켓 단절/스냅샷 재연결 백오프 검증
• Shadow 모드가 레지스트리 상태를 쓰지 않음을 검증 | 1) Shadow 모니터 프로세스 종료 및 인메모리 큐 폐기
2) CAS 서비스는 하위 호환 모드로 유지하거나 `MAM_ENGINE_CAS=0` 플래그로 비활성화하여 레거시 무조건 필드 병합(`update_job_status`)으로 복귀
3) 기존 셸 루프 상태 및 레코드 불변 유지 |
+| **Phase 2a**
*(완료, shadow-only)* | **`lib_py/loop/` 코어 & Shadow Watcher** | • `engine.py` (단일 전이 주체), `watcher.py` (`herdr agent wait` 래핑) 구현
• 모든 Writer에 대한 단일 CAS 트랜잭션 경계 구현
• 자동완료를 수행하지 않고 "Would-Complete" 로깅만 수행하는 Shadow 모드 가동 | • 락 내 비동기/I/O 배제 검증
• 소켓 단절/스냅샷 재연결 백오프 검증
• Shadow 모드가 레지스트리 상태를 쓰지 않음을 검증 | 1) Shadow 모니터 프로세스 종료 및 인메모리 큐 폐기
2) CAS 서비스는 하위 호환 모드로 유지하거나 `MAM_ENGINE_CAS=0` 플래그로 비활성화하여 레거시 무조건 필드 병합(`update_job_status`)으로 복귀
3) 기존 셸 루프 상태 및 레코드 불변 유지 |
| **Phase 2b** | **이중 감지 자동완료 활성화 (Flag-Gated)** | • 4중 실행 귀속 + 리포트 확정 계약 + 단일 CAS 전이 게이트 연동
• Pending Notification 지연 알림 및 크래시 복구 구현
• 검증된 백엔드/작업에 한해 opt-in 플래그로 활성화 (기본값 OFF) | • 부수효과 작업 예외 및 무판정 fail-closed 테스트 완료
• 경쟁/재시작 시나리오 통과
• 플래그 OFF 시 기존 explicit event 경로 100% 보존 | 환경변수/플래그 `MAM_AUTO_COMPLETE=0` 설정; 이미 확정된 terminal 레코드는 보존하고 신규 자동완료만 중단 |
| **Phase 3** | **`run_loop.sh` Thin Wrapper 전환 및 전면 회귀** | • `run_loop.sh`를 인자 전달 래퍼(`exec python3 -m lib_py.loop "$@"`)로 전환
• 플래너/리뷰어/반론/승격 오케스트레이션 로직 완전 이식
• 엄격한 판정 파서(Strict Parser) 적용 | • 5개 subprocess 전용 테스트(~1,621줄) 포함 전체 481개+ 테스트 0-Fail
• 시그널, 루프 가드, 임의 CWD 및 공백 경로 검증 통과 | **우아한 핸드오프 시퀀스**:
1) Quiesce: 실행 중인 Python 루프 프로세스에 종료 신호를 전달하여 신규 디스패치 중단
2) Checkpoint: 현재 루프 회차, 활성 Job ID 및 리뷰 상태를 `.mam/loop-checkpoint.json`에 원자적 기록
3) Exit: Python 루프 정상 종료 및 가드 해제
4) Resume: 백업된 셸 원본 스크립트로 `run_loop.sh` 복원 후 체크포인트 기반 재개 검증 |
| **Phase 4** | **불변 런타임 검증 및 Freeze 제거** | • 기동 시 전체 모듈 상주(Eager Import) 및 자식 템플릿 불변성 검증
• 작업 도중 스킬 파일 교체 및 늦은 경로 스트레스 테스트 통과
• `/tmp/mam-loop-freeze.XXXXXX` 우회책 완전 제거 | • 런타임 자기수정 크래시(B-13) 재발 방지 검증
• Live Helper 버전 호환 게이트 검증 통과
• 운영 문서 및 배포 스크립트 갱신 | Freeze 복사 로직(`run_loop.sh:112-127`) 재활성화 및 라이브 헬퍼 버전 검사 복구 |
diff --git a/docs/contracts/issue-5/phase2a-shadow.md b/docs/contracts/issue-5/phase2a-shadow.md
new file mode 100644
index 0000000..f69e37a
--- /dev/null
+++ b/docs/contracts/issue-5/phase2a-shadow.md
@@ -0,0 +1,63 @@
+# 📑 Issue #5 Phase 2a — Shadow Watcher 운영 노트
+
+- **문서 버전**: Rev.1
+- **상태**: Phase 2a 완료 (**shadow-only**: 레지스트리 무기록)
+
+---
+
+## 1. 이 단계가 하는 일과 하지 않는 일
+
+| 한다 | 하지 않는다 |
+|---|---|
+| `herdr agent wait`로 에이전트 상태를 관측 | 관측으로 상태를 바꾸는 일 |
+| 증거를 정책에 넣어 "완료 후보인가"를 **판정** | 판정 결과를 레지스트리에 **커밋** |
+| 판정을 `.mam/shadow/.ndjson`에 기록 | job 레코드 필드를 추가/변경 |
+| 4중 귀속 증거를 shadow 저장소에 보관 | 레코드 스키마 변경 (Phase 2b 결정) |
+
+**자동완료는 Phase 2b다.** 본 단계의 `candidate_approved`는 "커밋했을 것"이라는 로그일 뿐이다.
+
+## 2. 운영 스위치
+
+| 변수 | 기본 | 효과 |
+|---|---|---|
+| `MAM_ENGINE_CAS` | `1` | `0`이면 `update_job_status`/`pick_pending`이 Phase 2a 이전의 보존된 본체로 분기한다. 재배포·revert 불필요. |
+
+Shadow 러너는 애초에 쓰기 경로가 없으므로 **프로세스 종료만으로 흔적이 사라진다.** `.mam/shadow/`는 레지스트리와 분리되어 있어 삭제해도 job 상태에 영향이 없다.
+
+## 3. 락 규율 (P2A-01)
+
+```
+with registry_lock: 판정 + 레코드 1회 쓰기 + 알림 큐잉 ← 블로킹 호출 금지
+락 해제 후: 감사 로그 flush, 발행 등 느린 작업
+```
+
+감사 mirror가 락 밖으로 나가면서 순서 보장을 잃을 수 있으므로, 전이마다 단조 증가하는 `transition_seq`를 레코드와 알림에 함께 실어 늦게 도착한 flush도 순서를 복원할 수 있게 했다. 이 형태는 새로 만든 것이 아니라 `pick_pending`이 이미 쓰던 패턴을 모든 전이로 일반화한 것이다.
+
+## 4. `herdr agent wait` 사용 규칙
+
+- `--timeout`은 **밀리초**다. 코드의 파라미터명은 `timeout_ms`이며 초 단위 형태는 제공하지 않는다.
+- `--until`은 **항상 명시**한다. 생략 시 기본 매칭이 `idle,done,blocked`라 `working`이 빠진다.
+- `exit 1`의 페이로드는 **stderr**에 있다. stdout은 비어 있으므로 stdout에서 에러를 읽으려 하면 안 된다.
+- **타임아웃은 건강한 응답이다.** 소켓이 답했고 상태가 안 바뀐 것뿐이다. 전송 실패와 구분해야 한다.
+- 전송 실패 시 백오프는 1→2→4→8→16초 상한이며, **캐시된 스냅샷을 폐기**한다. 단절 구간 이전의 상태로 추론하면 단절이 완료처럼 보인다.
+
+## 5. 판정 순서 (fail-closed)
+
+1. 전송 이상 → `hold_reconnect` (증거 자체가 무효)
+2. 다른 세대/다른 pane 신호 → `reject_stale`
+3. `blocked` → `hold_blocked` (사람 대기 중 완료 금지)
+4. 4중 귀속 + 실행 앵커 미충족 → `hold`
+5. 비-quiescent 또는 도구 실행 중 → `hold`
+6. 리포트 미확정(매니페스트/해시) → `hold`
+7. 전부 통과 → `candidate_approved`
+
+## 6. 시나리오 계약
+
+Phase 0의 7종 + Phase 2a의 5종 = **12종**이 `expected_policy.decision`으로 판정을 고정한다. 신규 5종도 Phase 0의 `validate_watcher_scenario`를 통과해야 하므로 증거 스키마가 계속 강제된다.
+
+## 7. Phase 2b 인계
+
+1. `attempt_id` 등 귀속 필드를 레코드로 승격할지 결정 (현재는 shadow 저장소).
+2. `policy.decide`의 `candidate_approved`를 `engine.transition("completed", expected_attempt=...)`에 연결. 엔진과 CAS 표는 이미 준비되어 있다.
+3. Pending Notification 지연 알림·크래시 복구 (`state-transitions.md` §5).
+4. `preexec_fn`: Phase 2a는 데몬을 스폰하지 않아 미노출. 2b가 스폰을 도입하면 스레드 안전한 방식으로 교체해야 한다.
diff --git a/docs/contracts/issue-5/state-transitions.md b/docs/contracts/issue-5/state-transitions.md
index 21bee75..736fcac 100644
--- a/docs/contracts/issue-5/state-transitions.md
+++ b/docs/contracts/issue-5/state-transitions.md
@@ -1,6 +1,6 @@
# 📑 Target State Transitions and CAS Specification
-- **Document Version**: Rev.2
+- **Document Version**: Rev.3 (Phase 2a 구현 매핑 추가)
- **Issue**: [Gitea Issue #5](https://git.godopu.com/tmpl/multi-agent-mux/issues/5)
- **Scope**: Target Compare-And-Swap (CAS) State Machine & Attribution Contract
@@ -52,6 +52,20 @@ $$\text{Attribution Tuple} = (\text{job\_id}, \text{attempt\_id}, \text{agent\_s
---
+### 3-1. 구현 매핑 (Phase 2a)
+
+| 계약 | 구현 | 테스트 |
+|---|---|---|
+| §3 CAS 전이표 | `lib_py/delegation/engine.py::_decide` (순수 함수, 락 안에서 호출) | `test_engine_*` |
+| 단일 전이 주체 | `engine.transition` — 유일한 상태 writer. `update_job_status`·`pick_pending`이 이를 경유 | `test_g6_every_record_writer_*` |
+| 락 내 I/O 배제 | 락 = 판정 + 레코드 1회 쓰기. 감사 mirror는 큐잉 후 락 밖 flush | `test_g5_no_blocking_io_*` |
+| §2 4중 귀속 | `lib_py/loop/attribution.py::check` | `test_attribution_*` |
+| 롤백 | `MAM_ENGINE_CAS=0` → 보존된 `_*_legacy` 본체 | 전 특성화 테스트 parametrize |
+
+> 📌 **Phase 2a는 추론 완료를 커밋하지 않는다.** `inferred completed` 행은 `lib_py/loop/policy.py`가 `candidate_approved`로 **판정만** 하고, shadow 러너가 이를 로그로 남긴다. 실제 CAS 커밋 연동은 Phase 2b다.
+
+---
+
## 4. 리포트 확정 단계 (Report Finalization Lifecycle)
자동완료 추론 시 파일 쓰기 도중 발생하는 불완전 리포트 채택을 방지하기 위해 3단계 확정 계약을 수립합니다:
diff --git a/docs/contracts/issue-5/task.md b/docs/contracts/issue-5/task.md
index ba153b0..1bce54d 100644
--- a/docs/contracts/issue-5/task.md
+++ b/docs/contracts/issue-5/task.md
@@ -1,8 +1,8 @@
# 📑 Issue #5 Task Checklist & Verification Evidence
-- **Document Version**: Rev.6 (Phase 1 실행 완료 — P1-01~P1-03 증거 기록)
+- **Document Version**: Rev.7 (Phase 2a 실행 완료 — P2A-01~P2A-04 증거 기록)
- **Issue**: [Gitea Issue #5](https://git.godopu.com/tmpl/multi-agent-mux/issues/5)
-- **Status**: Phase 0 Complete / **Phase 1 Complete (v4.3.0)** / Phase 2–4 Staged
+- **Status**: Phase 0 Complete / **Phase 1 Complete (v4.3.0)** / **Phase 2a Complete (shadow-only)** / Phase 2b–4 Staged
---
@@ -73,11 +73,20 @@
---
-## Phase 2a: `lib_py/loop/` 코어 & Shadow Watcher (후속)
-- [ ] **P2A-01** 단일 engine/checkpoint와 모든 writer 공통 CAS 경계 구현.
-- [ ] **P2A-02** 4중 귀속/dispatch 증거/재연결 resnapshot/timeout/queue cancellation 처리.
-- [ ] **P2A-03** shadow-only watcher가 어떤 terminal도 쓰지 않음을 검증.
-- [ ] **P2A-04** race/attempt 재사용/부분 report/도구 실행/blocked/unknown/재시작 fixture 시나리오 실행.
+## Phase 2a: `lib_py/loop/` 코어 & Shadow Watcher (완료, shadow-only)
+
+- [x] **P2A-00** 이동 전 writer 특성화 선행 (X-01~X-04, `MAM_ENGINE_CAS` 0/1 parametrize).
+ - *증거*: 커밋 `9389d7e`. X-02가 계획서의 초기 가정을 정정한다 — `pick_pending`은 락 안에서 `pending`을 필터하므로 **이미 올바른 CAS**이며, 문제는 정확성이 아니라 **중복**이다.
+- [x] **P2A-01** 단일 전이 경계 구현 및 writer 2종 라우팅.
+ - *증거*: 커밋 `2532e78`. `engine.transition`이 유일한 상태 writer. `state-transitions.md` §3 표 전 항목(claim/비terminal/explicit terminal/멱등 재수신/상충 거부+감사/stale attempt/deadline) 테스트. `MAM_ENGINE_CAS=0` 레거시 본체 보존.
+ - *계획 대비 구조 정정*: 계획서는 `engine.py`를 `lib_py/loop/`에 두었으나, 그러면 `lib_py.delegation`이 **형제 패키지를 임포트**하게 되어 freeze 하에서 트리가 섞인다(Phase 1이 명문화한 규칙). Phase 1의 기존 가드 테스트가 이를 즉시 검출했고, 엔진을 레지스트리 writer들이 있는 `lib_py/delegation/`으로 옮겼다.
+ - *락 규율*: 락은 판정 + 레코드 1회 쓰기만 덮고, 감사 mirror는 큐잉 후 락 해제 뒤 flush한다. 순서는 `transition_seq`로 복원한다. 락 보유 중 `subprocess`/`socket`/`sleep` 호출 0건을 테스트로 감시한다.
+- [x] **P2A-02** watcher·귀속·dispatch 증거·재연결 백오프.
+ - *증거*: 커밋 `b49af56`. `--timeout`은 **밀리초**(파라미터명 `timeout_ms`), `--until` 항상 명시(생략 시 `working` 누락), exit 1은 **stderr**에서 파싱. 백오프 1→2→4→8→16s 상한, 실패 시 캐시 스냅샷 폐기. 귀속 증거는 **레지스트리가 아닌 shadow 전용 저장소**(`.mam/shadow/`)에 둔다 — 레코드에 3개 필드가 전부 없고 Phase 1이 스키마를 동결했기 때문.
+- [x] **P2A-03** shadow-only가 terminal을 쓰지 않음을 검증.
+ - *증거*: 커밋 `964abc6`. `ShadowRunner`는 engine 참조를 **갖지 않는다**(구조적 차단, AST 단정). 측정: 전체 시나리오 실행 전후 **레지스트리 디렉터리 전 파일 SHA-256 동일**.
+- [x] **P2A-04** race/attempt 재사용/부분 report/도구 실행/blocked/unknown/재시작 시나리오.
+ - *증거*: 신규 5종(`race_double_dispatch`, `attempt_reuse`, `partial_report`, `unknown_status`, `restart_midflight`)을 Phase 0 스키마로 작성해 **Phase 0의 `validate_watcher_scenario`를 통과**시켰고, 기존 7종과 합쳐 **12종 전부 `expected_policy.decision`과 일치**함을 단정한다.
---