Files
multi-agent-mux/docs/contracts/issue-5/phase2a-shadow.md
T
GodopuandClaude Opus 5 17a2dcd988 docs(issue-5): phase 2a contracts, scenarios and task checklist
Records what phase 2a actually delivers and, as importantly, what it does not:
the shadow runner decides and logs, and nothing commits an inferred completion
until 2b.

state-transitions.md gains an implementation mapping so each contract row names
the code and the test that hold it up, with the inferred-completed row marked as
judged-but-not-committed.

phase2a-shadow.md is the operations note: the rollback switch, the lock
discipline and why transition_seq exists, the herdr wait rules that are easy to
get wrong (milliseconds, always name --until, errors on stderr, a timeout is a
healthy answer), the fail-closed decision order, and the 2b handoff.

task.md records P2A-00..04 with commit evidence, including two corrections to my
own plan: pick_pending was already a correct swap rather than a hole, and the
engine had to live with the registry writers because putting it in lib_py/loop
would have made delegation import a sibling and mix trees under a frozen loop.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UrDNJkHmm18uu7n7JwV5Qd
2026-09-06 18:58:01 +09:00

3.7 KiB

📑 Issue #5 Phase 2a — Shadow Watcher 운영 노트

  • 문서 버전: Rev.1
  • 상태: Phase 2a 완료 (shadow-only: 레지스트리 무기록)

1. 이 단계가 하는 일과 하지 않는 일

한다 하지 않는다
herdr agent wait로 에이전트 상태를 관측 관측으로 상태를 바꾸는 일
증거를 정책에 넣어 "완료 후보인가"를 판정 판정 결과를 레지스트리에 커밋
판정을 .mam/shadow/<job_id>.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가 스폰을 도입하면 스레드 안전한 방식으로 교체해야 한다.