# v0.6.9 Interactive Error Analysis

작성일: 2026-06-02

## 관찰된 증상

사용자가 `uv run trinity` 대화형 모드에서 다음 요청을 실행했다.

```text
이더 스캔에서 비용이 거의 없이 실시간으로 고래 추적하는 방법과 봇을 설계해줘
```

TUI는 v0.6.9에서 다음 상태를 표시했다.

```text
Round 1
  claude: 오류 발생
  codex: 오류 발생
  gemini: 오류 발생
  합의 실패: No usable consensus: no valid agent opinions to evaluate.

Round 2
  claude: 오류 발생
  codex: 오류 발생
  gemini: 오류 발생
  합의 실패: No usable consensus: no valid agent opinions to evaluate.
```

Round 3은 세 agent 모두 `생각중...` 상태로 계속 진행 중이었다.

## 핵심 판단

이번 증상은 provider 프로세스가 모두 예외로 죽은 상황이 아니다.

v0.6.9에서 `invalid`, `timeout`, `fallback capture`, `empty response`를 더 이상 성공 응답처럼
표시하지 않도록 바뀌면서, 기존에 숨겨져 있던 완료 감지와 응답 추출 실패가 `오류 발생`으로
노출된 것이다.

즉 v0.6.9는 거짓 성공을 줄였지만, interactive provider protocol은 아직 실제 Claude/Codex/Gemini
CLI 화면과 맞지 않는다.

## 증거

현재 tmux session은 살아 있었다.

```text
trinity: 1 windows
```

pane 상태:

```text
0: cmd
1: claude: Design low-cost Etherscan whale tracking bot
2: codex: Round 3/5
3: gemini: Ready (Trinity)
```

Claude pane에는 실제 한국어 답변 본문이 존재했다. 예:

```text
보완 필요 부분
- MEV/샌드위치 공격 고려 누락
- RPC 노드 의존도 과소평가
- mempool 모니터링 vs confirmed TX 구분 불명확
...
I DISAGREE with all
```

따라서 Claude는 "응답 내용 없음"이 아니라, completion detector가 완료를 신뢰하지 못했거나
응답 추출 결과가 `ok` 상태로 인정되지 않은 쪽이 더 가능성이 높다.

Codex pane은 실제 답변보다 CLI 상태와 prompt echo가 대부분이었다. 예:

```text
>_ OpenAI Codex
model: loading /model to change
Booting MCP serv...
gpt-5.5 xhigh ...
```

이는 Codex가 준비되기 전 prompt가 주입되었거나, prompt return 감지가 실제 Codex TUI footer를
완료 상태로 해석하지 못했음을 시사한다.

Gemini pane에는 이전 marker와 현재 marker prompt echo가 섞여 있었다. 예:

```text
[TRINITY_DONE]#2
Shift+Tab to accept edits
After completing your response, output:
[TRINITY_DONE]#3
```

`.trinity/shared.md`에는 Gemini에 대해서만 validator 진단이 남았다.

```text
Round 1 / gemini
- classification: empty
- reasons:
  - empty response

Round 2 / gemini
- classification: empty
- reasons:
  - empty response
```

Claude/Codex는 validator invalid 진단이 남지 않았다. 이 둘은 내용 검증 실패보다는
`completed=False`, detector timeout, fallback capture 같은 metadata 상태 때문에 consensus에서
제외된 것으로 보는 것이 맞다.

## 코드상 발생 경로

`DeliberationProtocol._response_status()`는 다음 조건을 `ok`가 아닌 응답으로 본다.

```text
invalid_response
response_validation.usable == false
metadata.error == timeout
metadata.completed is false
detector == fallback 또는 fallback chain
empty content
```

`TrinityTUI.consume_event()`는 `response_status != "ok"`이면 agent state를 `ERROR`로 표시한다.

그 결과:

```text
completion detector가 불확실함
  -> response_status != ok
  -> TUI: 오류 발생
  -> shared.md opinion 제외
  -> consensus input에서 제외
  -> usable opinion 0개
  -> No usable consensus
```

## 근본 원인

### 1. 완료 감지와 응답 검증이 하나의 실패 상태로 섞여 있다

현재 TUI는 provider exception, timeout, fallback capture, empty extraction, validator invalid를 모두
`오류 발생`으로 표시한다.

