docs(issue-6): establish phase 1-5 implementation plan and task checklist (Rev.2)
This commit is contained in:
1 parent
f98b9582c7
commit
7b761be7b8
6 files changed
+827
No files matched your search
@@ -0,0 +1,67 @@
|
||||
### [reviewer-opencode-01] Issue #6 정밀 검토 및 해결 방안 보고서
|
||||
|
||||
- **Job**: `17f2e688` / **검토자**: `reviewer-opencode-01` (opencode)
|
||||
- **검토 방법**: 이슈가 인용한 모든 위치를 라이브 소스에서 직접 읽고, 결함 체인 전체(worker break → 레지스트리 → wait_for_job)를 코드 라인 단위로 추적하며, 셸 루프(`run_loop.sh`)와 Python 루프(`lib_py/loop/runner.py`) 양쪽 소비자를 대조하고, 기존 테스트 커버리지를 확인했다. 요약을 신뢰하지 않고 재현 경로를 직접 검증했다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 버그 진위 여부 검토 결과
|
||||
|
||||
### 결함 ① (Fail-Silent on Worker Failure) — **실재 확인 (CONFIRMED REAL)**
|
||||
|
||||
이슈의 인용은 정확하다. `multi-agent-mux-delegate-job`의 반복 루프:
|
||||
|
||||
- **:351-361** — `_phase == "worker"`에서 `job_status != "completed"`인 경우(주입 실패로 인한 subscriber timeout, 프롬프트 미수신 idle timeout 등) `echo ... Terminating workflow.` 후 `break`.
|
||||
- 이 `break` 경로에는 **어디에도** `registry.py status --set` 호출이 없다. 반비교: 루프 내 다른 종료 경로는 레지스트리를 올바르게 갱신한다 — reviewer 승인 시 `--set "completed"` (:394), 최대 반복 도달 시 `--set "error"` (:400).
|
||||
- 즉 worker 실패 조기 탈출이 **유일하게 레지스트리를 방치하는 종료 경로**다. 잡은 생성 시 `status: "pending"`(`lib_py/delegation/registry.py:84`)으로 등록되므로, 이 경로를 밟으면 `<job_id>.json`은 **영구히 `pending`**으로 남는다.
|
||||
- 함수는 이후 validation hook을 돌고 audit-log 경로를 stdout으로 출력한 뒤 정상 종료한다 — `exit 0`(fail-silent). 이슈 기술과 정확히 일치.
|
||||
|
||||
**소비자 측 교착 확인**: 셸 `wait_for_job`(`run_loop.sh:270-306`)과 Python `wait_for_job`(`lib_py/loop/runner.py:291-323`) 모두 종료 조건이 `completed`(→rc 0), `error`/`cancelled`(→rc 1)뿐이고, 그 외 상태(`pending` 포함)는 `sleep $check_interval` 후 재폴링한다. `pending`은 어떤 종료 조건에도 해당하지 않으므로 **`max_wait=3900s`를 전량 소진한 뒤 타임아웃으로 rc=1** — 정확히 이슈가 기술한 65분 영구 대기. 두 런타임(v4.4.0의 Python 코어 포함) 모두 동일하게 노출된다.
|
||||
|
||||
### 결함 ② (Race Condition on TUI Turn Handshake) — **실재 확인 (CONFIRMED REAL, 그러나 이미 부분 완화 장치 존재 — 임계값/대기시간 문제)**
|
||||
|
||||
- `send_keys_safe`(`lib.sh:2231-`)는 주입 전 `_pane_quiescent`(:2189)로 **연속 2회 동일 캡처**(렌더러 정지)를 확인한다. 이것이 바로 이슈가 요구하는 settling 브리미티브의 존재 자체는 이미 구현되어 있다.
|
||||
- 다만 실측 파라미터: `SKS_QUIESCENT_TRIES=20` × `SKS_QUIESCENT_INTERVAL=0.5s` = **최대 10초 대기**. 에이전트가 도구로 `completed` 이벤트를 발행한 직후(턴 내부) 오케스트레이터가 즉시 다음 잡을 생성해 주입하면, 에이전트 TUI는 아직 턴 출력을 렌더링 중이므로 캡처 내용이 계속 변한다. 렌더링이 10초 이상 지속되면 `_pane_quiescent`가 rc=1을 반환하고 `send_keys_safe`는 "pane never quiesced"와 함께 **주입 자체를 거부**한다.
|
||||
- **결함 ②와 ①의 연쇄(핵심)**: `run_agent`의 주입 실패는 rc=1을 반환하지만, 루프의 호출부(:327) `run_agent "$JOB_ID" ... `는 **반환값을 무시한다**(`if ! run_agent` 아님). 이후 `wait "$sub_pid"`는 프롬프트가 주입되지 않은 에이전트를 대기하다 idle timeout으로 rc≥2 → `job_status="timeout"` → worker break → 결함 ①의 fail-silent → `pending` 교착. 즉 **②는 ①의 주요 트리거이며, ①은 ②의 피해를 교착으로 증폭**한다. 두 결함은 독립 패치가 아니라 하나의 수정 단위로 다뤄야 한다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 결함 메커니즘 및 파급 효과 분석
|
||||
|
||||
| 항목 | 분석 |
|
||||
|---|---|
|
||||
| **발생 빈도** | 결함 ②는 렌더링 지연 > 10s인 에이전트/대형 출력에서 현실적으로 발생(이슈 자체가 실관측 사례). ②가 터질 때마다 ①로 인해 65분 교착이 뒤따른다. |
|
||||
| **루프 관점 파급** | `run_loop.sh`의 `--max-loop` 사이클 전체가 단일 worker 실패 하나로 최대 65분 정지. 병렬 리뷰어(`--all-reviewer`) 펜딘트 시 리소스 점유 + 대기 누적. |
|
||||
| **데이터 무결성** | `<job_id>.json`의 `pending`은 "미수행"과 "수행 실패"를 구별 불가하게 만든다 — 감사 로그(events.log)에 subscriber 기록은 남지만, 상태 원장 자체가 거짓("아직 안 함"). 이는 B-14(로컬 디스크 100% 동기화)에서 수립한 "디스크 상태가 진실"이라는 계약의 잔여 예외다. |
|
||||
| **심각도** | ①: **HIGH** (영구 교착 + 원장 오염). ②: **MEDIUM-HIGH** (교착의 주 트리거, 다만 quiescent 브리미티브가 있어 완전 무방비는 아님). |
|
||||
|
||||
## 3. 수정 난이도 및 위험도 평가
|
||||
|
||||
| 결함 | 난이도 | 공수 | 파급 범위 / 위험 |
|
||||
|---|---|---|---|
|
||||
| ① 레지스트리 fail-closed 전이 | **하 (Low)** | ~0.5일 (테스트 포함) | worker/reviewer 양 break 경로에 `status --set error` 1줄씩 추가. 소비자(`wait_for_job` 2개)는 이미 `error`를 정상 종료 조건으로 다루므로 **소비자 변경 불필요** — 파급 최소. 유일 위험: break 시점의 상태를 `error`로 덮어쓸 때 이미 디스크에 terminal 상태가 있는 경우(rc=3 broker_unavailable 분기가 이미 디스크 우선 병합을 하므로 그대로 재사용). |
|
||||
| ② TUI settling 안전화 | **중 (Medium)** | ~1일 | `run_agent` 반환값 접수(루프 :327의 `if ! run_agent` 화) + quiescent 파라미터 튜닝/환경변수화(`SKS_QUIESCENT_TRIES/INTERVAL`는 이미 튜너블). 주입 실패를 즉시 `error` 전이로 연결하면 ②가 터려도 ① 덕분에 루프가 **빠르게 실패 종료**된다(사실상 ②의 폭발 반경을 ① 패치가 절단). 위험: quiescent 대기 연장은 루프 지연을 늘리므로 상한 환경변수로 봉인 필요. |
|
||||
|
||||
## 4. 구체적 해결 방안 및 아키텍처 개선안
|
||||
|
||||
### 결함 ① — fail-closed 레지스트리 전이
|
||||
1. **worker break (:360) 직전**: `"$PY" "$SCRIPT_DIR/scripts/registry.py" --registry-dir "$REGISTRY_DIR" status --job "$JOB_ID" --set "error"` 추가 — `job_status` 변수가 이미 있으므로, 보존을 원하면 `--set "$job_status"` 계열로 세분화해도 되나 `wait_for_job`의 종료 어휘가 `error`이므로 **정규화해서 `error`로 기록** 권장.
|
||||
2. **reviewer break (:365) 동일 적용** (대칭 결함, 이슈에는 미기재지만 동일 라인 구조).
|
||||
3. **`run_agent` 실패 즉시 전이(② 연쇄 절단)**: 루프 :327을 `if ! run_agent ...; then <set error>; break; fi`로 변경 — 주입 실패가 subscriber 120초 대기를 거치지 않고 즉시 실패로 확정.
|
||||
4. **중복 방지**: rc=3 broker_unavailable 분기(:342-347)의 디스크 우선 병합 결과(`completed`/`error`)를 그대로 존중 — 이미 terminal이면 덮어쓰지 않는 가드(`disk_st` 체크 재사용).
|
||||
|
||||
### 결함 ② — TUI settling 안전 처리
|
||||
1. **즉시 조치**: 주입 실패의 즉시 error 전이(위 4-3)가 핵심 — settling은 완화 장치일 뿐, 실패 시의 원장 정직성이 본질.
|
||||
2. **quiescent 파라미터 환경변수화 튜닝**: `SKS_QUIESCENT_TRIES`(기본 20)를 잡 주입 맥락에서 `MAM_SKS_INJECT_TRIES`(예: 40회×0.5s=20s)로 상향하는 옵션 + **프롬프트 복귀(`>`) 감지 시 조기 탈출** — `_pane_quiescent`가 "동일 캡처"만 보는 것을, adapter의 `input_prompt`/`input_placeholder` 사실(`lib_py.agents facts`)과 교차 확인하면 idle 복귀를 정확히 포착(사실 브리지가 이미 내보내는 `MAM_INPUT_PROMPT`/`MAM_INPUT_PLACEHOLDER`/`MAM_INPUT_RULE_PATTERN`을 `send_keys_safe`가 이미 수신하므로, quiescent 성공 판정에 placeholder 프레즌스 조건을 추가하는 것이 최소 변경).
|
||||
3. **선택(중기)**: 주입 재시도 1회 — 실패 시 settle 재대기 후 1회 재주입, 재실패 시 error 전이. 무한 재시도는 금지(교착의 새 형태).
|
||||
|
||||
## 5. 검증 및 테스트 계획
|
||||
|
||||
1. **결함 ① 회귀 가드 (신규)**: mock herdr/agent 환경에서 worker 주입 실패를 유도(`run_agent`가 rc=1 반환하도록) → 잡 `.json`이 `error`로 갱신되고 `wait_for_job`이 `max_wait` 소진 없이 rc=1로 즉시 복귀함을 단언. 반례: broker_unavailable+디스크 completed 시 덮어쓰기 없음.
|
||||
2. **결함 ② 회귀 가드 (신규)**: `_pane_quiescent`가 연속 변화 캡처(렌더링 중)를 시뮬레이션하는 mock으로 `send_keys_safe`의 거부 → `run_agent` rc=1 → 레지스트리 `error` 전이 연쇄를 단언.
|
||||
3. **기존 스위트**: `tests/test_o2_race_free_lock.py`(loop lock), Issue #5 Phase 2a/2b G-가드들, B-14/B-15의 G-1~G-10(`tests/test_b13*`, `test_issue5_*`)이 이번 변경과 충돌 없음을 확인 — 특히 `test_g5_no_blocking_io_while_the_registry_lock_is_held`가 새 `status --set` 호출 위치(락 밖)와 정합.
|
||||
4. **전체 스위트**: 현재 기준선(750 passed) 유지 확인.
|
||||
5. **라이브 스모크**: 실제 2-에이전트 위임에서 일부러 주입을 방해(세션 일시 정지) → 루프가 65분이 아니라 수십 초 내에 `error`로 종료되는지 실측.
|
||||
|
||||
### 총평
|
||||
두 결함 모두 **실재하며 상호 증폭 관계**다(②가 트리거, ①이 교착으로 변환). ①은 원장 정직성 계약(B-14 계열)의 마지막 잔여 구멍이고 수정 비용이 매우 낮다(난이도 하, ~0.5일). ②는 이미 존재하는 quiescent 프리미티브를 주입 맥락에 맞게 튜닝하고 `run_agent` 반환값을 루프가 수용하도록 하는 중간 난이도 작업(~1일)이다. 둘을 한 수정 단위로 묶어 진행할 것을 권고하며, 설계 변경 수준의 재계획은 불필요하다.
|
||||
@@ -0,0 +1,51 @@
|
||||
### [reviewer-opencode-01] Issue #6 통합 보고서 검토 의견
|
||||
|
||||
- **Job**: `58fba2b1` / **검토자**: `reviewer-opencode-01` (원 1차 리뷰 Job `17f2e688`의 작성자)
|
||||
- **검토 대상**: `.agents/reports/issue_6_consolidated_review_report.md` (오케스트레이터 작성, 4인 리뷰 종합)
|
||||
- **검토 방법**: 통합 보고서의 모든 핵심 주장을 라이브 소스·실측으로 독립 재검증 — direct 경로(`:200-232`), 레지스트리 CLI/엔진 CAS 코드, Herdr 0.8.2 CLI 실도움, 좀비 잡 포렌식, mock_herdr 커버리지. 내 1차 리뷰(`17f2e688`)의 권고 대비 증감도 대조했다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 총평 및 통합 보고서 정합성 평가
|
||||
|
||||
**정합성 높음 — 종합적으로 충실하고 정확한 통합이다.** 핵심 판정(①② 모두 실재, 상호 증폭, 국소 패치로 해결 가능)은 나를 포함한 4인 리뷰의 실측과 일치하며, 통합 과정에서 왜곡·축소는 발견되지 않았다. 오히려 내 1차 리뷰를 **초월**하는 두 가지 실질 기여가 있다:
|
||||
|
||||
1. **`--type direct` 숨은 결함 발견 (§2.1-2)**: 나는 loop/discuss 경로(`:351-361`, `:375-378`)만 추적했으나, 오케스트레이터가 실제로 타는 경로는 `runner.py:269`의 `--type direct`이고 이 경로(:213 `run_agent` 무가드, :227 `wait "$sub_pid" || true`, 상태 전이 부재, `exit 0`)가 완전 침묵이라는 발견 — **직접 확인했다. 이것이 통합 보고서의 가장 큰 기여다.**
|
||||
2. **CAS 선행조건 (`expected_status in (pending, running)`)**: 내 1차 리뷰가 "이미 terminal이면 덮어쓰지 않는 가드"를 논점으로만 언급한 반면, 통합 보고서는 이를 구체적 메커니즘(엔진의 기존 CAS 계약 재사용 + CLI 노출)으로 발전시켰다. 실측: `engine.py:188`에 `expected_status` 선행조건이 이미 존재하나 **`registry.py` status 서브커맨드(:292-295)는 `--job`/`--set`만 받고 이를 노출하지 않는다** — 보고서의 §4.1-1이 정확히 이 누락 배선을 메우는 제안임을 확인.
|
||||
|
||||
**실증 포렌식 검증 (강력한 1차 증거)**: 보고서 §2.1-3이 인용한 좀비 잡 3건을 내가 직접 재확인: `93a44347`와 `2961b822`는 **지금 이 순간에도 `status: "pending"`으로 방치**되어 있고, `ee242671`(바로 내가 이전에 "인터럽트된 라운드"로 진단했던 내 잡)은 `running`에 `started` 이벤트 하나만 남은 채 표류 중 — 즉 그때의 중단은 인터럽트가 아니라 **direct 경로가 내 subscriber 실패를 침묵시킨 것**이었을 개연성이 높다. 결함 ①의 라이브 피해 사례로서 이보다 좋은 증거는 없다.
|
||||
|
||||
## 2. 누락되거나 보강이 필요한 핵심 사항 (Missing)
|
||||
|
||||
1. **`run_agent` 반환값 접수의 명문화 필요** (내 1차 리뷰 4-3의 핵심이 통합 보고서에 암묵적): `finalize_unfinished`는 "구독자 rc≠0 + 비터미널 디스크"에서 발동하므로 주입 실패를 **간접적으로만**(주입 실패 → started 미발행 → 구독자 idle timeout → rc=2 → finalize) 포착한다. 이는 동작하나 (a) 주입 실패를 잡는 데 최대 120초(구독자 idle timeout)가 걸리고, (b) `run_agent`가 이미 rc=1로 실패 사실을 즉시 알고 있는데 이를 버리는 구조는 그대로다. **권고**: `run_agent` 실패 시 즉시 finalize 호출을 명시적으로 설계에 포함하라 — 실패 전파가 120초 → 수 초로 단축되고, "주입은 실패했으나 에이전트가 늦게 started를 발행하는" 엣지에서 두 경로의 경합을 없앤다.
|
||||
2. **`herdr agent wait` 실패 모드 표준화**: wait가 타임아웃/대상 부재로 실패할 때의 rc와 출력 계약(성공과 실패를 구별할 수 있는가)이 §4.2에 명시되지 않았다. 폴백 휴리스틱 진입 조건이 이 rc에 의존하므로, Phase 0 스파이크에 "wait 실패 rc 실측" 항목을 추가해야 한다.
|
||||
3. **`MAM_SKS_*` 환경변수 문서화**: 기존 `SKS_QUIESCENT_TRIES/INTERVAL`과 신규 네이티브 타임아웃(15000/5000ms)의 튜닝 변수군을 `.mam.env.example`에 문서화하는 항목이 §5에 빠져 있다(과거 v4.1.x 패치의 표준 절차).
|
||||
|
||||
## 3. 불필요하거나 과도한 제약/설계 (Unnecessary)
|
||||
|
||||
**과도한 설계는 없다.** 명시적으로 검토한 결과:
|
||||
|
||||
- `--expect-status` 신설이 아닌 **기존 CAS 엔진(:188)의 CLI 노출** — 새 메커니즘이 아니라 배선. 최소 비용 최대 효과.
|
||||
- `agent wait --until idle --until done`의 `done` 포함: 에이전트가 턴이 아니라 대화 자체를 종료하는 상태를 놓치지 않는 정당한 포함.
|
||||
- 0.7.x 화면 휴리스틱 폴백 유지: 제거가 아닌 2차 계층 유지 — 호환성 보험이며 비용이 낮다.
|
||||
- 유일한 사소한 지적: §4.2-2의 "잔여 busy 토큰 사전 캡처 대조" 가드는 폴백 경로에서만 필요하다. 1차 게이트(wait --until idle)를 통과한 뒤에는 상태가 이미 idle이므로 네이티브 경로에서 이 가드는 중복 — 구현 시 **폴백 경로에 한정**해 배치하는 것이 코드를 줄인다 (동작에는 무해하므로 차단 사항 아님).
|
||||
|
||||
## 4. 간과된 잠재적 위험 및 엣지 케이스 (Overlooked)
|
||||
|
||||
1. **`agent prompt --wait --until working`의 턴 비추적성**: 실측한 herdr 도움말에 "It does not track turns: if the agent is already working, that active turn's completion may match" — 즉 working 상태에서 제출하면 **이전 턴**의 완료가 새 입력의 수락 증거로 오판될 수 있다. 통합 보고서의 1차→2차 순서(wait로 idle 확보 후 prompt)가 이를 구조적으로 방지하지만, 이 보장이 **순서에 의존**한다는 사실이 문서에 명시되지 않았다. "prompt는 반드시 wait 성공 후에만 호출"을 계약 조건으로 못박아야 한다.
|
||||
2. **wait 무한 대기 함정**: `agent wait`는 `--timeout` 없으면 **무한 대기**한다(실측). §4.2-1이 15000ms를 명시한 것은 정확하나, 타임아웃 누락 시 새로운 교착 유형이 생긴다 — 구현 가드(타임아웃 인자 강제)와 테스트(`--timeout` 없는 호출 금지 lint성 단언)가 필요.
|
||||
3. **구독자 rc=3(broker_unavailable)과 finalize의 상호작용**: loop 경로의 기존 디스크 우선 병합(:342-347)이 finalize와 만날 때, 브로커 장애 중 도착한 늦은 completed 이벤트와의 경합이 새 패치로 어떻게 정렬되는지 테스트 매트릭스에 명시적 시나리오가 필요하다 (§5-1의 CAS 보존 테스트가 이를 커버하나 rc=3 변형 포함 권고).
|
||||
4. **좀비 잡 청소**: `93a44347`/`2961b822`는 패치 후에도 `pending`으로 남는다 — 패치는 **신규** 실패만 막는다. 구현 잡에 과거 좀비 잡 수동 정리(`status --set error` 또는 삭제)를 부수 절차로 포함할 것.
|
||||
|
||||
## 5. 구현 착수를 위한 최종 제안 및 권고사항
|
||||
|
||||
1. **착수 승인**: 공수 산정(2.0~3.5 인일)은 터치포인트 실측(3개 finalize 호출부 + CLI 인자 ~15줄 + 러너 파싱 ~10줄 + 핸드셰이크 게이트 ~60줄 + mock ~50줄 + 테스트 2파일)과 정합 — 타당하다.
|
||||
2. **구현 순서 권고**: (1) `registry.py` CLI `--expect-status` 노출(독립 테스트 가능, 무위험) → (2) `finalize_unfinished` direct+loop 적용 → (3) 러너 rc-관용 파싱 → (4) 네이티브 핸드셰이크 + 폴백 → (5) mock/회귀 테스트. 각 단계 후 750 전체 스위트 유지 확인.
|
||||
3. **테스트 보강**: §5의 2개 신규 파일에 위 §4의 엣지 3건(wait 실패 rc, 턴 비추적성 방지 계약, rc=3 경합)과 내 1차 리뷰 §5-1(주입 실패 → 즉시 error 전이 연쇄)을 시나리오로 추가.
|
||||
4. **문서 동기화**: MESSAGING.md(§5.1 wapper 종료 계약 갱신)와 `.mam.env.example`(타임아웃 변수군)을 패치 커밋에 포함 — v4.4.0 문서 원장 체계의 표준 절차.
|
||||
|
||||
## 6. 최종 판정
|
||||
|
||||
통합 보고서는 4인 리뷰를 왜곡 없이 종합했고, direct 경로 발견과 CAS 선행조건 설계로 원 리뷰들(내 것 포함)을 실질적으로 초월했다. 포렌식 증거(좀비 잡)는 라이브 레지스트리에서 재검증되었으며, 해결안은 기존 CAS 엔진과 Herdr 0.8.2 네이티브 API의 정확한 재사용으로 과설계 없이 최소 비용이다. 내가 보강한 4개 항목(run_agent 즉시 finalize, wait 실패 rc 계약, 턴 비추적성 순서 계약 명문화, 좀비 청소)은 모든 것이 구현 착수 전/중 반영 가능한 권고 사항이지 통합 보고서의 결함이 아니다. 구현 착수 준비 완료 판정에 동의한다.
|
||||
|
||||
[VERDICT: PASS]
|
||||
Reference in new issue
Block a user