docs(issue-5): add phase 0 contract specifications, state transitions, and implementation plan
This commit is contained in:
1 parent
fa59741a9b
commit
720774c1e7
5 files changed
+525
No files matched your search
@@ -0,0 +1,141 @@
|
||||
# 📑 Issue #5 Baseline Contract Specification
|
||||
|
||||
- **Document Version**: Rev.3 (Post Cross-Review Corrections)
|
||||
- **Issue**: [Gitea Issue #5 — `[Refactor] lib.sh 작업 위임 로직 및 run_loop.sh 오케스트레이터의 Python 모듈화 (lib_py 승격)`](https://git.godopu.com/tmpl/multi-agent-mux/issues/5)
|
||||
- **Baseline Commit**: `47640e2fd7b108be29446a9384a343727e87edf8`
|
||||
- **Environment Baseline**: Python 3.9.6, Herdr 0.8.2, macOS Darwin 24.3.0
|
||||
- **Test Suite Baseline**: 469 tests collected initially; 484 tests collected with Phase 0 contract suite (+15 contract & mutation tests)
|
||||
- **Status**: Phase 0 Baseline Contract (Observed vs. Proposed vs. Gap)
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요 및 계약 정의 원칙
|
||||
|
||||
본 문서는 Issue #5(Python 모듈화 및 `lib_py` 승격)의 착수 기준선(Baseline)으로서, 현재 코드베이스(`run_loop.sh`, `lib.sh`, `delegate-job` 도구군)에 구현되어 동작 중인 CLI, 이벤트, 리포트, 락(Lock) 및 상태 전이 계약을 엄밀하게 분류하여 명문화합니다.
|
||||
|
||||
각 항목은 다음 세 범주로 구분하여 기록합니다:
|
||||
- **`[Observed]`**: 현재 소스 코드 및 단위 테스트에서 실제로 확인된 동작.
|
||||
- **`[Proposed]`**: 향후 Phase 1–4에서 구현하고자 하는 목표 동작.
|
||||
- **`[Gap]`**: 현행 동작과 목표 동작 사이의 불일치, 결함 또는 미구현 사항.
|
||||
|
||||
---
|
||||
|
||||
## 2. CLI 인터페이스 계약 (Orchestrator & Delegation)
|
||||
|
||||
### 2-1. `run_loop.sh` CLI 옵션 및 인수 파싱 계약
|
||||
|
||||
| 옵션 | 기본값 | 제약 및 검증 규칙 | 현행 소스 위치 | 대응 테스트 | 범주 |
|
||||
|---|---|---|---|---|---|
|
||||
| `--creator <session>` | 없음 (필수) | 비어있지 않은 문자열 필수(L95). 세션 레지스트리(`.mam/agent-sessions.yaml` / `.db`) 등록 및 실행 중 검증은 L160–185에서 수행 | `run_loop.sh:79, 95-98, 160-185` | `test_loop_cli.py:test_creator_parses_and_reaches_session_check` | `[Observed]` |
|
||||
| `--task <goal-text>` | 없음 (필수) | 작업 목표 문자열. 누락 시 fast-fail 및 `usage` 출력 후 exit 1 | `run_loop.sh:85, 95-98` | `test_loop_cli.py:test_missing_creator_and_task_fail_fast` | `[Observed]` |
|
||||
| `--target-agent <name>` | 제거됨 | 사용 즉시 에러 메시지 출력 후 exit 1 반환 (하위 호환 폐기) | `run_loop.sh:80-83` | `test_loop_cli.py:test_target_agent_is_rejected` | `[Observed]` |
|
||||
| `--plan` | `false` | 플래너 개입 및 설계 단계 활성화 플래그 | `run_loop.sh:56` | `test_loop_cli.py:test_planner_without_plan_fail_fast` | `[Observed]` |
|
||||
| `--planner <session>` | 자동 조회 | `--plan` 플래그 없이 지정 시 즉시 exit 1 반환. 미지정 시 등록된 planner 역할 세션 자동 선택 | `run_loop.sh:84, 100-105` | `test_loop_cli.py:test_explicit_planner_unregistered`, `test_explicit_planner_skips_auto_resolve` | `[Observed]` |
|
||||
| `--plan-talk <N>` | `1` | 숫자 regex(`^[0-9]+$`) 검증. 에러 메시지는 양수를 요구하지만 regex는 **`0`을 허용**함. 0 지정 시 토론 루프(`$turn -le $PLAN_TALK_TURNS`)를 완전히 건너뜀 | `run_loop.sh:57-62, 442` | `test_loop_cli.py` (regex 파싱) | `[Observed]` |
|
||||
| `--reviewer <A,B>` | 없음 | 쉼표 구분 리뷰어 세션 목록 | `run_loop.sh:63` | `test_loop_cli.py` | `[Observed]` |
|
||||
| `--all-reviewer` | `false` | 모든 활성 리뷰어 세션 강제. `--reviewer`와 동시 지정 시 **`--all-reviewer`가 우선**하며 `--reviewer` 목록은 무시되고 경고 로그 출력 | `run_loop.sh:64, 192-196` | 현행 전용 CLI 테스트 부재 (`[Gap]`) | `[Observed]` |
|
||||
| `--max-loop <N>` | `3` | 양수 정수 검증(`^[0-9]+$` and `N > 0`). 0 이하 시 에러 반환 | `run_loop.sh:65-70` | `test_loop_cli.py` | `[Observed]` |
|
||||
| `--max-rebut <N>` | `1` | 비음수 정수 검증(`^[0-9]+$`). 0 지정 시 반론(Rebuttal) 완전 비활성화 | `run_loop.sh:71-76` | `test_o1_rebuttal.py:test_v3_rebut_option_parsing_and_budget` | `[Observed]` |
|
||||
| `REBUT_TOTAL_BUDGET` | `MAX_REBUT * MAX_LOOP` | 전체 실행 기간 동안의 총 반론 허용 상한(Run-wide budget). 조기 소진 시 추가 반론 금지 | `run_loop.sh:93, 762-766` | `test_o1_rebuttal.py:test_v3_rebut_option_parsing_and_budget` | `[Observed]` |
|
||||
| `--verbose` | `false` | 상세 실행 타임라인 추적 출력 활성화 플래그 | `run_loop.sh:77` | N/A | `[Observed]` |
|
||||
| `--cleanup` | `false` | 루프 성공 종료 시 생성된 임시 작업 디렉터리(`.mam/jobs/<job>`) 및 가입자 출력 파일 삭제 | `run_loop.sh:78, 921-930` | N/A | `[Observed]` |
|
||||
| `-h`, `--help` | N/A | 도움말 출력 후 **exit 1**로 비정상 종료 (현재 코드의 관행적 동작) | `run_loop.sh:50, 86` | `test_loop_cli.py:test_help_lists_creator_not_target_agent` | `[Observed]` |
|
||||
|
||||
> ⚠️ **Help Exit Code Gap (`[Gap]`)**:
|
||||
> 현재 `run_loop.sh:50`은 `usage()` 내부에서 `exit 1`을 호출하여 `--help` 요청 시에도 exit 1을 반환합니다. 표준 CLI 관례(exit 0)와 다르나, 기존 서브프로세스 테스트들이 `exit 1`에 의존하므로 Phase 0에서는 현행 동작을 유지하며 의도적 변경은 Phase 3 wrapper 승격 시 별도 Decision으로 다룹니다.
|
||||
|
||||
### 2-2. 환경, 경로 및 수명주기 계약
|
||||
|
||||
1. **파이썬 인터프리터 탐색 (`_delegate_py_bin`)**:
|
||||
- `AGENT_PYTHON_BIN` ➔ `VIRTUAL_ENV/bin/python` ➔ 상위 디렉터리 `.venv/bin/python` 탐색 ➔ 시스템 `python3` 순으로 캐싱 및 결정 (`lib.sh:1841-1861`). `[Observed]`
|
||||
2. **런타임 고정 복사본 (B-13 Freeze Snapshot)**:
|
||||
- 루프 기동 시 `.agents/skills/` 트리를 `/tmp/mam-loop-freeze.XXXXXX`로 복사 후 해당 위치의 `run_loop.sh`로 `exec` 재실행 (`run_loop.sh:112-127`). `[Observed]`
|
||||
- 단, `multi-agent-mux-create`, `resume`, `stop` 등 1회성 스크립트는 Live Tree를 참조 (`[Observed]`).
|
||||
3. **루프 가드 및 시그널 정리**:
|
||||
- `MAM_LOOP_MARKER`(`.mam/loop-guard-active`)에 `pid`, `lstart`, `started` 타임스탬프를 원자적 하드링크로 기록 (`loop_lock.sh:69-75`). `[Observed]`
|
||||
- `trap _mam_release_guard EXIT INT TERM HUP`을 통해 비정상 종료 시에도 안전하게 가드 파일 및 Freeze 임시 디렉터리 정리 (`run_loop.sh:189`). `[Observed]`
|
||||
4. **작업 위임 함수 계약**:
|
||||
- `delegate_submit_job`: `registry.py register`를 실행하고 생성된 32-bit hex JID를 stdout으로만 출력 (`lib.sh:1876-1889`). `[Observed]`
|
||||
- `delegate_publish_event`: Non-fatal 계약 (`|| true`). 누락된 JID, 스크립트 부재, 브로커 전송 실패 시에도 0을 반환하여 호출자를 중단시키지 않음 (`lib.sh:1897-1905`). `[Observed]`
|
||||
- `start_watchdog`: 백그라운드에서 `reconcile.sh --subscribe --idle-timeout 0`를 실행하고 해당 데몬 PID를 stdout으로 반환 (`lib.sh:1908-1938`). `[Observed]`
|
||||
|
||||
---
|
||||
|
||||
## 3. 이벤트 스키마 및 수명주기 계약 (MQTT & Registry)
|
||||
|
||||
### 3-1. 이벤트 스키마 v1 명세
|
||||
- 필수 필드: `schema_version` (1), `seq` (단조 증가 양의 정수), `job_id` (JID), `event` (이벤트명), `timestamp` (ISO-8601 UTC `YYYY-MM-DDTHH:MM:SSZ`), `detail` (문자열 설명).
|
||||
- 확장 필드: `data` (딕셔너리), `data.hmac_sig` (HMAC-SHA256 16진수 서명).
|
||||
|
||||
### 3-2. 이벤트와 레지스트리 상태(Status) 매핑 현황
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> pending: register_job
|
||||
pending --> running: pick_pending / event 'started'
|
||||
running --> running: event 'progress' (상태 변경 없음)
|
||||
running --> running: event 'permission_required' (상태 변경 없음)
|
||||
running --> completed: event 'completed' (Terminal)
|
||||
running --> error: event 'error' (Terminal)
|
||||
running --> cancelled: event 'cancelled' / stop_session (Terminal)
|
||||
```
|
||||
|
||||
| 이벤트 이름 | 레지스트리 상태 전이 | 지속성(Retain) | 설명 | 범주 |
|
||||
|---|---|---|---|---|
|
||||
| `started` | `running` | `false` | 작업자가 작업을 수락하고 실행을 시작함 | `[Observed]` |
|
||||
| `progress` | 전이 없음 (`running` 유지) | `false` | 중간 진행 경과 보고. 레지스트리 status는 미변경 | `[Observed]` |
|
||||
| `permission_required` | 전이 없음 (`running` 유지) | `false` | 사용자 권한 승인 대기. 레지스트리 status는 미변경 | `[Observed]` |
|
||||
| `completed` | `completed` | `true` | 작업 성공 완료. Terminal 상태 | `[Observed]` |
|
||||
| `error` | `error` | `true` | 작업 실패 종료. Terminal 상태 | `[Observed]` |
|
||||
| `cancelled` | `cancelled` | `true` | 세션 중단 등에 의한 취소. Terminal 상태 | `[Observed]` |
|
||||
|
||||
> ⚠️ **이벤트-상태 간극 Gap (`[Gap]`)**:
|
||||
> 1. `progress`와 `permission_required`는 이벤트로 발행되지만 레지스트리 `status` enum(`VALID_STATUSES = ('pending', 'running', 'completed', 'error', 'cancelled')`)에 포함되지 않아, 레지스트리 파일만으로는 권한 승인 대기 여부를 직접 알 수 없습니다 (`mqtt_common.py:44-48`).
|
||||
> 2. `publish_event.py`의 Terminal 이벤트는 `("completed", "error", "cancelled")`이지만, `job_subscriber.py:46`의 Terminal 이벤트는 `("completed", "error")`로 정의되어 있습니다. 가입자가 디스크 폴백(`_check_disk_fallback`)을 수행할 때 비로소 `cancelled` 상태를 감지하여 가상 `error` 이벤트(`synth_event="error"`)로 변환하여 처리합니다 (`job_subscriber.py:80`).
|
||||
> 3. **Terminal Non-reentry (`[Proposed]` vs `[Gap]`)**: 한 번 Terminal 상태에 도달한 후 중복/상충 이벤트에 의해 상태가 역행하지 않는 불변식은 Phase 2a의 목표 계약(`[Proposed]`)이며, 현행 `update_job_status`는 락 내에서 단순 필드 덮어쓰기(`record.update(fields)`)를 수행하므로 완벽한 비역행이 보장되지 않는 간극(`[Gap]`)이 있습니다.
|
||||
|
||||
### 3-3. 보안 및 재생 공격 방어
|
||||
1. **인증 모드 분리**: `auth_token == null`일 때는 HMAC 검증 통과 (PoC 모드). `auth_token != null`일 때는 `data.hmac_sig` 검증 실패 시 즉시 폐기 (`job_subscriber.py:122-126`). `[Observed]`
|
||||
2. **단조 시퀀스 검증**: 가입자는 수신된 페이로드의 `seq`가 이전 `last_seq`보다 큰 경우에만 처리하고, 같거나 작은 패킷은 replay 공격으로 간주하여 drop (`job_subscriber.py:127-136`). `[Observed]`
|
||||
3. **로컬 감사 추적(Audit Log)**: MQTT 전송 성공/실패와 무관하게 `append_event`를 통해 `.mam/delegate_job_logs/<job_id>/{meta,events,status}`에 모든 시도 기록 (`mqtt_common.py:122, publish_event.py:208-219`). `[Observed]`
|
||||
4. **MQTT 전송 실패 시 로컬 상태 기록**: 브로커 접속 불가 시에도 `publish_event.py`는 로컬 레지스트리의 상태를 `new_status`로 동기화한 뒤 exit code 2로 종료 (`publish_event.py:223-233`). `[Observed]`
|
||||
|
||||
---
|
||||
|
||||
## 4. 리포트 파싱 및 승격 계약 (Report & Promotion)
|
||||
|
||||
### 4-1. 판정 토큰(Verdict) 추출 로직
|
||||
- `run_loop.sh:204-210`: `has_verdict "$file" "$verdict"`는 파일의 **마지막 비공백 행(last non-blank line)**을 추출하여 `^\[VERDICT: ${verdict}\][[:space:]]*\r?$` 패턴과 일치하는지 검사합니다.
|
||||
- `[Gap]`: 마지막 행이 마크다운 코드 블록(```) 내부에 있는 예제 문자열인 경우에도 현재 셸 정규식은 이를 유효한 판정 토큰으로 오인할 수 있습니다. Phase 2의 엄격한 파서(Strict Parser)는 코드 펜스 내부를 제외해야 합니다.
|
||||
- 리포트 파일 위치 탐색(`find_report`): `find ".mam/jobs/$1" -maxdepth 2 -name "report-final.md" | head -n 1`로 첫 번째 일치 파일을 취하며, 정확한 단일 경로 강제가 아닙니다 (`run_loop.sh:234`). `[Observed]`
|
||||
|
||||
### 4-2. 승격(Promotion) 및 정리(Cleanup) 계약
|
||||
1. **계획서 승격**: `CURRENT_PLAN`을 `.agents/reports/$PLANNER_SESSION/plan-${PLAN_JOB_ID}.md.tmp`에 기록 후 `mv -f`로 원자적 교체 (`run_loop.sh:895-901`). `[Observed]`
|
||||
2. **리뷰 리포트 승격**: 각 리뷰어의 `report-final.md`를 `.agents/reports/$rev/report-${job_id}.md`로 복사 및 이동 (`run_loop.sh:903-916`). `[Observed]`
|
||||
3. **배치 비원자성 (`[Gap]`)**: 다수의 리뷰 리포트를 승격할 때 개별 파일 단위로 `cp && mv`를 수행하므로, 중간 크래시 발생 시 일부 리포트만 승격되는 부분 성공 위험이 존재합니다.
|
||||
|
||||
---
|
||||
|
||||
## 5. 잠금(Lock) 및 동시성 제어 계약
|
||||
|
||||
### 5-1. 락 체계 및 획득 순서
|
||||
1. **Workspace Loop Guard**: `.mam/loop-guard-active` (원자적 하드링크 + `pid`/`lstart` 스틸 재시도).
|
||||
2. **Session Registry Lock**: `.mam/agent-sessions.db` (SQLite WAL 동시성 및 트랜잭션).
|
||||
3. **Job Registry Lock**: `.mam/jobs/<id>.lock` 또는 레지스트리 디렉터리 락 (`fcntl.flock(LOCK_EX)`).
|
||||
|
||||
### 5-2. `update_job_status`의 비-CAS 특성 (`[Gap]`)
|
||||
- 현재 `mqtt_common.py:377-403`의 `update_job_status`는 락을 획득한 후 `record.update(fields)`로 단순히 필드를 덮어쓰며, 호출자가 기대한 이전 상태(`expected_status`)나 attempt 버전을 비교하지 않습니다.
|
||||
- 또한 `registry.py:129-131`의 `pick_pending`은 `update_job_status`를 거치지 않고 직접 파일에 `chosen["status"] = "running"`을 씁니다.
|
||||
- **`[Proposed]`**: Phase 2a에서는 모든 상태 변경이 단일 엔진의 원자적 CAS(Compare-And-Swap) 인터페이스를 통해서만 수행되도록 통일해야 합니다.
|
||||
|
||||
---
|
||||
|
||||
## 6. 소스 및 테스트 매핑 요약
|
||||
|
||||
| 구성요소 | 핵심 소스 파일 | 핵심 테스트 파일 | 기준선 상태 |
|
||||
|---|---|---|---|
|
||||
| 오케스트레이터 CLI | `run_loop.sh` | `tests/test_loop_cli.py` | 9개 테스트 통과 |
|
||||
| 루프 가드 락 | `loop_lock.sh` | `tests/test_o2_race_free_lock.py` | 22개 테스트 통과 |
|
||||
| 반론 상한선 | `run_loop.sh` | `tests/test_o1_rebuttal.py` | 10개 테스트 통과 |
|
||||
| 위임 헬퍼 | `lib.sh` | `tests/test_tier1_unit.py` | 단위 테스트 통과 |
|
||||
| 작업 레지스트리 | `registry.py`, `mqtt_common.py` | `tests/test_tier2_component.py` | fcntl 기반 락킹 |
|
||||
@@ -0,0 +1,111 @@
|
||||
# 📑 Herdr Runtime Contract and Backend Capability Matrix
|
||||
|
||||
- **Document Version**: Rev.3 (Post Cross-Review Corrections)
|
||||
- **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 내부 필드를 절대 주입하지 않습니다.
|
||||
|
||||
---
|
||||
|
||||
## 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`** (전이 거부).
|
||||
@@ -0,0 +1,63 @@
|
||||
# 📑 Issue #5 Implementation Plan (Phase 1–4 Execution & Rollback Specification)
|
||||
|
||||
- **Document Version**: Rev.4 (Post Revision Review Corrections)
|
||||
- **Issue**: [Gitea Issue #5](https://git.godopu.com/tmpl/multi-agent-mux/issues/5)
|
||||
- **Status**: Target Architecture Roadmap & Execution Gate Criteria
|
||||
|
||||
---
|
||||
|
||||
## 1. 개요 및 마이그레이션 원칙
|
||||
|
||||
본 문서는 Issue #5(Python 모듈화 및 `lib_py` 승격)의 단계적 구현(Phase 1–4) 순서, 필수 완료 기준(Gate), 롤백 절차, 그리고 명시적 비범위(Out of Scope)를 규정합니다.
|
||||
|
||||
### 📌 핵심 원칙:
|
||||
1. **점진적 격리 및 무중단 전환**: 프로덕션 셸 오케스트레이션(`run_loop.sh`)은 Phase 3 이전까지 계속해서 활성 상태를 유지하며, 파이썬 모듈은 독립된 패키지(`lib_py`)로 단계적으로 승격됩니다.
|
||||
2. **동일 작업공간 다중 Writer 절대 금지**: 과도기적 단계에서도 특정 워크스페이스에는 오직 단 하나의 상태 제어자(신규 엔진 또는 구형 셸 루프)만 실행되어야 합니다.
|
||||
3. **Fail-Closed 및 Rollback 보장**: 각 단계는 이전 버전으로 즉시 되돌릴 수 있는 명확한 롤백 소유권과 체크포인트 인계 절차를 갖추어야 합니다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 단계별 구현 단위 및 필수 완료 기준 (Roadmap & Gates)
|
||||
|
||||
| 단계 | 작업 목표 | 세부 구현 내용 | 필수 완료 게이트 (Gate Criteria) | 롤백 절차 (Rollback Path) |
|
||||
|---|---|---|---|---|
|
||||
| **Phase 0**<br>*(현재)* | **사전 계약 고정 및 Fixture 수집** | • CLI, 이벤트, 리포트, 락 계약 기준선 문서화<br>• Herdr 0.8.2 네이티브 캡처 및 복합 Watcher 시나리오 2계층 분리<br>• 6개 백엔드 실측 증거 행렬 수집 | • 484개 전체 테스트 수집 및 회귀 0-Fail (100% 통과)<br>• Fixture 스키마 및 증거 경계 검증 통과<br>• 프로덕션 코드 무변경 diff 검증 | 미추적 산출물 즉시 삭제 (`rm -rf docs/contracts/issue-5 tests/fixtures/issue_5 tests/test_issue5_phase0_contracts.py`) |
|
||||
| **Phase 1** | **`lib_py/delegation/` 추출** | • `lib.sh` L1833 이하 위임 헬퍼를 파이썬 서비스 클래스로 추출<br>• `start_watchdog`을 내부 명칭 `ensure_subscriber_daemon`으로 명명(기존 함수 별칭 보존)<br>• 장수 프로세스 단일 MQTT 연결 재사용 프로토타입 | • CLI/non-fatal/HMAC/seq/디스크 폴백 회귀 100% 통과<br>• 데몬 수명 및 PID 계약 일치 확인<br>• **자동완료 정책 변경 없음** | `lib.sh` 위임 헬퍼 호출을 이전 인라인/셸 방식으로 즉시 복원 |
|
||||
| **Phase 2a** | **`lib_py/loop/` 코어 & Shadow Watcher** | • `engine.py` (단일 전이 주체), `watcher.py` (`herdr agent wait` 래핑) 구현<br>• 모든 Writer에 대한 단일 CAS 트랜잭션 경계 구현<br>• 자동완료를 수행하지 않고 "Would-Complete" 로깅만 수행하는 Shadow 모드 가동 | • 락 내 비동기/I/O 배제 검증<br>• 소켓 단절/스냅샷 재연결 백오프 검증<br>• Shadow 모드가 레지스트리 상태를 쓰지 않음을 검증 | 1) Shadow 모니터 프로세스 종료 및 인메모리 큐 폐기<br>2) CAS 서비스는 하위 호환 모드로 유지하거나 `MAM_ENGINE_CAS=0` 플래그로 비활성화하여 레거시 무조건 필드 병합(`update_job_status`)으로 복귀<br>3) 기존 셸 루프 상태 및 레코드 불변 유지 |
|
||||
| **Phase 2b** | **이중 감지 자동완료 활성화 (Flag-Gated)** | • 4중 실행 귀속 + 리포트 확정 계약 + 단일 CAS 전이 게이트 연동<br>• Pending Notification 지연 알림 및 크래시 복구 구현<br>• 검증된 백엔드/작업에 한해 opt-in 플래그로 활성화 (기본값 OFF) | • 부수효과 작업 예외 및 무판정 fail-closed 테스트 완료<br>• 경쟁/재시작 시나리오 통과<br>• 플래그 OFF 시 기존 explicit event 경로 100% 보존 | 환경변수/플래그 `MAM_AUTO_COMPLETE=0` 설정; 이미 확정된 terminal 레코드는 보존하고 신규 자동완료만 중단 |
|
||||
| **Phase 3** | **`run_loop.sh` Thin Wrapper 전환 및 전면 회귀** | • `run_loop.sh`를 인자 전달 래퍼(`exec python3 -m lib_py.loop "$@"`)로 전환<br>• 플래너/리뷰어/반론/승격 오케스트레이션 로직 완전 이식<br>• 엄격한 판정 파서(Strict Parser) 적용 | • 5개 subprocess 전용 테스트(~1,621줄) 포함 전체 481개+ 테스트 0-Fail<br>• 시그널, 루프 가드, 임의 CWD 및 공백 경로 검증 통과 | **우아한 핸드오프 시퀀스**:<br>1) Quiesce: 실행 중인 Python 루프 프로세스에 종료 신호를 전달하여 신규 디스패치 중단<br>2) Checkpoint: 현재 루프 회차, 활성 Job ID 및 리뷰 상태를 `.mam/loop-checkpoint.json`에 원자적 기록<br>3) Exit: Python 루프 정상 종료 및 가드 해제<br>4) Resume: 백업된 셸 원본 스크립트로 `run_loop.sh` 복원 후 체크포인트 기반 재개 검증 |
|
||||
| **Phase 4** | **불변 런타임 검증 및 Freeze 제거** | • 기동 시 전체 모듈 상주(Eager Import) 및 자식 템플릿 불변성 검증<br>• 작업 도중 스킬 파일 교체 및 늦은 경로 스트레스 테스트 통과<br>• `/tmp/mam-loop-freeze.XXXXXX` 우회책 완전 제거 | • 런타임 자기수정 크래시(B-13) 재발 방지 검증<br>• Live Helper 버전 호환 게이트 검증 통과<br>• 운영 문서 및 배포 스크립트 갱신 | Freeze 복사 로직(`run_loop.sh:112-127`) 재활성화 및 라이브 헬퍼 버전 검사 복구 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 라이브 헬퍼 버전 호환성 및 작업 상태 전이 계약 (Live Helper Compatibility Gate)
|
||||
|
||||
통합 보고서(Rev.2) 및 B-13 합의에 따라, `multi-agent-mux-create`, `resume`, `stop` 등 1회성 스크립트는 Freeze 복사본이 아닌 Live Tree에서 실행됩니다. Phase 4에서 Freeze가 제거되기 전까지, 라이브 헬퍼와 고정 실행 엔진 간의 충돌을 방지하고 기존 작업 상태 전이 효과를 보존하기 위해 다음 규칙을 적용합니다:
|
||||
|
||||
1. **ABI 버전 및 프로토콜 해시 검증**:
|
||||
- Live Tree의 스크립트가 실행될 때, 현재 실행 중인 루프의 `MAM_VERSION` 또는 공통 라이브러리 인터페이스 해시를 비교합니다.
|
||||
- 메이저 버전 불일치 또는 레지스트리 스키마 변경이 감지되면, 라이브 헬퍼는 즉시 오류를 반환하고 안전하게 중단(fail-closed)합니다.
|
||||
2. **작업 취소(`cancelled`) 전이의 정규 서비스 경유 보존**:
|
||||
- `multi-agent-mux-stop/scripts/stop_session.sh`는 세션 강제 종료 시 잔여 활성 작업에 대해 `cancelled` 이벤트를 발행합니다 (`stop_session.sh:242, 322`).
|
||||
- 라이브 헬퍼는 레지스트리 JSON 파일을 임의로 변조하지 않고, `delegate_publish_event` 및 공통 상태 전이 서비스(Transition Service)를 호출하여 CAS 불변식을 준수하면서 작업을 정규 `cancelled` 상태로 전이시킵니다.
|
||||
- 이를 통해 세션 중단 시 작업이 영구 고착(hang)되지 않고 오케스트레이터 루프가 즉각 취소를 인지할 수 있도록 보장합니다.
|
||||
|
||||
---
|
||||
|
||||
## 4. 명시적 비범위(Out of Scope) 및 호환성 계약
|
||||
|
||||
1. **저장소 통합 금지**:
|
||||
- 세션 관리용 SQLite/YAML 저장소(`.mam/agent-sessions.yaml`, `.db`)와 비동기 작업 관리용 JSON 레지스트리(`.mam/jobs/`)를 하나로 통합하려 시도하지 않습니다.
|
||||
2. **원샷 스킬의 Live Tree 실행 유지**:
|
||||
- `multi-agent-mux-create`, `resume`, `stop` 등 1회성 셸 스크립트는 Freeze 복사본이 아닌 실제 작업 트리의 코드를 실행하도록 유지합니다.
|
||||
3. **타임아웃 3원 분리 보존**:
|
||||
- (a) 전체 벽시계 타임아웃, (b) MQTT 가입자 idle-timeout, (c) TUI idle 자동완료를 독립적으로 유지하며, 자동완료가 기존 타임아웃 메커니즘을 덮어쓰지 않습니다.
|
||||
|
||||
---
|
||||
|
||||
## 5. 성능 수용 기준 (Performance Acceptance Criteria)
|
||||
|
||||
고정밀 10ms 폴링 대신 실제 시스템 자원 측정을 기준으로 성능을 평가합니다:
|
||||
- **프로세스 포크 수 (Fork Count)**: 루프 반복당 서브프로세스 생성 횟수 대폭 감소 (셸 루프 대비 ≥70% 감축).
|
||||
- **유휴 CPU 점유율 (Idle CPU)**: 대기 중 CPU 사용률 1% 미만 유지 (이벤트 구동 및 유계 폴링).
|
||||
- **MQTT 연결 재사용**: 단일 장수 프로세스 내에서 다수의 작업 발행 시 연결 재수립 오버헤드 제거 (p50 레이턴시 단축).
|
||||
@@ -0,0 +1,120 @@
|
||||
# 📑 Target State Transitions and CAS Specification
|
||||
|
||||
- **Document Version**: Rev.2
|
||||
- **Issue**: [Gitea Issue #5](https://git.godopu.com/tmpl/multi-agent-mux/issues/5)
|
||||
- **Scope**: Target Compare-And-Swap (CAS) State Machine & Attribution Contract
|
||||
|
||||
---
|
||||
|
||||
## 1. 이벤트(Event)와 상태(Status)의 개념적 분리
|
||||
|
||||
기존 시스템의 혼선을 방지하기 위해 용어를 명확하게 분리하여 정의합니다:
|
||||
- **이벤트 (Event)**: 에이전트, 워처(Watcher), 또는 외부 운영자가 특정 시점에 메시징 백플레인(MQTT)이나 로컬 채널로 전달하는 **사건(Fact)**입니다 (`started`, `progress`, `permission_required`, `completed`, `error`, `cancelled`).
|
||||
- **상태 (Status)**: 단일 상태 제어 엔진(Engine)이 원자적 CAS 트랜잭션을 통해 공통 레지스트리에 영속적으로 기록하는 **현재 작업의 공식 생애주기 단계**입니다 (`pending`, `running`, `completed`, `error`, `cancelled`).
|
||||
|
||||
> 📌 **핵심 원칙**: 이벤트는 상태를 변경해 달라는 **전이 요청(Transition Request)**일 뿐이며, 엔진의 유효성 검증과 CAS 게이트를 통과하기 전까지는 레지스트리 상태가 변경되지 않습니다.
|
||||
|
||||
---
|
||||
|
||||
## 2. 4중 실행 귀속(Execution Attribution) 보장 계약
|
||||
|
||||
단순히 에이전트 터미널이 유휴(`idle`) 상태라고 해서 작업이 성공적으로 종료되었다고 추론(Inferred Completion)해서는 안 됩니다. 이전 작업의 잔여 유휴 상태, 세션 재사용, 또는 프로세스 재시작으로 인한 오탐을 원천 방지하기 위해 다음 **4중 식별자 튜플**을 필수로 검증합니다:
|
||||
|
||||
$$\text{Attribution Tuple} = (\text{job\_id}, \text{attempt\_id}, \text{agent\_session\_uuid}, \text{pane\_process\_generation})$$
|
||||
|
||||
1. **`job_id`**: 등록된 고유 작업 식별자 (32-bit hex 또는 UUID).
|
||||
2. **`attempt_id`**: 단일 Job 내에서 재시도 시 단조 증가하는 시도 번호.
|
||||
3. **`agent_session_uuid`**: 에이전트 프로세스 기동 시 할당된 영속 고유 식별자.
|
||||
4. **`pane_process_generation`**: TMUX/Herdr 창 및 기저 셸 프로세스의 시작 시각(`lstart`) 및 PID 조합.
|
||||
|
||||
### 귀속 및 실행 앵커 불변식:
|
||||
- 동일 에이전트 세션에는 동시에 단 하나의 활성 작업(`job_id`)만 배정됩니다.
|
||||
- 디스패치 확인(`dispatch_ack`) 이후, 해당 `attempt_id`에 속하는 실제 터미널 출력 및 활동(Execution Anchor)이 관측된 경우에만 완료 후보로 인정합니다.
|
||||
- 네이티브 API가 제공하지 못하는 식별자는 `null + reason`으로 표기하며, 가짜 값을 임의로 주입하지 않습니다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 목표 CAS 상태 전이표 (Target State Machine)
|
||||
|
||||
모든 상태 변경은 Registry Lock 보호 하에서 기대 상태(`expected_state`), 시도 세대(`attempt_id`), 버전 번호(`version`)를 원자적으로 비교한 후 갱신(CAS)합니다.
|
||||
|
||||
| 현재 상태 (`Current`) | 요청 신호 (`Request`) | 전이 조건 및 검증 규칙 | 결과 상태 (`Next`) | 비고 및 갈등 처리 |
|
||||
|---|---|---|---|---|
|
||||
| `pending` | `started` / `claim` | 작업 할당 세션 및 `attempt_id` 일치 확인 | `running` | `started`는 상태 enum이 아니며 `running`으로 전이 |
|
||||
| `running` | `progress` | 비-Terminal 유지. 최근 활동 시각 및 세부 정보 갱신 | `running` | 상태 변경 없이 메타데이터만 갱신 |
|
||||
| `running` | `permission_required` | 비-Terminal 유지. `blocked_reason` 기록 | `running` | 권한 대기 중에는 자동완료 전면 금지 |
|
||||
| `running` | `explicit terminal` | 유효한 작업자 세션의 서명된 `completed`/`error`/`cancelled` 수신 | 요청된 Terminal | 정상 종료 커밋 |
|
||||
| `running` | `inferred completed` | 4중 귀속 일치 + 실행 앵커 확인 + 확정 해시/매니페스트 존재 + Fresh `idle/done` + 블로커 없음 + 부수효과 검증 | `completed` | Fail-Closed AND 게이트 통과 시에만 CAS 커밋 |
|
||||
| `terminal` | 동일 논리적 완료 재수신 | 기 커밋된 Terminal 상태와 동일한 요청 | `terminal` (No-op) | 멱등(Idempotent) ACK 반환, 추가 디스패치 금지 |
|
||||
| `terminal` | 상충 신호 (`progress`/다른 Terminal) | 이미 Terminal 상태에 도달한 후 늦게 도착한 신호 | 기 커밋 상태 유지 | 상태 역행 및 덮어쓰기 거부, Conflict Audit Log 기록 |
|
||||
| 임의 상태 | Stale Attempt / Generation | 과거 시도 세대 또는 프로세스 세대 불일치 신호 | 전이 거부 | Stale Attempt 경고 기록 및 패킷 폐기 |
|
||||
| `running` | `deadline_timeout` | 작업 타임아웃 경과 및 실행 중단 확인 | `error` | 타임아웃 원인 기록 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 리포트 확정 단계 (Report Finalization Lifecycle)
|
||||
|
||||
자동완료 추론 시 파일 쓰기 도중 발생하는 불완전 리포트 채택을 방지하기 위해 3단계 확정 계약을 수립합니다:
|
||||
|
||||
```
|
||||
[writing] ──(쓰기 진행 중)──> [stable_candidate] ──(원자적 교체 및 매니페스트)──> [finalized]
|
||||
```
|
||||
|
||||
1. **`writing` 단계**:
|
||||
- 에이전트가 임시 파일(`report-final.md.tmp`)을 작성 중인 상태.
|
||||
- 워처는 이 상태에서 완료를 절대 인정하지 않음 (Hold).
|
||||
2. **`stable_candidate` 단계 (1차/보조 필터)**:
|
||||
- 리포트 파일의 mtime이 단조 시계(monotonic clock) 기준 **최소 15초(기본 권장값)** 동안 불변인 상태.
|
||||
- ⚠️ **불변식**: **디바운스 경과(15s)만으로는 완료 권한이 성립되지 않습니다.** 이는 일시적인 LLM 생각 중단이나 네트워크 지연일 수 있으므로 오직 '후보 필터'로만 기능합니다.
|
||||
3. **`finalized` 단계 (2차/확정 계약)**:
|
||||
- 최종 대상 경로(`report-final.md`)로의 **원자적 이름 변경(Atomic Rename)** 완료.
|
||||
- 해당 시도의 `attempt_id`와 `report SHA-256 hash`가 명시된 **완료 매니페스트(Commit Manifest)** 생성 완료.
|
||||
- 외부 부수효과(Git push, API 호출)가 수반되는 작업인 경우, 해당 부수효과의 완료 증거가 매니페스트에 첨부되어야 함.
|
||||
- 리포트가 재작성되면 이전 해시는 즉시 무효화되며 새로운 확정 절차가 필요함.
|
||||
|
||||
---
|
||||
|
||||
## 5. 지연 알림 (Pending Notification) 및 트랜잭션 복구 설계
|
||||
|
||||
상태 전이 락(Registry Lock) 하에서 외부 I/O(MQTT 브로커 전송, 네트워크 호출)를 수행하면 데드락과 심각한 지연이 발생할 수 있습니다. 이를 방지하기 위해 다음 **내구성 지연 알림(Pending Notification) 트랜잭션**을 명세합니다:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Engine
|
||||
participant Registry as Job Registry (Disk)
|
||||
participant Publisher as Async Publisher (Wire)
|
||||
participant Broker as MQTT Broker
|
||||
|
||||
Note over Engine: 1. CAS 전이 결정
|
||||
activate Engine
|
||||
Engine->>Registry: 락 획득 (fcntl.flock)
|
||||
Engine->>Registry: 원자적 상태 커밋 (status=completed, pending_notification=intent)
|
||||
Engine->>Registry: 락 즉시 해제
|
||||
deactivate Engine
|
||||
|
||||
Note over Publisher: 2. 비동기 알림 전송
|
||||
Publisher->>Broker: MQTT 이벤트 발행 (HMAC 서명 포함)
|
||||
alt 전송 성공
|
||||
Broker-->>Publisher: ACK (PUBACK)
|
||||
Publisher->>Registry: pending_notification 완료 표시
|
||||
else 전송 실패 / 크래시
|
||||
Note over Publisher: 백오프 재시도 및 기동 시 미완료 알림 복구 전송
|
||||
end
|
||||
```
|
||||
|
||||
### 트랜잭션 보장 규칙:
|
||||
1. **단일 내구성 작업 레코드**:
|
||||
- 독립된 별도의 Outbox DB를 신설하지 않고, 기존 `.mam/jobs/<id>.json` 레코드 내에 `pending_notification: { event, timestamp, seq, signature }`를 원자적으로 기록합니다.
|
||||
2. **락 해제 후 전송**:
|
||||
- 레지스트리 락을 해제한 후 비동기 워커가 MQTT 발행을 수행합니다.
|
||||
3. **크래시 복구(Crash Recovery)**:
|
||||
- 엔진 재시작 시 `status in TERMINAL`이면서 `pending_notification`이 남아있는 레코드를 스캔하여 멱등적으로 재전송합니다.
|
||||
|
||||
---
|
||||
|
||||
## 6. 권한 차단(Permission Blocked) 해제 정책
|
||||
|
||||
- 에이전트 터미널이 `blocked` (권한 승인 대기) 상태로 관측되면, **어떠한 경우에도 자동완료 전이를 시도하지 않습니다.**
|
||||
- 차단 해제 판단:
|
||||
- 묵시적 전이 금지: 단순히 다음 관측에서 `working`이 되었다고 해서 이전 차단이 정당하게 해결되었다고 단정하지 않습니다.
|
||||
- 명시적 승인 증거 또는 운영자의 승인 입력 로그가 확인되지 않는 한, 작업은 `running (blocked)` 상태를 유지하며 데드라인 도달 시 안전하게 타임아웃 처리합니다.
|
||||
@@ -0,0 +1,90 @@
|
||||
# 📑 Issue #5 Task Checklist & Verification Evidence
|
||||
|
||||
- **Document Version**: Rev.3 (Post Cross-Review Corrections)
|
||||
- **Issue**: [Gitea Issue #5](https://git.godopu.com/tmpl/multi-agent-mux/issues/5)
|
||||
- **Status**: Phase 0 Complete / Phase 1–4 Staged
|
||||
|
||||
---
|
||||
|
||||
## Phase 0: 사전 계약 기준선 및 Fixture 수집 (이번 실행 범위)
|
||||
|
||||
- [x] **P0-01** 기준 commit, dirty diff, Herdr/Python 버전, pytest collection 수 기록.
|
||||
- *증거*: Commit `47640e2`, Herdr `0.8.2`, Python `3.9.6`, Pytest `469 tests collected initially` ➔ `484 tests collected with Phase 0 suite` (+15 contract & mutation tests).
|
||||
- [x] **P0-02** `docs/contracts/issue-5/baseline.md` 작성: 공개 CLI/default/invalid input/stdout-stderr/exit/interpreter/path/signal 계약과 소스·기존 테스트 매핑.
|
||||
- *증거*: `docs/contracts/issue-5/baseline.md` §2 (`--all-reviewer` 우선순위 및 L192–196 소스 매핑 반영).
|
||||
- [x] **P0-03** event schema/HMAC/seq/retain/audit/디스크 fallback 및 publisher 실패 후 로컬 commit 동작 기록.
|
||||
- *증거*: `docs/contracts/issue-5/baseline.md` §3 (감사 로그 경로 `.mam/delegate_job_logs/<job_id>/` 정확히 반영).
|
||||
- [x] **P0-04** event와 registry status 분리; permission clearance·cancelled subscriber 차이·pick_pending 등 writer inventory/gap 기록.
|
||||
- *증거*: `docs/contracts/issue-5/baseline.md` §3, `job_subscriber.py:46, 80` vs `publish_event.py:49` 간극 명시.
|
||||
- [x] **P0-05** report strictness/promotion/cleanup 및 job/session/loop lock 획득·해제/ownership 계약 기록.
|
||||
- *증거*: `docs/contracts/issue-5/baseline.md` §4, §5.
|
||||
- [x] **P0-06** `docs/contracts/issue-5/state-transitions.md` 작성: 4중 귀속, dispatch anchor, target CAS 표와 duplicate/conflict/deadline 정책.
|
||||
- *증거*: `docs/contracts/issue-5/state-transitions.md` §2, §3.
|
||||
- [x] **P0-07** writing/stable_candidate/finalized 및 manifest/hash/부수효과 확정 계약 기록; debounce만으로 completed 금지.
|
||||
- *증거*: `docs/contracts/issue-5/state-transitions.md` §4.
|
||||
- [x] **P0-08** Herdr native wait version/help/spec 및 단일 결과 capture 수집. timeout/unknown target/transport failure와 unknown 상태를 분리하고 외부 collector의 before/after 참조를 검증.
|
||||
- *증거*: `tests/fixtures/issue_5/herdr/wait-contract.json`, `docs/contracts/issue-5/herdr-runtime.md` §1 (timeout stderr 채널 반영).
|
||||
- [x] **P0-09** claude native 상태/call-outcome 행렬·실측 provenance·미관측 gap 기록.
|
||||
- *증거*: `tests/fixtures/issue_5/herdr/claude/idle.json`, `herdr/manifest.json`.
|
||||
- [x] **P0-10** agy native 상태/call-outcome 행렬·실측 provenance·미관측 gap 기록.
|
||||
- *증거*: `tests/fixtures/issue_5/herdr/agy/working.json`, `tests/fixtures/issue_5/herdr/agy/wait_timeout.json`, `herdr/manifest.json`.
|
||||
- [x] **P0-11** hermes native 상태/call-outcome 행렬·실측 provenance·미관측 gap 기록.
|
||||
- *증거*: `herdr/manifest.json` (미가동 세션 gap으로 정직하게 기록).
|
||||
- [x] **P0-12** grok native 상태/call-outcome 행렬·실측 provenance·미관측 gap 기록.
|
||||
- *증거*: `tests/fixtures/issue_5/herdr/grok/idle.json`, `herdr/manifest.json`.
|
||||
- [x] **P0-13** opencode native 상태/call-outcome 행렬·실측 provenance·미관측 gap 기록.
|
||||
- *증거*: `herdr/manifest.json` (미가동 세션 gap으로 정직하게 기록).
|
||||
- [x] **P0-14** codex native 상태/call-outcome 행렬·실측 provenance·미관측 gap 기록.
|
||||
- *증거*: `tests/fixtures/issue_5/herdr/codex/done.json`, `herdr/manifest.json`.
|
||||
- [x] **P0-15** `herdr/manifest.json`과 `watcher/manifest.json` 분리. native observed 캡처만 저장; watcher timeline source별 observed/simulated, 전체 mixed 여부, identity association과 redaction 검증.
|
||||
- *증거*: `tests/fixtures/issue_5/herdr/manifest.json`, `tests/fixtures/issue_5/watcher/manifest.json`.
|
||||
- [x] **P0-16** 두 계층 schema/reference/coverage 테스트 추가: native payload에 job/attempt/report phase 주입 거부, 오류와 상태 분리, simulated를 observed로 표기 거부. 운영 session 쓰기 금지.
|
||||
- *증거*: `tests/test_issue5_phase0_contracts.py`.
|
||||
- [x] **P0-16a** watcher 복합 시나리오 7종 작성: initial idle, tool active, report pause, finalized candidate, blocked, disconnect, generation restart. source별 증거/가상 시계/expected policy를 구분.
|
||||
- *증거*: `tests/fixtures/issue_5/watcher/*.json` (7개 시나리오 완료).
|
||||
- [x] **P0-16b** `herdr-runtime.md`에 native API·단일 wait·외부 collector·file/event/dispatch source 경계를 명시.
|
||||
- *증거*: `docs/contracts/issue-5/herdr-runtime.md`.
|
||||
- [x] **P0-17** Phase 1–4 `implementation_plan.md`/`task.md` 작성, live-helper version/공통 CAS/notification 복구 결정 기록.
|
||||
- *증거*: `docs/contracts/issue-5/implementation_plan.md`, `docs/contracts/issue-5/task.md`.
|
||||
- [x] **P0-18** 관련 CLI/lock/rebuttal/adapter 테스트와 전체 suite 실행; 수집·실행·통과·실패 숫자 분리.
|
||||
- *증거*: 전체 484개 수집, 선행 전체 실행 481개 통과(0 failed, 0 skipped, 786.18s), 15개 계약/변이 테스트 전체 통과, 집중 코어 회귀 155개 통과(60.48s).
|
||||
- [x] **P0-19** 문서 링크/JSON/git diff --check/production 코드 무변경 확인.
|
||||
- *증거*: `git diff --check` 클린, 공백 린트 완료, 프로덕션 `.sh`/`.py` 무변경 (오직 `docs/`, `tests/` 변경).
|
||||
- [x] **P0-20** 결과 보고: Phase 0 산출물 완료 여부, backend별 native live gap·mixed/simulated scenario 현황, 남은 gate를 별도 명시하고 peer review 요청.
|
||||
- *증거*: `.mam/jobs/61f2fa87/agy-reports/report-final.md`.
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: `lib_py/delegation/` 구축 (후속 승인된 작업)
|
||||
- [ ] **P1-01** 기존 delegation import 서비스/CLI adapter 추출; 저장소 통합 없이 nonfatal·daemon PID 계약 보존.
|
||||
- [ ] **P1-02** 장수 프로세스 단일 MQTT 연결 재사용과 benchmark; legacy publisher 호환 테스트.
|
||||
- [ ] **P1-03** HMAC/seq/retain/disk fallback 회귀와 rollback 확인. 자동완료 비활성 유지.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2a: `lib_py/loop/` 코어 & Shadow Watcher (후속)
|
||||
- [ ] **P2A-01** 단일 engine/checkpoint와 모든 writer 공통 CAS 경계 구현.
|
||||
- [ ] **P2A-02** 4중 귀속/dispatch 증거/재연결 resnapshot/timeout/queue cancellation 처리.
|
||||
- [ ] **P2A-03** shadow-only watcher가 어떤 terminal도 쓰지 않음을 검증.
|
||||
- [ ] **P2A-04** race/attempt 재사용/부분 report/도구 실행/blocked/unknown/재시작 fixture 시나리오 실행.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2b: 이중 감지 자동완료 활성화 (후속)
|
||||
- [ ] **P2B-01** report finalization/manifest/hash 및 외부 부수효과 evidence protocol 구현.
|
||||
- [ ] **P2B-02** terminal+pending notification commit/재전송·재시작·늦은 conflict 처리 검증.
|
||||
- [ ] **P2B-03** 검증된 backend/job에만 default-off flag로 활성화; unsupported explicit event 경로 보존.
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: `run_loop.sh` Thin Wrapper 전환 및 전면 회귀 (후속)
|
||||
- [ ] **P3-01** planner/reviewer/rebuttal/promotion 및 strict 판정/예산/중재/합의 테스트.
|
||||
- [ ] **P3-02** thin wrapper 인자·interpreter·cwd·signal·guard·child cleanup 호환 검증.
|
||||
- [ ] **P3-03** 전체 suite+신규 race/restart/lifecycle, writer 단일성, rollback checkpoint 인계 검증.
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: 불변 런타임 검증 및 Freeze 제거 (후속)
|
||||
- [ ] **P4-01** eager import+child/helper/template 실행 버전 정책 구현/검증.
|
||||
- [ ] **P4-02** 실행 중 코드 교체와 늦은 재심/승격/rollback 스트레스 검증.
|
||||
- [ ] **P4-03** gate 통과 후에만 Freeze 제거 및 운영 문서 갱신.
|
||||
Reference in new issue
Block a user