# Design — Handoff (자매 간 핸드오프) > **처방 대상**: F3 (QA 자동 라우팅 누락), F4 (핸드오프 멘션 불안정) ## 기존 방식의 한계 기존 hanarang-harness 는 자매 간 통신을 **디스코드 멘션**으로 구현했다: ``` 하랑이: [구현해줘] @나랑이 나랑이: [완료] @하랑이 하랑이: [QA 필요할 듯] @다랑이 ``` 문제: - 멘션 파싱 실패 → 다음 자매가 깨지 않음 (F3) - 잘못된 자매 호출 → 파이프라인이 이상한 방향 (F4) - 멘션 = 메시지 = LLM 재해석 → 결정성 없음 - 디스코드 rate limit / 네트워크 장애에 취약 ## 새 방식 — 상태 전이가 곧 핸드오프 핸드오프는 **XState state transition** 이다. 디스코드 멘션은 **사용자 알림 전용**이다. ``` ┌──────────────────────────────────────────────────┐ │ hanarang-rails orchestrator │ │ (XState FSM) │ └──────────────────────────────────────────────────┘ │ │ │ │ ▼ ▼ ▼ ▼ spawn spawn spawn spawn (execa) (execa) (execa) (execa) │ │ │ │ ▼ ▼ ▼ ▼ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ 하랑이 │ │ 나랑이 │ │ 다랑이 │ │ 이랑이 │ │(spawn) │ │(spawn) │ │(spawn) │ │(spawn) │ └────────┘ └────────┘ └────────┘ └────────┘ │ │ │ │ │ structured │ structured │ structured │ structured │ output │ output │ output │ output ▼ ▼ ▼ ▼ ┌──────────────────────────────────────────────────┐ │ orchestrator receives │ │ Zod-validated HandoffMessage │ └──────────────────────────────────────────────────┘ │ ▼ [FSM transition] │ ▼ next actor spawn (병렬: 디스코드 bridge 가 상태 전이 이벤트를 구독 → 사용자 알림) ``` ## HandoffMessage 스키마 각 자매가 orchestrator 에 반환하는 구조화 결과: ```ts const HandoffMessage = z.discriminatedUnion('actor', [ z.object({ actor: z.literal('harang'), verdict: z.enum(['PLAN_READY', 'ABORT']), payload: z.object({ planDir: z.string(), sprintId: z.string(), contractId: z.string(), }).optional(), abortReason: z.string().optional(), }), z.object({ actor: z.literal('narang'), verdict: z.enum(['IMPL_DONE', 'ERROR']), payload: z.object({ branch: z.string(), commits: z.array(z.string()), workdir: z.string(), selfTestReport: z.unknown(), }).optional(), errorReason: z.string().optional(), }), z.object({ actor: z.literal('darang'), verdict: z.enum(['APPROVE', 'REQUEST_CHANGES', 'ABORT']), payload: z.object({ checklistResults: z.array(z.object({ id: z.string(), passed: z.boolean(), note: z.string().optional(), })), issues: z.array(ReviewIssue), artifactPath: z.string(), }), }), z.object({ actor: z.literal('erang'), verdict: z.enum(['DEPLOY_DONE', 'DEPLOY_FAILED']), payload: z.object({ deployArtifactPath: z.string(), projectType: z.string(), verificationResults: z.unknown(), }), }), ]) ``` ## Spawn 계약 Orchestrator 가 자매를 spawn 할 때 표준화된 계약: ```ts async function spawnSister(opts: { sister: SisterName contractId: string workdir: string timeoutMs: number signal: AbortSignal }): Promise { const { stdout } = await execa('openclaw', [ 'spawn', '--role', opts.sister, '--contract', opts.contractId, '--workdir', opts.workdir, '--output', 'structured-json', ], { timeout: opts.timeoutMs, signal: opts.signal, cwd: opts.workdir, }) // 반드시 Zod 로 검증 return HandoffMessage.parse(JSON.parse(stdout)) } ``` - **stdout 이 structured JSON** 이 아니면 즉시 `ERROR` 로 처리 - 타임아웃 시 `AbortController` 로 프로세스 kill - Zod 검증 실패 = 규격 위반 = `ERROR` ## 디스코드 브릿지 (알림 전용) 디스코드는 **사용자에게 진행 상황을 알리는 용도** 로만 사용. 자매 간 통신 아님. ``` orchestrator state transition │ ▼ ┌──────────────┐ │ event emitter│ └──────┬───────┘ │ ▼ ┌────────────────┐ │ discord bridge │ (discord.js) └──────┬─────────┘ │ ▼ 포럼 포스트 업데이트 / 멘션 (사용자 = 자기야) ``` 브릿지가 보내는 메시지 유형: | 이벤트 | 메시지 예시 | 사용자 멘션 | |---|---|---| | `planning` 진입 | "🦊 하랑이가 SPRINT-001 기획 시작" | X | | `implementing` 진입 | "⚙️ 나랑이가 구현 시작 (feature/sprint-001)" | X | | `REQUEST_CHANGES` | "🔁 다랑이 re-review 요청 (라운드 2)" | X | | `escalated` 진입 | "🚨 3회 실패. 자기야 확인 필요" | **O** | | `done` 진입 | "✅ SPRINT-001 완료. 배포 승인해주세요" | **O** | ## 병렬 자매 호출 (나중에) v1 은 **strict sequential**. 동시에 두 자매가 돌지 않음. v2 후보: - 나랑이가 worker 여러 개 spawn (내부 병렬) - 다랑이가 static / runtime 리뷰 병렬 - orchestrator 자체는 여전히 single pipeline lock ## 실패 시나리오 & 복구 | 시나리오 | 감지 | 복구 | |---|---|---| | 자매 spawn 실패 (openclaw 커맨드 에러) | execa exit code ≠ 0 | ERROR event → retry policy | | 자매 structured output 파싱 실패 | Zod validation 실패 | ERROR event → retry + log | | 자매 타임아웃 (응답 없음) | AbortController fired | TIMEOUT event → retry policy | | 자매 정상 종료했는데 verdict 이상 | Zod 통과했지만 invalid state transition | ERROR event → abort + log | ## 참고 - `principles.md` 원칙 2 - `failure-audit.md` F3, F4 - `state-machine.md` — 전이 정의 - `retry-policy.md` — TIMEOUT / ERROR 이벤트 처리