사용자 관점에서는 모두 에러처럼 보이지만 실제 의미는 다르다.

- provider error: CLI process 또는 subprocess 실패
- completion timeout: 답변이 끝났는지 확신하지 못함
- extraction empty: 완료는 감지했지만 본문 추출 실패
- invalid response: 본문이 CLI UI/prompt echo로 판정됨
- uncertain capture: 본문은 있으나 완료 신뢰도가 낮음

이 구분이 없어서 현재 화면만으로는 어느 계층이 실패했는지 알기 어렵다.

### 2. Claude는 실제 답변이 있는데도 완료 신뢰도 때문에 버려진다

Claude pane에는 실질 답변이 있었다. 하지만 `HookDetector` 또는 `PromptReturnDetector`가 성공하지
못하면 metadata에 `completed=False`가 남고, protocol은 이를 consensus에서 제외한다.

현재 정책은 보수적이지만 너무 강하다. 실제 본문이 validator를 통과해도 completion detector가
불확실하면 usable opinion으로 쓰지 않는다.

### 3. Codex interactive mode는 준비 상태와 응답 상태를 구분하지 못한다

Codex pane에는 `model: loading`, `Booting MCP server`, footer, prompt echo가 섞였다.

위험 지점:

- `CodexAgent.start()`는 CLI를 띄운 뒤 ready 상태를 기다리지 않는다.
- `PromptReturnDetector`는 마지막 의미 있는 줄이 `›`, `$`, `>` 같은 prompt와 일치해야 완료로 본다.
- 실제 Codex TUI는 footer와 model/status line을 계속 출력하므로 마지막 줄 기반 감지가 취약하다.
- prompt가 입력 버퍼에 남거나 wrapping되면 echo 제거가 실패한다.

### 4. Gemini marker detector가 request boundary를 충분히 갖지 못한다

Gemini는 request마다 `[TRINITY_DONE]#N` marker를 붙인다. 하지만 pane scrollback에는 이전 marker와
현재 prompt echo가 함께 남는다.

현재 `MarkerDetector.prepare_for_request()`는 sent prompt 안의 marker count만 무시한다.
그러나 실제 terminal은 marker prompt를 여러 줄 또는 여러 위치에 표시할 수 있다. 그러면 prompt
echo만으로도 marker count가 예상보다 커지고, detector가 조기 완료될 수 있다.

그 뒤 `GeminiAgent._truncate_at_completion_marker()`가 prompt echo 안의 marker를 실제 답변 종료
marker로 보고 앞부분을 잘라내면 empty response가 된다.

### 5. tmux pane 폭이 너무 좁아 line-based parser가 깨진다

현재 pane 폭은 약 19~20 columns였다.

```text
claude: 19x24
codex: 19x24
gemini: 20x24
```

이 폭에서는 한 문장이 여러 줄로 강제 wrap된다. line 기반 echo matching, prompt pattern matching,
marker matching은 모두 불안정해진다.

예를 들어 `newPendingTransactions` 같은 단어도 `newPendingTr` / `ansactions`로 쪼개진다.
정규식 기반 cleaner가 기대하는 단위와 실제 capture 단위가 달라진다.

### 6. non-ok 응답 진단이 충분히 저장되지 않는다

현재 `.trinity/shared.md`에는 validator invalid 진단만 남는다.

Claude/Codex처럼 completion timeout 또는 fallback 때문에 제외된 응답은 어떤 detector가 실패했고,
cleaned content가 있었는지 확인하기 어렵다.

디버깅에 필요한 최소 정보가 없다.

## 왜 합의가 항상 실패했는가

합의 엔진 자체가 고장난 것이 아니라, 합의 엔진에 전달된 usable opinion이 0개였다.

흐름:

```text
claude: 실제 답변 있음, 하지만 completion status non-ok -> 제외
codex: CLI 상태/prompt echo 또는 completion non-ok -> 제외
gemini: empty response -> 제외

usable opinions = {}
ConsensusEngine.evaluate({})
  -> No usable consensus: no valid agent opinions to evaluate.
```

따라서 `No usable consensus`는 결과적으로 맞는 판정이다. 문제는 그 이전의 provider protocol이다.

## 수정 우선순위

### P0. response status를 세분화해서 TUI에 표시

