Files
multi-agent-mux/docs/contracts/issue-5/herdr-runtime.md
T
Godopu fc0bd96d8a test(issue-5): harden watcher evidence validation and add codex pass review report
- Enforce evidence-root confinement for capture_ref and 1:1 capture citation uniqueness
- Add pane identity and backend identity cross-validation
- Add bidirectional agreement checks between watcher/herdr manifests and scenario fixtures
- Expand contract test suite to 41 tests (100% pass)
- Document evidence confinement rules in herdr-runtime.md and update task.md
- Archive Codex 4th review report (Job 1bd561e6: [VERDICT: PASS])
2026-09-06 14:31:41 +09:00

129 lines
13 KiB
Markdown

# 📑 Herdr Runtime Contract and Backend Capability Matrix
- **Document Version**: Rev.5 (Evidence Source-Kind Dispatch & Identity Correspondence — §3-1 신설)
- **Issue**: [Gitea Issue #5](https://git.godopu.com/tmpl/multi-agent-mux/issues/5)
- **Target Herdr Version**: `herdr 0.8.2`
- **Scope**: Native Herdr API Contract, External Collector Specification, and Backend Matrix
---
## 1. Herdr 0.8.2 네이티브 API 계약 (Native API Contract)
### 1-1. 핵심 CLI 명령어 및 반환 페이로드
`herdr`는 AI 코딩 에이전트 전용 터미널 멀티플렉서로서, 소켓 API를 통해 터미널 스크린 감시 상태를 제공합니다:
1. **`herdr agent list`**: 현재 활성화된 모든 에이전트 목록 및 상태 반환 (JSON stdout, exit 0).
2. **`herdr agent get <target>`**: 특정 에이전트의 단일 시점 스냅샷 반환 (`agent_status`, `interactive_ready`, `pane_id`, `revision`, `state_change_seq` 등).
3. **`herdr agent wait <target> [OPTIONS]`**: 에이전트가 지정된 상태에 도달할 때까지 블로킹 대기.
4. **`herdr agent explain [OPTIONS] [target]`**: 내부 정규식 매칭 규칙(`matched_rule`, `evaluated_rules`) 평가 세부정보 반환.
### 1-2. 네이티브 상태 집합 (`agent_status`)
- **지원 Enum**: `idle`, `working`, `blocked`, `done`, `unknown` (총 5종).
- **`interactive_ready` (boolean)**:
- 터미널이 키 입력을 수신할 준비가 되었는지를 나타내는 플래그.
- **실측 불변식**: 에이전트가 코딩 작업 중(`agent_status: "working"`)일 때도 `interactive_ready: true`임이 입증됨. 따라서 `interactive_ready` 단독으로는 작업 완료의 충분조건이 될 수 없으며, 오직 `agent_status in {"idle", "done"}` 상태와 결합되어야 합니다.
### 1-3. `herdr agent wait` 호출 규격 및 채널 분리 실측
실제 `herdr 0.8.2` 실행(`wait-contract.json` 및 `evidence/timeout_stderr.json`)을 통해 검증된 입출력 채널 및 종료 코드 계약은 다음과 같습니다:
| 호출 조건 | 명령 예시 | 종료 코드 | 출력 채널 | 반환 JSON 페이로드 구조 |
|---|---|---|---|---|
| **상태 도달 (성공)** | `herdr agent wait creator-agy-01 --until working --timeout 1000` | `exit 0` | `stdout` | `{"id":"cli:agent:wait","result":{"agent":{...},"type":"agent_info"}}` |
| **타임아웃 (Timeout)** | `herdr agent wait creator-agy-01 --until done --timeout 50` | `exit 1` | **`stderr`** | `{"error":{"code":"timeout","message":"timed out waiting for agent status"},"id":"cli:agent:wait"}` |
| **대상 부재 (Unknown)** | `herdr agent wait non-existent --until idle --timeout 50` | `exit 1` | **`stderr`** | `{"error":{"code":"agent_not_found","message":"agent target non-existent not found"},"id":"cli:agent:wait"}` |
| **기본 대기 옵션 생략** | `herdr agent wait <target>` | N/A | N/A | `--until` 생략 시 기본 매칭 대상은 `idle, done, blocked`임. `--timeout` 생략 시 무기한 대기함 |
> 🚨 **Critical Empirical Finding (수정 반영)**:
> `herdr agent wait`에서 성공(상태 일치) 시에는 정상 JSON이 `stdout`으로 출력되지만, **타임아웃(`timeout`) 및 대상 부재(`agent_not_found`) 등 모든 에러 응답은 `stderr`로 에러 JSON이 출력되며 종료 코드는 `exit 1`**입니다 (`stdout`은 비어있음). 따라서 Watcher 엔진은 `exit 1` 시 반드시 `stderr` 스트림에서 JSON 에러 페이로드를 파싱해야 합니다.
---
## 2. 외부 시퀀스 수집기 계약 (External Sequence Collector)
### 2-1. 단일 `wait` 호출의 본질적 한계
- `herdr agent wait`는 타겟이 특정 상태에 도달한 시점에 **단 1개의 최종 스냅샷 JSON**만 반환하는 동기적 장벽(Barrier)입니다.
- 호출 도중 거쳐간 중간 전이 과정(Transition Stream)을 반환하지 않습니다.
- 따라서 `working ➔ idle`과 같은 상태 전이를 입증하려면, 외부 오케스트레이터/수집기가 사전 스냅샷(`before`)과 사후 스냅샷(`after`)을 독립적으로 획득하여 타임스탬프와 함께 연결해야 합니다.
### 2-2. 수집기(Collector) 권장 규격
- **폴링 주기**: 1~2초 수준의 유계(Bounded) 폴링 (`agent get`). 고정밀 10ms 스핀 폴링은 불필요하며 CPU 낭비를 초래합니다.
- **Envelope 메타데이터**: 단조 시계 오프셋(`monotonic_start`, `monotonic_end`), `collector_clock_id`, `capture_id`, `source_command`를 필수로 보존하여 시점 간 왜곡을 방지합니다.
---
## 3. 이벤트, 파일시스템 및 디스패치 연계 경계
Herdr 네이티브 페이로드와 Multi-Agent-Mux 오케스트레이션 메타데이터 사이에는 엄격한 책임 격리가 유지되어야 합니다:
```
┌─────────────────────────────────────────────────────────────┐
│ Herdr Native API Boundary │
│ • agent_status (idle/working/blocked/done/unknown) │
│ • interactive_ready (true/false) │
│ • pane_id, state_change_seq, terminal_title │
└──────────────────────────────┬──────────────────────────────┘
│ 관측 증거 제공 (채널 2)
▼
┌─────────────────────────────────────────────────────────────┐
│ Multi-Agent-Mux Watcher Boundary │
│ • 4-Fold Attribution: │
│ (job_id, attempt_id, session_uuid, │
│ pane_process_generation) │
│ • File Evidence (report mtime debounce >=15s, hash) │
│ • Backplane Events (seq, HMAC signature, started/progress)│
└─────────────────────────────────────────────────────────────┘
```
- **불변식**: Herdr 원본 응답 페이로드에는 `job_id`, `attempt_id`, `report phase`, `expected_decision` 등의 MAM 내부 필드를 절대 주입하지 않습니다.
### 3-1. Watcher 타임라인 증거 스키마 및 `source_kind` 분기 계약 (Rev.5)
Watcher 복합 시나리오(`tests/fixtures/issue_5/watcher/*.json`)의 각 타임라인 항목은 아래 계약을 따르며, `tests/test_issue5_phase0_contracts.py`의 `validate_watcher_scenario`가 이를 기계적으로 강제합니다. 위반 시 fixture 검증은 **fail-closed(거부)** 입니다.
| 규칙 | 내용 | 강제 지점 |
|---|---|---|
| **지원 source_kind 집합** | `herdr`, `event`, `filesystem`, `harness` 4종만 허용. 그 외 값은 provenance와 무관하게 거부 | `SUPPORTED_SOURCE_KINDS` |
| **관측(observed) 증거 스키마** | 독립 증거 스키마가 구현된 kind만 `source_provenance: observed`를 선언할 수 있음. **현재 `herdr`만 해당.** `event`/`filesystem`/`harness`는 스키마가 생길 때까지 `simulated`(또는 `synthetic`)로만 기록하며, `observed`로 표기 시 `capture_ref`가 유효하더라도 즉시 거부 | `OBSERVED_EVIDENCE_SCHEMAS` |
| **capture_ref 격리** | repo 상대 경로여야 하며(절대 경로 금지), 해석 결과가 `tests/fixtures/issue_5/herdr/` 하위여야 함(`..` 우회 금지). `.json`이고 `schema_version 1.0.0`·`provenance observed`·`capture_id`를 갖는 네이티브 캡처만 증거로 인정 | `_load_observed_capture_ref` |
| **Herdr 항목 대응성** | ① 참조 캡처는 Layer 1 네이티브 계약(`validate_native_capture`)을 재통과 ② 항목 payload는 비어있지 않은 dict이며 `agent_status`를 **필수 선언**(누락 시 비교 생략이 아닌 거부) ③ 캡처는 `result.agent` 객체를 포함해야 함(`wait` timeout 등 nonzero-exit 캡처는 agent_status를 입증할 수 없으므로 거부) ④ 선언된 **모든** 필드가 agent 객체의 네이티브 필드에 존재하고 값이 정확히 일치(비네이티브 필드 선언 시 거부) | `_validate_observed_herdr_item` |
| **정체성 대응 (4중 귀속 연동)** | 캡처의 `agent_session.value` == `dispatch_context.agent_session_uuid`, 캡처의 `pane_id`가 `dispatch_context.pane_process_generation`(`<pane_id>:<pid>:<start>`)의 선두 구성요소여야 함. 시나리오 `backend`가 `backend_agnostic`이 아니면 캡처 `backend`와 일치해야 함 | `_validate_observed_herdr_item` |
| **1캡처 = 1관측** | 하나의 `capture_id`는 시나리오 내 관측 항목 하나만 입증할 수 있음(동일 스냅샷의 재인용 금지) | `validate_watcher_scenario` |
| **provenance 정직성** | observed·simulated 혼재 시 전체 `provenance`는 `mixed`, 전량 관측 시 `observed_composite`, 전량 모의 시 `simulated`여야 함 (`synthetic` 항목은 비관측으로 집계) | `validate_watcher_scenario` |
| **매니페스트 정합성** | `watcher/manifest.json`의 `provenance`/`expected_decision`은 시나리오 파일과 일치해야 하며, `herdr/manifest.json`의 observed 슬롯은 동일 backend의 캡처가 슬롯명과 같은 `agent_status`(또는 `wait_timeout` 슬롯은 `error.code=timeout`)를 기록해야 함. 디스크의 모든 캡처는 정확히 하나의 observed 슬롯에 색인되어야 함(고아 캡처 금지) | `validate_watcher_manifest`, `validate_herdr_manifest` |
> 📌 이 계약은 **fixture 검증 계약**이며 프로덕션 Watcher 구현(Phase 2a)이 아닙니다. Phase 2a의 `watcher.py`는 이 표의 "관측 증거 스키마" 열을 확장(event/filesystem 스키마 추가)할 때 `OBSERVED_EVIDENCE_SCHEMAS`를 함께 갱신해야 하며, 스키마 없는 kind를 관측으로 승격하는 일은 허용되지 않습니다.
---
## 4. 6개 백엔드 기능 및 실측 증거 행렬 (Capability Matrix)
본 프로젝트가 지원하는 6개 에이전트 백엔드(`claude`, `agy`, `hermes`, `grok`, `opencode`, `codex`)의 현 환경 실측 상태 및 Gap 현황입니다 (모든 슬롯은 `tests/fixtures/issue_5/herdr/manifest.json`과 100% 일치):
| 백엔드 | 설치 바이너리 경로 | 활성 세션명 | 실측 캡처 슬롯 | 미관측 슬롯 (Gaps) 및 사유 | Provenance |
|---|---|---|---|---|---|
| **`claude`** | `~/.local/bin/claude` | `planner-reviewer-claude-01` | `idle` (`claude/idle.json`) | `working`, `blocked`, `done`, `unknown` (캡처 시점에 idle 상태였음) | `observed` |
| **`agy`** | `~/.local/bin/agy` | `creator-agy-01` | `working` (`agy/working.json`), `wait_timeout` (`agy/wait_timeout.json`) | `idle`, `blocked`, `done`, `unknown` (작업 수행 중이며 권한 건너뛰기 적용) | `observed` |
| **`grok`** | `~/.grok/bin/grok` | `reviewer-creator-grok-01` | `idle` (`grok/idle.json`) | `working`, `blocked`, `done`, `unknown` (리뷰 대기 중 유휴 상태 캡처) | `observed` |
| **`codex`** | `~/.local/bin/codex` | `planner-reviewer-codex-01` | `done` (`codex/done.json`) | `idle`, `working`, `blocked`, `unknown` (이전 플래너 작업 완료 직후 캡처) | `observed` |
| **`hermes`** | `~/.local/bin/hermes` | N/A (미가동) | 없음 | `idle`, `working`, `blocked`, `done`, `unknown` (Herdr 서버에 활성 세션 부재) | `unobserved` |
| **`opencode`**| `~/.opencode/bin/opencode` | N/A (미가동) | 없음 | `idle`, `working`, `blocked`, `done`, `unknown` (Herdr 서버에 활성 세션 부재) | `unobserved` |
> 📌 **정직한 증거 보고 원칙 (D3, D6)**:
> 1. 오직 실제로 실행하여 캡처한 슬롯만 `observed`로 인정하며, 모의 실행이나 가상 시뮬레이션을 네이티브 캡처로 둔갑시키지 않습니다.
> 2. `hermes`와 `opencode`의 미가동 슬롯 및 기존 4개 백엔드의 미관측 상태는 모두 정직하게 `unobserved gap`으로 기록합니다.
> 3. Grok 세션 등은 캡처 시점에 `idle`이었으나 리뷰 실행 시 `working`으로 전이되므로, 정적 캡처가 에이전트의 영구 불변 능력을 뜻하지 않음을 명시합니다.
---
## 5. 복합 시나리오 기대 정책 (Composite Expected Policy)
Watcher가 다중 채널 증거를 결합하여 내리는 최종 완료 판정 정책:
1. **`initial_idle`**: 디스패치 기록은 있으나 해당 시도의 실행 앵커가 없으면 ➔ **`hold`** (완료 거부).
2. **`tool_running`**: Herdr는 `working`이나 하네스에서 도구 실행 중이면 ➔ **`hold`** (대기).
3. **`report_pause_15s`**: 파일이 15초간 멈추었으나 확정 매니페스트가 없으면 ➔ **`hold`** (대기).
4. **`finalized_candidate`**: 4중 귀속 일치 + Fresh `idle/done` + 원자적 매니페스트/해시 일치 ➔ **`candidate_approved`** (CAS 커밋 허용).
5. **`blocked_permission`**: Herdr가 `blocked`이면 ➔ **`hold_blocked`** (완료 금지).
6. **`transport_disconnected`**: 소켓 통신 실패 시 ➔ **`hold_reconnect`** (스냅샷 재동기화).
7. **`restarted_generation`**: PID/세션 식별자가 변경된 과거 시도의 신호 ➔ **`reject_stale`** (전이 거부).