마지막 업데이트 2026-10-02
2026-10-02 · 예시 커밋 0f94a87a [PPI-1348] fix: HC 모드 입력 전사 실패 시 자동 복구 (#1148) · 8파일 +334/−33
--color-moved로 걸러내고, 각 hunk에서는 "언제 안 도는가"만 묻는다. 그러면 334줄짜리 PR이 새 함수 3개로 줄어든다. LLM에게 데이터 흐름 지도를 먼저 받는 것도 좋지만, 지도이지 답이 아니다 — 화살표마다 파일:줄 근거와 "안 도는 조건"을 요구하고 직접 대조한다.
PR 본문(커밋 메시지 bullet 6개)만 읽고 이렇게 정리한다.
이후 읽는 모든 코드는 이 문장을 확인하거나 반박하는 용도로만 쓴다. 가설 없이 diff를 열면 줄마다 "이게 왜 필요하지?"가 쌓여 정지한다.
git show --stat 0f94a87a
| 역할 | 파일 | 줄 | 읽는 강도 |
|---|---|---|---|
| 계약 (새 이벤트) | vendor/.../events.py, __init__.py, agent_session.py | +19 | 훑기 |
| 발생 지점 | vendor/.../agent_activity.py | +12 | 정독 |
| 소비 + 복구 | agent.py | +107/−31 | 정독 (리팩터링분 제외) |
| 가드 | input_gate.py | +8 | 정독 |
| 명세 | tests/test_turn_terminalization.py 외 1 | +188/−2 | 테스트 이름만 먼저 |
전체의 절반 이상(테스트 188줄)은 처음엔 이름만 읽으면 되고, vendor 4파일 중 3개는 배선이라 훑기만 하면 된다.
git show 0f94a87a -- 'apps/livekit-agent/tests/*' | grep '^+.*def test'
| 테스트 (test_half_cascade_…) | 의미 |
|---|---|
provider_failure_delivers_unreadable_recovery_once | 정상 복구, 딱 1회 |
provider_failure_before_local_commit_waits_for_commit | 이벤트가 로컬 commit보다 먼저 오는 순서 역전 |
provider_failure_without_eligible_turn_skips_recovery | 억제 조건 |
superseded_vendor_turn_failure_skips_resumed_epoch | 재개된 턴의 정상 발화를 덮어쓰지 않음 |
이 4줄이 이 PR이 다루는 경우의 수 전부다. 코드를 읽다가 이 목록에 없는 분기를 만나면 그게 리뷰 포인트다.
events.py UserInputTranscriptionFailedEvent 정의 ← 계약
↓
agent_activity.py 턴 폐기 직전 emit
조건: input_turn_id 있음 · not skip_reply
· not session._closing · rt_session 교체 안 됨
↓
agent.py @session.on("user_input_transcription_failed")
_on_user_input_transcription_failed
→ vendor input_turn_id를 로컬 turn_id로 매핑
→ superseded면 중단 (input_gate.is_latest_vendor_turn)
↓
agent.py _maybe_schedule_provider_final_fallback
→ 로컬 turn_committed_at 전이면 보류, commit 후 1회만 예약
↓
agent.py _run_provider_final_fallback
→ _deliver_unreadable_recovery (기존 함수 재사용)
git show 0f94a87a -- '*events.py' '*agent_activity.py' # 계약 + 발생
git show 0f94a87a -- apps/livekit-agent/input_gate.py # 가드
git show 0f94a87a -- apps/livekit-agent/agent.py # 소비 + 복구
파일 목록은 알파벳순이라 agent.py(소비자)가 먼저 나오고 events.py(계약)가 마지막에 나온다. 그 순서대로 읽으면 "이 이벤트는 어디서 오지?"를 머리에 들고 100줄을 읽게 된다.
agent.py 변경 중 동작이 바뀌지 않는 기계적 변경:
soniox_blank_fallback_tasks → unreadable_recovery_tasks (Soniox 전용이 아니라 판독 불가 복구 공용이 됨)_claim_unreadable_recovery_turn()git show 0f94a87a --color-moved=dimmed-zebra -w -- apps/livekit-agent/agent.py
옮겨지기만 한 줄은 흐리게 표시된다. 진하게 남는 건 사실상 _on_user_input_transcription_failed, _maybe_schedule_provider_final_fallback, _run_provider_final_fallback 새 함수 3개와 정리 루프 몇 줄이다.
버그는 대부분 억제 조건에 숨는다. PR 본문의 억제 목록을 코드 위치와 1:1로 대조한다.
| PR 본문의 억제 조건 | 코드 |
|---|---|
| skip_reply · 세션 종료 · rt_session 교체 | agent_activity.py emit 조건 4개 |
| 중복 | turn_id in unreadable_recovery_tasks, turn_id in stt_fallback_turn_ids |
| 새 발화 (VAD 재진입) | 재진입 처리에서 pending_provider_final_failure_turn_ids.discard |
| 수동 인터럽트 | _stt_no_final_turn_is_eligible(cancellation_generation=…) |
| 세션 종료 (agent 측) | stale 정리 루프에 새 set 추가 |
| 재개 턴 보호 | superseded = not is_latest_vendor_turn |
한쪽에만 있는 항목이 나오면 그게 질문거리다. 새 상태 컨테이너(pending_provider_final_failure_turn_ids)가 생기면 add·discard·정리 지점을 전부 찾는다 — 하나라도 빠지면 누수나 유령 복구가 된다.
좋은 출발점이지만 지도이지 답이 아니다. 그냥 "흐름 만들어줘"라고 하면 생기는 문제:
skip_reply·superseded·commit 전 보류 같은 분기를 자주 빼먹는다.PR #1148 diff를 리뷰하기 전에 데이터 흐름 지도를 만들어줘.
1. producer → consumer 순서로 화살표 체인. 각 화살표에 파일:줄 근거 필수
2. 각 단계마다 "이 경로가 안 도는 조건(early return/가드)"을 전부 나열
3. 동작 변경 hunk와 순수 리팩터링(이름 변경·추출) hunk를 분리
4. 테스트 이름 → 어떤 분기를 검증하는지 매핑
5. 확신 없는 연결은 "추정"으로 표시
1번(줄 근거)과 2번(안 도는 조건)이 핵심이다. 줄 근거가 있어야 화살표를 직접 열어 대조할 수 있고, 안 도는 조건 목록이 있어야 7절처럼 PR 본문과 대조해 빠진 것을 찾을 수 있다.
| 명령 / 스킬 | 용도 |
|---|---|
git show --stat <sha> | ② 역할 분류 |
git show <sha> -- <path> | ④ 흐름 순서대로 파일 단위 열기 |
--color-moved=dimmed-zebra -w | ⑤ 이동·공백 변경 흐리게 |
/diff-walkthrough 1148 | 한 파일씩 턴테이킹으로 함께 보기 |
/diff-summary 0f94a87a | diff + 변경 목적 요약 (2·5번 템플릿 항목은 요청 시 추가) |
/pr-diff-review 1148 | 시각화 리뷰 문서 생성 |