`오류 발생` 하나로 합치지 말고 다음 상태를 별도로 표시한다.

```text
provider_error
completion_timeout
extraction_empty
invalid_cli_noise
invalid_prompt_echo
uncertain_capture
responded
```

완료 기준:

- 사용자가 TUI만 봐도 provider crash인지, 완료 감지 실패인지, 추출 실패인지 구분 가능.
- `.trinity/shared.md` 또는 log에 모든 non-ok 응답의 status, detector, reason, excerpt 저장.

### P0. Gemini marker detector를 request-scoped로 변경

Marker count 방식만 쓰지 말고, 현재 request 전후 boundary를 저장한다.

필요 변경:

- detector가 `start_line` 또는 baseline capture hash를 보존.
- marker 검색은 request 이후 output에서만 수행.
- prompt echo 안의 marker와 answer body marker를 구분.
- marker 뒤 UI는 버리되, marker 앞에 답변이 없으면 immediate success로 보지 않는다.

완료 기준:

- prompt echo의 `[TRINITY_DONE]#N`만으로 completed가 되지 않는다.
- stale `[TRINITY_DONE]#N-1`이 다음 request를 완료시키지 않는다.
- empty extraction이면 `completed=True`가 아니라 `extraction_empty`로 분리된다.

### P0. Codex interactive ready wait 추가

Codex를 launch한 뒤 바로 prompt를 보내지 말고 ready 상태를 기다린다.

필요 변경:

- `CodexAgent.start()`에서 model loading, MCP booting, prompt ready 상태를 구분.
- ready 전송 실패 시 prompt를 보내지 않고 `provider_not_ready`로 진단.
- Codex TUI footer를 고려한 prompt return detector 추가.

완료 기준:

- `model: loading` 상태에서 round prompt가 주입되지 않는다.
- Codex prompt echo만 consensus 후보로 들어가지 않는다.

### P0. usable content가 있으면 completion timeout과 분리

현재는 `completed=False`면 무조건 consensus에서 제외한다.

개선 정책:

```text
validation usable && completed true
  -> ok

validation usable && completed false
  -> uncertain_capture
  -> 기본은 TUI 경고, consensus 포함 여부는 config로 결정

validation unusable
  -> excluded
```

사용자가 "provider가 타임아웃하지 않는 이상 기다려서 고품질 답변을 받자"고 요구했기 때문에,
기본값은 완료 감지 timeout을 짧게 두고 버리는 방식이 아니라 계속 대기 또는 명시적 중단이어야 한다.

### P1. pane layout을 parser 친화적으로 변경

19~20 column pane은 parser에 불리하다.

옵션:

- visible UI는 compact하게 유지하되, provider pane은 충분한 width로 생성.
- capture용 detached/session pane을 120 columns 이상으로 유지.
- line wrap이 parser에 영향을 주지 않도록 logical output source를 확보.

완료 기준:

- echo matching과 marker matching이 terminal wrap에 의존하지 않는다.

### P1. raw/clean/status artifact 저장

각 request마다 다음 파일을 남긴다.

```text
.trinity/logs/provider/<agent>/<request-id>.raw.txt
.trinity/logs/provider/<agent>/<request-id>.clean.txt
.trinity/logs/provider/<agent>/<request-id>.json
```

JSON에는 최소 다음 필드를 저장한다.

```json
{
  "agent": "gemini",
  "round": 1,
  "detector": "MarkerDetector([TRINITY_DONE]#1)",
  "completed": true,
  "response_status": "extraction_empty",
  "validation": {
    "usable": false,
    "classification": "empty"
  }
}
```

완료 기준:

- 다음 장애는 tmux pane을 직접 보지 않아도 원인 추적 가능.

## 다음 작업 제안

1. TUI status와 protocol response status를 먼저 세분화한다.
2. Gemini marker detector를 request-scoped로 고친다.
3. Codex ready wait와 Codex 전용 prompt return detector를 추가한다.
4. Claude는 usable content가 있는 completion timeout을 `uncertain_capture`로 낮춰 표시한다.
5. provider raw/clean/status artifact 저장을 추가한다.

이 순서가 맞다. 현재 가장 큰 리스크는 "답변이 없어서 실패"가 아니라 "답변이 있어도 어떤 계층에서
버려졌는지 알 수 없는 상태"다.
