Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
1215 lines
214 KiB
Markdown
1215 lines
214 KiB
Markdown
# 📜 Multi-Agent Mux 버전 이력 (`VERSIONS.md`)
|
||
|
||
이 문서는 `multi-agent-mux` 프레임워크의 버전별 주요 기능 추가, 아키텍처 개선, 버그 수정 및 품질 검증 이력을 기록합니다.
|
||
|
||
---
|
||
|
||
## 📌 현재 버전 개요 (Current Release)
|
||
|
||
- **프레임워크 버전**: `v4.11.0`
|
||
- **최신 릴리스 일시**: 2026-10-27 (KST)
|
||
- **기준 브랜치**: `main`
|
||
- **핵심 아키텍처**:
|
||
- **Terminal 상태 단방향 불변성 (`engine.py`, Issue #6 Phase 1)**: `completed`, `error`, `cancelled` 터미널 상태 진입 후 후속 이벤트나 업데이트에 의한 덮어쓰기를 엄격히 차단(Terminal Immutability). `registry.py close` 서브커맨드 및 fail-closed 조기 종료 도입.
|
||
- **Herdr 턴 완료 핸드셰이크 (`lib.sh: wait_for_agent_idle`, Issue #6 Phase 2)**: 위임 에이전트(`claude`, `agy`, `hermes`, `codex` 등)의 프롬프트 전송 후 Herdr 뷰포트 및 프로세스 상태를 관측하여 비동기 턴 종료를 결정론적으로 대기하는 핸드셰이크 프로토콜 확립.
|
||
- **Loop 모드 `attempt_id` 추적 및 안전 전이 (Issue #6 Phase 3)**: 워커/리뷰어 전환 시 동일 회차 내 재전이 충돌을 방지하는 `attempt_id` 규약 적용 및 4단계 fail-closed `finalize_unfinished` 보장.
|
||
- **결정론적 종료 코드 계약 (Issue #6 Phase 4 & R1)**: `0` (Success), `1` (Failure / Inject error), `2` (Subscriber timeout; 단, 에이전트가 Herdr에서 working 중이면 벽시계 --timeout까지 디스크 재관측 후 fail-close), `3` (Broker unavailable), `5` (Precondition failed / session_busy) 표준화. (send_keys_safe 헬퍼의 배달 실패/busy 타임아웃 rc=4는 래퍼 차원에서 rc=1 실패로 수용).
|
||
- **프로토콜 기반 생존·전달 계약 (Issue #18)**: heartbeat 의무(N, 자동 타이머) · 무응답 판정(K×N) · 전달 확인(T 내 `started`) · retained `<prefix>/state` · 원격 전송(`remote:<name>`). TUI 스크래핑은 주입·다이얼로그·진단에만 남는다.
|
||
- **워크트리 운영 모델 2단계 (Issue #21 항목 4·5·7)**: 워크트리 준비·해체 헬퍼(변경 전 판정, untracked 포함, 검증된 단일 `--force`), 리뷰 identity(라운드별 스냅샷·stale 재리뷰·승격 직전 재확인·`identity.json`), 수용 사례 A1–A8.
|
||
- **잡 예산·라벨·멈춤 알림 (Issue #33·#30)**: 위임 잡 예산은 `--job-timeout`/`MAM_JOB_TIMEOUT_S`, 벽시계 초과는 `wall_clock_timeout`(무응답 `deadline_timeout` 과 구분), `lib_py/stall_watch.py` 가 `blocked`/`idle` 을 `progress stalled:*` 로 알리기만 한다(종결 근거는 #18 의 프로토콜 이벤트뿐).
|
||
- **설정 파일 로더 하나 (Issue #26)**: `.mam.env` 는 `lib_py/delegation/envfile.py` 한 곳이 읽는다(셸 해석 없음, 환경변수 우선, 읽을 수 없는 파일은 오류). bash 산술에 들어가는 정수 설정값은 `mam_int` 로 10진수.
|
||
- **4-Way 버전 락스텝**: `lib.sh`(`MAM_VERSION`) ↔ `lib_py/loop/eager.py`(`FRAMEWORK_VERSION`) ↔ `VERSIONS.md` ↔ 8개 `SKILL.md`.
|
||
|
||
---
|
||
|
||
## 🧭 스킬 패키지 버전 매트릭스 (Skills Version Matrix)
|
||
|
||
모든 8개 스킬은 YAML frontmatter 메타데이터(`author`, `version`, `platforms`, `environments`) 표준화를 통해 `v4.11.0`로 동기화되어 배포됩니다.
|
||
|
||
| 스킬명 | 버전 | 역할 및 주요 책임 | 상태 |
|
||
| :--- | :---: | :--- | :---: |
|
||
| **`multi-agent-mux-create`** | `4.11.0` | 에이전트 세션 신규 생성 및 Herdr 컨테이너 격리 스폰 | ✅ 배포 |
|
||
| **`multi-agent-mux-stop`** | `4.11.0` | 대화 UUID 원자적 캡처 및 세션 안전 종료 (Graceful Stop) | ✅ 배포 |
|
||
| **`multi-agent-mux-resume`** | `4.11.0` | 온디스크 대화 컨텍스트 기반 Tier-1 초고속 세션 복원 | ✅ 배포 |
|
||
| **`multi-agent-mux-status`** | `4.11.0` | 실시간 Herdr 세션 및 레지스트리 드리프트 스냅샷 조회 | ✅ 배포 |
|
||
| **`multi-agent-mux-monitor`** | `4.11.0` | YAML ↔ 런타임 상태 간 자율 조정자 (Reconciler Loop) | ✅ 배포 |
|
||
| **`multi-agent-mux-delegate-job`** | `4.11.0` | MQTT 이벤트 채널 기반 비동기 단위 작업 위임 | ✅ 배포 |
|
||
| **`multi-agent-mux-loop`** | `4.11.0` | Planner-Creator-Reviewer 3자 자율 계획·실행·피어리뷰 루프 | ✅ 배포 |
|
||
| **`multi-agent-mux-orc-onboard`** | `4.11.0` | 오케스트레이터 UUID 격리 등록 및 서브 세션 오염 방지 | ✅ 배포 |
|
||
|
||
---
|
||
|
||
## 📋 버전별 상세 변경 내역 (Changelog)
|
||
|
||
### 🚀 `v4.11.0` — attach 명령은 herdr 에 등록된 이름을 쓰고, 세션 시작·종료는 스킬로만 한다 (Issue #71) (2026-10-27)
|
||
|
||
> **마이너 (MINOR Release)**: 레지스트리의 `attach_command` 가 원래 세션 이름으로 herdr 를 불러, 이름이 herdr 의 규칙(소문자 `[a-z0-9_-]`, 32자 이하)을 벗어나면 붙지 못했다. 이제 attach 명령은 한 함수가 만들고, 레지스트리 행에서 `kill_command`·`start_command` 를 지웠으며, 레지스트리에 형식 버전(`registry_format: 2`)을 더했다. `status --json` 의 키가 빠지고 stop 의 동작(행 없는 세션 닫기)이 늘어 MINOR 로 올린다. PR #73.
|
||
|
||
#### ⚠️ 호환성
|
||
- 레지스트리 행에 `kill_command`·`start_command` 가 더 이상 없다. `status --json` 에서도 `start_command` 키가 빠졌다.
|
||
- `attach_command` 는 `herdr --session <server> agent attach <herdr 에 등록된 이름>` 이다. `default` 서버도 적는다.
|
||
- 옛 레지스트리(형식 키 없음 = 1)는 첫 쓰기 때 2 로 바뀐다. 첫 쓰기 전까지 status 에 옛 행이 보일 수 있다.
|
||
- 세션 시작은 create·resume, 종료는 stop 스킬로 한다. attach 만 보기 전용이라 herdr 명령을 직접 쓴다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **attach 명령 하나 (`lib_py/agents/sanitize.py` `attach_command`)**: create·resume·reconcile 이 모두 이 함수를 쓴다. 값은 `shlex.quote` 로 감싼다. lib.sh 래퍼의 `herdr agent attach <원래 이름>` 도 이름을 바꿔 부르고, 대상 뒤의 옵션(`--takeover`)은 그대로 넘긴다.
|
||
2. **행이 없는 세션의 stop (`stop_session.sh`)**:
|
||
- 레지스트리에 행이 없으면 herdr 세션만 닫고 레지스트리는 바꾸지 않는다. herdr 가 기록한 cwd 가 이 작업 공간 안일 때만 닫는다.
|
||
- 다른 작업 공간의 세션은 exit 5, 서버에 없으면 exit 1, 닫은 뒤에도 살아 있으면 exit 6 이다.
|
||
- 레지스트리를 읽지 못하면(YAML 깨짐, DB 손상, 테이블 없음) "행 없음" 으로 보지 않고 exit 1 로 멈춘다(`load_state_json strict`).
|
||
- 사용자에게 보여 주던 날 `kill-session` 안내 두 곳(create exit 43, `id_order.py`)은 stop 스킬 명령으로 바꿨다.
|
||
3. **이름 변환 규칙 하나 (`lib.sh`)**: `_sanitize_herdr_agent_name` 정의를 하나로 합쳤고 shim 은 `declare -f` 로 같은 함수를 받는다. `has-session` 의 `lower()[:32]` 대체 경로를 지웠다. 해시 도구가 없으면 python3 로, python3 도 없으면 오류로 멈춘다(`00000000` 을 붙이지 않는다). ASCII 가 아닌 이름·`-n`·줄바꿈이 든 이름도 Python 함수와 결과가 같다.
|
||
4. **레지스트리 형식 버전 2 (`lib_py/registry_format.py`, `lib_py/atomic_yaml.py`)**: 레지스트리를 쓰는 함수가 잠금(`BEGIN IMMEDIATE`) 안에서, 호출자의 변경 전에 모든 행을 바꾸고 YAML 도 다시 쓴다. 업데이트 뒤 부를 진입점: `PYTHONPATH=.agents/skills python3 -m lib_py.registry_format --yaml <agent-sessions.yaml>` (#72 에서 쓴다).
|
||
5. **stall_watch**: herdr 이름을 `StallWatch` 에서 한 번만 구해 `HerdrSource` 에 넘긴다.
|
||
|
||
#### ✅ 품질 검증
|
||
- **회귀**: `tests/test_issue71_herdr_names.py`(70개). base(v4.10.1)에서 68개가 실패한다.
|
||
- **리뷰 (`--all-reviewer`, 작성자 PR 의 리뷰 전용 루프)**: 1회차 3/4 PASS. codex 의 지적(행 없는 stop 의 `HERDR_SESSION_NAME=default` 처리)은 create 와 같은 서버 선택 규칙이라는 반론으로 철회됐다. 2회차(@5f60653) **4/4 PASS**. 리뷰어들이 macOS bash 3.2 에서 이름 변환이 Python 함수와 같은 것을 `LC_ALL=C`·UTF-8 양쪽에서 확인했다. 리포트는 `.agents/reports/<session>/` 에 있다.
|
||
- **전체 스위트 (macOS, Python 3.9.6, @5f60653)**: 2173 passed, 56 skipped, 2 failed. 실패는 부하에 민감한 타이밍 테스트 둘(`test_issue14_delivery_ack.py::test_wrapper_idle_timeout_grace_ignores_pane_busy_token`, `test_issue30_stall_watch.py::test_s13_unreliable_agent_status_turns_idle_off_but_keeps_blocked`)이고, 단독으로 3번 모두, 두 파일 전체로도 통과했다(60 passed, 1 skipped). 실제 `~/.claude`·`/private/tmp/claude-<uid>`·herdr 서버 pane 목록·opencode 의 data·state·config 에 이 실행이 남긴 것은 없었다.
|
||
- **작성자 확인 (Linux, Python 3.14)**: 2211 passed, 20 skipped, 실패 0.
|
||
- **알려진 비차단**: `name` 이 없는 행은 형식 변환에서 건너뛰므로 두 필드가 남을 수 있다. delegate-job 래퍼의 위임 안내 문구(`작업이 세션 ...에 전송되었습니다`)는 아직 옛 attach 형태(`HERDR_SESSION_NAME=... herdr agent attach <원래 이름>`)를 보여 준다.
|
||
- **병합**: merge commit 으로 병합한다. `test_issue23_24_startup_path.py` S11 이 이 브랜치의 커밋 `0fc162e` 를 기준으로 쓴다.
|
||
|
||
### 🚀 `v4.10.1` — stall_watch 가 herdr 에 변환된 에이전트 이름을 쓴다 (Issue #66) (2026-10-25)
|
||
|
||
> **패치 (PATCH Release)**: 세션 이름이 herdr 의 에이전트 이름 규칙(소문자 `[a-z0-9_-]`, 영문자로 시작, 32자 이하)을 벗어나면, 그 세션에 위임한 잡의 `stall_watch` 가 pane 을 찾지 못하고(`stall_watch:agent_not_found`) 멈춤 알림이 꺼졌다. 이제 herdr 를 부를 때만 `sanitize_herdr_agent_name()` 의 결과를 쓴다. 옵션·동작 계약은 바뀌지 않는다. PR #70.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **herdr 호출 (`lib_py/stall_watch.py`)**: `HerdrSource` 의 `agent get`·`agent read`·`agent prompt` 와 `StallWatch` 의 `watcher.wait_once` 가 변환된 이름을 쓴다. 32자를 넘는 이름(앞 23자 + `-` + sha1 8자), 대문자·`.` 이 든 이름이 해당한다. 이미 규칙에 맞는 이름은 그대로다.
|
||
2. **원래 이름이 남는 곳**: 로그와 알림은 원래 세션 이름을 쓴다. 에이전트 종류(`resolve_agent`)도 원래 이름의 `-<agent>` 접미사로 정한다. 긴 이름은 변환되면 접미사가 해시로 바뀌기 때문이다.
|
||
3. **mock herdr (`tests/conftest.py`)**: `strict_agent_names` 옵션을 켜면 실제 herdr 처럼 등록된(변환된) 이름에만 답하고, 다른 이름에는 `agent_not_found` 를 낸다. 기본은 꺼져 있어 기존 테스트의 동작은 같다.
|
||
|
||
#### ✅ 품질 검증
|
||
- **회귀**: `tests/test_issue66_stall_watch_sanitized_name.py`(7개). base(v4.10.0)에서 5개가 실패한다. 긴 이름·대문자 이름의 idle 재촉과 blocked 알림, 원래 이름의 로그, 접미사로 정하는 에이전트 종류, 규칙에 맞는 이름의 무변경을 확인한다.
|
||
- **리뷰 (`--all-reviewer`, 작성자 PR 의 리뷰 전용 루프)**: 1회차(@bb496fd) **4/4 PASS**. lib.sh `_sanitize_herdr_agent_name` 과 Python 함수의 결과가 같은 것도 확인했다. 리포트는 `.agents/reports/<session>/` 에 있다.
|
||
- **전체 스위트 (macOS, Python 3.9.6, @bb496fd)**: 2105 passed, 56 skipped, 실패 0. 실제 `~/.claude`·`/private/tmp/claude-<uid>`·herdr 서버 pane 목록·opencode 의 data·state·config 에 이 실행이 남긴 것은 없었다.
|
||
- **작성자 확인 (Linux)**: 전체 스위트 2140 passed, 20 skipped, 1 failed. 실패는 `tests/test_issue17_item4_decisions.py` 의 정리 경합 하나이고 단독으로 10번 모두 통과했다.
|
||
- **알려진 비차단**: lib.sh 에 `_sanitize_herdr_agent_name` 정의가 두 벌(:36, :293) 있다. 뒤의 것이 적용되고, sha 도구가 없을 때의 대처만 다르다. 이 PR 이전부터 있던 것이다.
|
||
|
||
### 🚀 `v4.10.0` — 같은 폴더의 같은 종류 에이전트가 서로의 대화 ID 를 쓰지 않는다 (Issue #63) (2026-10-23)
|
||
|
||
> **마이너 (MINOR Release)**: 같은 폴더에 ID 가 나중에 생기는 같은 종류 에이전트(agy·codex·hermes·opencode)가 둘 있으면, stop·purge·resume 이 `find_workspace_uuid` 의 추측으로 다른 행의 대화 ID 를 기록하거나 지우거나 이어받을 수 있었다. 이제 세 경로가 #40 의 판정을 같이 쓰고, 같은 폴더의 생성 순서를 정하는 새 대기 동작(`MAM_ID_ORDER_WAIT_S`)과 resume 의 새 거부(exit 1)가 추가되어 MINOR 로 올린다. PR #69.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **ID 판정 (`lib_py/agents/session_ids.py`, 새 파일)**: stop·purge·resume 이 같은 판정을 쓴다. 행의 own id 가 우선이고, 없으면 #40 의 `autopin.capture` 를 쓴다. 다른 행이 기록한 ID 는 쓰지도 지우지도 않는다. claude·grok 은 그대로다.
|
||
2. **stop·purge**:
|
||
- stop 은 프로세스를 끄기 전에 ID 를 정한다. herdr 가 이미 죽은 행은 확인할 수 없으므로 ID 를 기록하지 않고 이유를 남긴다.
|
||
- purge 는 own id 이거나 확인을 통과한 ID 만 지우고, 레지스트리를 쓰는 시점에 다른 행이 기록했는지 다시 확인한다.
|
||
3. **resume**:
|
||
- own id 가 없고 같은 폴더에 같은 종류 행이 둘 이상이면 추측하지 않고 새 대화로 띄운다.
|
||
- 해결한 ID 를 다른 행이 이미 기록했으면 띄우기 전에 거부한다(exit 1).
|
||
- 기록된 ID 가 더 이상 유효하지 않으면 새 대화로 띄우고 그 ID 를 지운다(`update_yaml_resumed.sh --drop-stale-id`).
|
||
4. **생성 순서 (`lib_py/agents/id_order.py`, 새 파일)**:
|
||
- 같은 폴더에서 같은 종류 에이전트가 생성 중이거나 ID 없이 실행 중이면, 새 create·resume 은 그 ID 가 기록될 때까지 기다린다. 폴더·에이전트 단위 잠금(`fcntl`)과 "생성 중" 표시를 쓰고, 폴더는 실제 경로로 비교한다.
|
||
- 상한은 `MAM_ID_ORDER_WAIT_S`(기본 600 s, 0 이면 기다리지 않음)다. 넘으면 아무것도 띄우지 않고 기다린 세션 이름을 알리며 거부한다(exit 1).
|
||
- create·resume 이 띄운 뒤 행을 등록하기 전에 죽어 "생성 중" 표시만 남으면, 그 herdr 세션이 살아 있는 동안 계속 막는다. herdr 가 세션이 없다고 확실히 답하거나 그 시도의 행이 등록되면 푼다. 조회 실패는 "모름"으로 보고 막으며, 조회도 대기 상한 안에서만 한다(상한 0 이면 묻지 않음).
|
||
5. **정리**: `find_workspace_uuid()` 가 ID 를 반환하고 다른 행의 ID 를 후보에서 뺀다(`exclude_ids`). 쓰이지 않게 된 `capture_conversation_id`(lib.sh)와 `CAPTURE_ID`(stop)를 지웠다.
|
||
|
||
#### ✅ 품질 검증
|
||
- **회귀**: `tests/test_issue63_same_cwd_ids.py`(27개). 완료 조건의 회귀 테스트는 base(v4.9.9)에서 실패하고 head 에서 통과한다.
|
||
- **리뷰 (`--all-reviewer`, 작성자 PR 의 리뷰 전용 루프, 수정은 claude creator)**: codex 가 고아 "생성 중" 표시 처리에서 차단 결함을 회차마다 찾았고 ede9c62·8f64174·7cac6f6·1614af5 에서 고쳤다. 마지막 루프 1회차(@1614af5) **4/4 PASS**. 리포트는 `.agents/reports/<session>/` 에 있다.
|
||
- **전체 스위트 (macOS, Python 3.9.6, @1614af5)**: 2097 passed, 56 skipped, 1 failed.
|
||
- 실패는 `tests/test_issue50_dispatch_herdr.py::test_c11_…` 하나다. 이 브랜치는 위임 코드를 건드리지 않고, 단독으로 세 번 돌리면 매번 통과했다(시간 의존).
|
||
- 실제 `~/.claude`·`/private/tmp/claude-<uid>`·herdr 서버 pane 목록·opencode 의 data·state·config 에 이 실행이 남긴 것은 없었다.
|
||
- **작성자 확인 (Linux)**: 전체 스위트와 실제 agy 두 개 시험(같은 폴더 동시 create·stop·동시 resume·B purge 뒤 A 의 대화 파일 보존).
|
||
- **알려진 비차단**:
|
||
- herdr 조회가 계속 실패하면 고아 표시가 남아 이후 create·resume 이 매번 상한까지 기다린다. 거부 메시지가 `herdr kill-session` 명령을 알려 준다.
|
||
- 등록 중에 herdr 목록이 잠깐 비어 있으면 표시가 풀릴 수 있다.
|
||
- `--no-onboard` 로 만든 세션은 첫 ID 가 기록될 때까지 다음 create 가 상한까지 기다린다(#68).
|
||
- presence lock 으로 ID 를 찾는 것과, MAM 밖에서 사람이 같은 폴더에 띄운 에이전트는 범위 밖이다.
|
||
|
||
### 🚀 `v4.9.9` — completed 를 확인한 쪽이 세션 ID 를 등록하고, agy 는 다른 작업 공간의 대화를 받지 않는다 (Issue #40) (2026-10-21)
|
||
|
||
> **패치 (PATCH Release)**: 세션 ID 가 생성 뒤에야 생기는 에이전트(agy·opencode·codex·hermes)는 지금까지 모니터 데몬의 reconcile 이 찾아 등록했다. 데몬이 없는 워크트리 creator 는 등록되지 않았고, agy 의 drift C 는 호스트 공용 대화 폴더의 모든 `*.db` 를 후보로 보아 다른 작업 공간의 대화 ID 를 등록할 수 있었다. 이제 잡의 completed 를 확인한 쪽이 같은 판정으로 한 번 등록하고, reconcile 도 그 판정을 쓴다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **어댑터 규격**: `BaseAgentAdapter.id_lifecycle`(`pre_assigned`: claude·grok / `post_discovered`: 나머지)과 `capture_active_session_id`. 세션 시작 뒤 갱신되고, 검증을 통과하고, 다른 행이 기록하지 않은 후보가 정확히 하나일 때만 ID 를 돌려준다. 아니면 이유(`no-candidate`, `ambiguous`, `unverifiable-owner` 등)를 돌려준다.
|
||
2. **agy**:
|
||
- `verify_artifact` 의 discover 모드는 `last_conversations.json` 이 다른 작업 공간에 매핑한 ID 를 거절한다. 다른 행이 기록한 ID 도 항상 제외한다.
|
||
- capture 는 캐시를 제안으로만 쓴다. 그 대화의 presence lock 이나 DB 를 행의 pane 프로세스(또는 그 자손)가 열고 있음을 `lsof`·`ps` 로 확인해야 등록한다. 확인할 수 없으면 등록하지 않는다.
|
||
3. **`lib_py/agents/autopin.py`**: `handle_completed` 를 completed 를 확인한 쪽이 부른다.
|
||
- 위임 래퍼(direct), `create --onboard`, 루프 `wait_for_job`(복구 경로).
|
||
- 로컬 herdr 잡만 처리한다(remote·human 제외).
|
||
- (잡, dispatch token, attempt) 하나는 한 번만 처리한다(잡 단위 flock 과 잡 폴더의 마커).
|
||
- 등록은 `atomic_dump_yaml` 로 쓰고 `session_id_source=discovered`, `session_id_verified=true` 를 남긴다. 실패하면 이유를 행에 남기고, 잡의 결과는 바꾸지 않는다.
|
||
- 에이전트가 실행하는 `publish_event.py` 에는 쓰기를 넣지 않았다.
|
||
4. **reconcile**: drift C 의 agy·hermes·opencode·codex 가 같은 capture 를 부른다. agy 의 `*.db` glob 은 없어졌다. `_pin_and_verify_resume` 은 `pin_row` 를 쓴다.
|
||
|
||
#### ✅ 품질 검증
|
||
- **회귀**: `tests/test_issue40_auto_pin.py`(27개). 이슈의 완료 조건 네 가지를 고정한다.
|
||
1. 다른 작업 공간의 ID 를 등록하지 않는다.
|
||
2. 데몬 없이(`MAM_NO_SUBSCRIBER_DAEMON=1`) 첫 completed 뒤 agy·opencode·codex·hermes 가 등록된다.
|
||
3. 래퍼와 루프 복구가 같은 잡을 처리해도 등록은 한 번이고, 후보가 모호하면 등록하지 않고 이유를 남긴다.
|
||
4. claude·grok 행과 이미 ID 가 있는 행은 바뀌지 않는다.
|
||
|
||
base(v4.9.8)에서 실패한다. `tests/test_issue64_ro_connect.py` 의 다른 작업 공간 agy 단언은 이제 거절(False)을 기대한다.
|
||
- **리뷰 (`--all-reviewer`, `--plan`, 구현은 claude creator)**: 1회차(@feccfd5) **4/4 PASS**. 리포트는 `.agents/reports/<session>/` 에 있다.
|
||
- **전체 스위트 (macOS, Python 3.9.6, @feccfd5)**: 2068 passed, 56 skipped, 3 failed.
|
||
- 실패 3개는 모두 `tests/test_issue42_skill_blocks.py` 였다. 이 변경이 옮긴 SKILL.md 블록과 `reconcile.sh` 근거 줄 번호가 목록에 남아 있었다.
|
||
- `tests/data/skill_code_blocks.json` 을 갱신한 뒤(@84af709) 이 파일은 37 passed, 1 skipped.
|
||
- 실제 `~/.claude`·`/private/tmp/claude-<uid>`·herdr 서버 pane 목록·opencode 의 data·state·config 에 이 실행이 남긴 것은 없었다.
|
||
- **알려진 비차단**:
|
||
- agy 등록은 `lsof`·`ps`, 행의 pane pid, agy 가 presence lock 이나 DB 를 열고 있음에 기댄다. `lsof` 가 없는 환경에서는 agy 가 자동 등록되지 않는다(오등록보다 미등록을 택함). completed 경로는 이유를 행에 남긴다.
|
||
- reconcile 은 `ambiguous` 만 이유를 남기고 `unverifiable-owner`·`owner-mismatch`·`rejected` 는 조용히 건너뛴다.
|
||
- id 가 빈 claude 행에서 `pre-assigned` 보호가 걸리는지 확인하는 테스트는 없다.
|
||
- 중복 마커는 실패 결과도 기록하므로, 래퍼 시점에 `no-candidate` 였던 잡은 루프 복구에서 다시 시도하지 않는다. 다음 잡의 completed 나 reconcile 이 다시 시도한다.
|
||
- 다른 행이 가진 ID 는 멈춘(stopped) 행의 것이어도 제외한다.
|
||
- stop 의 캡처 경로 정리는 범위 밖이다.
|
||
|
||
### 🚀 `v4.9.8` — agy·codex·hermes DB 조회도 읽기 전용 (Issue #64) (2026-10-20)
|
||
|
||
> **패치 (PATCH Release)**: v4.9.7 이 opencode 에 적용한 읽기 전용 열기(`_ro_connect`)를 agy·codex·hermes 의 조회에도 적용한다. macOS 의 시스템 SQLite 는 쓰기 가능으로 열었다 닫기만 해도 `-wal`·`-shm` 을 남긴다(persistent WAL). 그래서 MAM 의 조회가 다른 작업 공간의 agy 대화 DB 옆에 사이드카를 만들 수 있었다. Linux 는 닫을 때 지우므로 Linux 확인은 이슈에서 따로 한다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **원인**: `reconcile.sh` 의 drift C(agy)는 고정되지 않은 agy 행이 있으면 대화 폴더(호스트 공용)의 모든 `*.db` 를 후보로 보고 `verify_session_uuid(..., mode="discover")` 를 부른다. 다른 작업 공간의 대화도 같은 작업 공간의 형제가 가진 것이 아니면 걸러지지 않는다. 이어지는 `AgyAgentAdapter.verify_artifact` 가 `SELECT count(*) FROM steps` 를 쓰기 가능 연결로 실행했다. `status.sh`·`create_session.sh` 도 reconcile 을 부르므로 같은 경로를 탄다.
|
||
2. **공용 `_ro_connect`**: opencode 어댑터에 있던 함수를 `lib_py/agents/base.py` 로 옮겼다. 사이드카 상태에 따른 열기 방식(둘 다 있으면 `mode=ro`, `-wal` 이 없으면 `mode=ro&immutable=1`, `-shm` 없는 `-wal` 은 임시 사본을 메모리로)은 그대로다. opencode 는 같은 함수를 import 한다.
|
||
3. **바뀐 조회**:
|
||
- agy `verify_artifact`
|
||
- codex `_find_rollout_via_db`·`discover`
|
||
- hermes `verify_artifact`·`discover`
|
||
- `status.sh` 의 hermes·codex 확인(두 출력 모드 모두)
|
||
- `reconcile.sh` drift C 의 hermes·codex
|
||
- `db_rows_naming` 의 기본 열기(codex residual)
|
||
|
||
purge 의 의도된 쓰기(codex·hermes DELETE, opencode 옛 행 정리)와 MAM 자신의 DB 는 그대로다.
|
||
|
||
#### ✅ 품질 검증
|
||
- **회귀**: `tests/test_issue64_ro_connect.py`(43개). 네 가지 사이드카 상태(clean·leftover·walonly·writer)에서 조회 결과와 DB 폴더 목록을 확인한다. 범위는 세 어댑터, `db_rows_naming`, `status.sh`·`reconcile.sh` 실행, 다른 작업 공간의 agy 대화 재현이다. base(v4.9.7)에서 23개가 실패한다.
|
||
- **리뷰 (`--all-reviewer`, 구현은 agy creator)**: 1회차(@b0eccc5) **4/4 PASS**. 리포트는 `.agents/reports/<session>/` 에 있다.
|
||
- **전체 스위트 (macOS, Python 3.9.6, @b0eccc5)**: 2044 passed, 56 skipped, 실패 0. 실제 `~/.claude`·`/private/tmp/claude-<uid>`·opencode 의 data·state·config 폴더·herdr 서버 pane 목록에 이 실행이 남긴 것은 없었다. agy·codex·hermes 홈의 사이드카 목록도 그대로였다.
|
||
- **알려진 비차단**:
|
||
- 새 런타임 테스트의 reconcile 단계는 mock herdr 에 pane 이 없어 drift C 까지 가지 않는다. drift C 의 hermes·codex 는 정적 검사가 지킨다.
|
||
- drift C(agy)는 `last_conversations.json` 이 다른 작업 공간에 기록한 대화도 후보로 받는다(기존 동작). 새 테스트가 이 동작을 단언하므로 그 결함을 고칠 때 함께 바꾼다.
|
||
- `-wal` 없음을 판단한 직후 쓰는 쪽이 생기면 immutable 읽기가 틀릴 수 있다(#60 에서 남긴 창이 세 에이전트로 넓어짐, ms 수준). 실행 중인 에이전트의 DB 는 사이드카가 있어 `mode=ro` 로 읽는다.
|
||
- MAM 밖의 프로세스(다른 작업 공간의 agy 등)는 계속 자기 사이드카를 만들고 지운다. 홈 가드가 그것을 볼 수 있다.
|
||
|
||
### 🚀 `v4.9.7` — opencode 2.x 의 Linux 확인 후속: 안내 문구, 읽기 전용 조회, 인증 확인 (Issue #60, PR #62) (2026-10-19)
|
||
|
||
> **패치 (PATCH Release)**: v4.9.6 을 Linux(Ubuntu, Python 3.14, opencode 2.0.32, herdr 0.9.3)에서 확인했다. 경로·XDG·`--standalone` 자식의 수명·거부되는 플래그·`/exit`·status 는 Mac 과 같았다. 아래는 그 확인에서 찾은 결함으로, 특정 OS 에 한정되지 않는다. Issue #60 은 이 릴리스로 닫힌다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **2.x 홈 화면 안내 문구는 입력이 아니다**: 입력 칸의 `Ask anything… "<예시>"` 를 입력 글자로 읽어 `stall_watch.input_empty()` 가 빈 칸을 보지 못했다. 어댑터에 `input_placeholder_pattern`(입력 칸 첫 줄 전체 일치)을 두었다. opencode 패턴은 좁은 화면에서 잘린 예시도 받고, 따옴표로 시작하는 예시가 있어야 한다(사용자가 친 `Ask anything…` 는 입력이다). 다른 에이전트는 기존 `input_placeholder` 정확 일치 그대로다. 실제 2.x 캡처 fixture 2개(140열, 좁은 화면)를 추가했다.
|
||
2. **opencode DB 조회는 DB 옆에 아무것도 만들거나 지우지 않는다**: `_ro_connect` 가 사이드카 상태로 여는 방식을 고른다.
|
||
- `-wal`·`-shm` 둘 다(쓰는 쪽이 있음): `mode=ro`
|
||
- `-wal` 없음: `mode=ro&immutable=1`
|
||
- `-shm` 없는 `-wal`(남겨진 파일): 커밋된 행이 있으므로 임시 복사본을 메모리로 읽는다
|
||
|
||
어댑터의 verify·discover·`_has_session`·자손 조회·residual 과 `status.sh`·`reconcile.sh`(drift C, Mac 리뷰)가 이것을 쓴다. `db_rows_naming` 은 선택적 `connect` 를 받는다(opencode 만 넘긴다). purge 의 의도된 쓰기(`session delete`, 옛 행 정리)는 그대로다.
|
||
3. **`debug paths` 는 (바이너리, HOME, XDG_DATA_HOME) 마다 한 번**: 실행마다 opencode 로그에 한 줄을 남기므로 프로세스 안에서 결과를 기억한다. 실패한 결과는 기억하지 않는다.
|
||
4. **인증 확인은 바이너리**: 2.x 에는 `auth.json` 이 없고 새 HOME 은 무료 기본 모델로 돈다. opencode 의 `auth_ok` 는 PATH 의 `opencode` 또는 실행 가능한 `~/.opencode/bin/opencode` 이다(Mac 리뷰: 실행 권한 없는 파일·폴더는 아님). `create_session.sh` 는 새 `python -m lib_py.agents auth-ok <agent>` 로 어댑터에 묻고, 실제 로그에 기록하던 `opencode --version` 호출이 없어졌다.
|
||
5. **stop 문구**: `/exit` 전달이 확인되지 않을 때 "kill chain 으로 넘어간다" 대신 세션 종료를 기다린다고 출력한다. 종료 판단과 kill chain 은 그대로다.
|
||
6. **테스트**: `test_u1e` 가 `DELEGATE_JOB_PYTHON=sys.executable` 을 넘겨 PATH 의 python3 에 의존하지 않는다.
|
||
|
||
#### ✅ 품질 검증
|
||
- **회귀**: `tests/test_issue60_linux_fixes.py`. base(v4.9.6)에 새 테스트만 얹으면 46개 중 23개가 실패했다(작성자 측정). Mac 리뷰 뒤 macOS persistent WAL 상태, `Ask anything…` 입력, 실행 불가 설치, status·reconcile 읽기 전용 확인을 더했다.
|
||
- **Linux (작성자)**: 리뷰 2회차 4/4 PASS. 전체 2018 passed, 20 skipped, 12 failed(알려진 `test_issue10_13_purge.py` agy 실패, #28). 실제 opencode 데이터·상태·설정 폴더와 로그는 바뀌지 않았다.
|
||
- **리뷰 (`--review-only`, `--all-reviewer`, 이 Mac 정합성 중심)**:
|
||
- 1회차(@6a4e385): 4/4 NOT PASS. 공통 차단은 새 테스트의 `clean` 상태가 macOS 에서 항상 실패한 것이다(macOS SQLite 는 닫아도 `-wal`·`-shm` 을 남김; 제품 코드는 맞음). codex 는 실행 불가 설치를 인정하는 `auth_ok` 와 `Ask anything…` 입력을 지우는 패턴을, agy 는 `status.sh`·`reconcile.sh` 의 쓰기 가능 열기를 지적했다. 오케스트레이터가 6a37c00 에서 모두 고쳤다.
|
||
- 2회차(@6a37c00): **4/4 PASS**. 리포트는 `.agents/reports/<session>/` 에 있다.
|
||
- **전체 스위트 (macOS, Python 3.9.6, @6a37c00)**: 2001 passed, 56 skipped, 실패 0. 실제 `~/.claude`·`/private/tmp/claude-<uid>`·opencode 의 data·state·config 폴더·herdr 서버 pane 목록에 이 실행이 남긴 것은 없었고, 사용자의 opencode 서비스는 그대로였다.
|
||
- **알려진 비차단**:
|
||
- `-wal` 없음을 판단한 직후 쓰는 쪽이 생기면 immutable 읽기가 틀릴 수 있다(창은 ms 수준, 단건·LIMIT 20 조회).
|
||
- `debug paths` 결과는 프로세스 수명 동안 유지된다. 같은 경로의 바이너리를 경로 규칙이 바뀌는 버전으로 올리면 오래 도는 `reconcile --subscribe` 는 다시 시작해야 한다.
|
||
- create 의 opencode 사전 검사는 바이너리 존재뿐이다. 깨진 바이너리는 준비 대기 단계에서 드러난다.
|
||
- 2.x 대화 화면의 안내 문구는 캡처하지 않았다(메시지를 보내면 모델을 호출한다).
|
||
- agy(#28)·codex·hermes 는 아직 DB 를 쓰기 가능으로 연다. macOS 에서 열기만 해도 사이드카가 남을 수 있다.
|
||
|
||
### 🚀 `v4.9.6` — opencode 2.x 전용 지원 (Issue #60) (2026-10-18)
|
||
|
||
> **패치 (PATCH Release)**: opencode 가 1.x 에서 2.x 로 올라가면서 TUI 가 `--agent`·`-m` 을 거부하고(실행 즉시 종료), 세션 저장소가 `session_v2` 로 바뀌고, `opencode db path` 가 없어졌다. MAM 은 opencode **2.x 만** 지원한다(1.x 호환 경로 없음). 사실은 이 Mac 에서 opencode 2.0.32 를 임시 HOME·TMPDIR 로 실측해 확인했다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **실행 명령**: 생성·재개 모두 `opencode --auto --standalone`(재개는 `--session <id>` 추가)이다. 기본 실행은 사용자당 하나인 백그라운드 서비스(`opencode serve --service`, 고정 포트)에 붙는다. `--standalone` 은 세션마다 TUI 의 자식 서버(`serve --stdio`)를 띄우고, 그 서버는 TUI 가 끝나면(SIGKILL·pty 닫힘) 함께 끝난다. 모델은 실행 명령으로 고를 수 없고 opencode 설정을 따른다.
|
||
2. **세션 저장소 `session_v2`**: discover·verify·status·reconcile(drift C)이 `session_v2` 를 읽는다. 시각은 ms 단위이고, 자식 세션(`parent_id`)은 대화 세션으로 고르지 않는다.
|
||
3. **DB 경로**: `opencode debug paths` 의 `db` 줄로 찾는다. 실패하면 `XDG_DATA_HOME`(호출자 자신의 홈일 때만) 또는 `<home>/.local/share` 아래 `opencode/opencode.db` 를 쓴다. `status.sh` 도 어댑터의 경로를 쓴다.
|
||
4. **purge**: `opencode session delete --standalone -- <id>` 로 지운다. 이 명령은 자식 세션까지 2.x 테이블에서 지운다. 1.x 에서 이전된 DB 에 남는 옛 테이블(`session`·`message`·`part`·`todo`·`session_share`)의 행은 본인과 자손 세션 모두 MAM 이 지운다. 삭제가 실패하면 경고를 내고 남은 행을 residual 로 보고한다.
|
||
5. **사용 중 판정**: 백그라운드 서비스는 DB 를 늘 열어 두므로 DB 보유자 중 `serve --service` 는 사용 중으로 보지 않는다. 다른 보유자나 argv 에 uuid 를 가진 프로세스는 여전히 사용 중이다. `resident_children` 은 `opencode` 이다.
|
||
6. **문서**: `docs/OPENCODE_OLLAMA_GUIDE.md`(TUI 는 `-m`·`--agent` 를 받지 않음, MAM 은 `--standalone` 으로 실행), `docs/NEW_AGENT_INTEGRATION_GUIDE.md` 의 OpenCode 행.
|
||
|
||
#### ✅ 품질 검증
|
||
- **회귀**: `tests/test_issue60_opencode_v2.py`(2.x 스키마·CLI·자식 세션·이전된 DB·삭제 실패·서비스 보유자·XDG 규칙·실행 문자열), 가짜 2.x CLI `tests/_fake_opencode.py`. 설치된 opencode 2.x 가 있으면 임시 HOME 에서 실제 바이너리 계약(세션 생성·discover·purge·residual·없는 id)도 확인한다. 테스트 격리는 `XDG_DATA_HOME` 을 지우고, 홈 가드는 그 경로의 opencode 폴더도 감시한다.
|
||
- **리뷰 (`--review-only`, `--all-reviewer`, 이 Mac 정합성 중심)**:
|
||
- 1회차(@6b4b96f): claude·agy·grok PASS, codex NOT PASS(자식 세션의 1.x 행 잔존). 오케스트레이터가 수정했다(cb5f439: 자손 정리·residual, 서비스 보유자 제외, `XDG_DATA_HOME`, `resident_children`).
|
||
- 2회차(@cb5f439): grok PASS, claude·agy·codex NOT PASS. 공통 지적은 테스트 격리가 `XDG_DATA_HOME` 을 지우지 않아 sandbox 가 실제 data 폴더를 가리킬 수 있다는 것(차단)이다. 비차단으로 `status.sh` 의 고정 경로, 실제 바이너리 확인(`--version`)이 실제 HOME 의 opencode 로그에 기록하는 문제가 있었다. 모두 0e05b1b 에서 수정했다.
|
||
- 3회차(@0e05b1b): claude·agy·grok PASS. codex 는 사용 한도로 요청을 받지 못해 같은 head 를 단독으로 다시 리뷰해 PASS. **4/4 PASS**. 리포트는 `.agents/reports/<session>/` 에 있다.
|
||
- **전체 스위트 (macOS, Python 3.9.6, @0e05b1b)**: 1949 passed, 56 skipped(tomllib·서브모듈·Linux 전용). 실제 `~/.claude`·`/private/tmp/claude-<uid>`·opencode 의 data·state·config 폴더·opencode 로그·herdr 서버 pane 목록은 바뀌지 않았고, 사용자의 opencode 서비스와 실제 DB 의 세션은 그대로였다.
|
||
- **Linux 후속 확인(별도)**: 실제 2.x 화면 캡처로 입력 영역 fixture 갱신, `/exit` 정상 종료, 홈 화면 안내 문구의 입력 판정(stall_watch), `auth_ok`(`auth.json`, 현재 미사용), 실제 바이너리 계약 테스트.
|
||
|
||
### 🚀 `v4.9.5` — claude purge 가 세션이 소유한 경로를 모두 지우고 남은 것을 보고한다 (Issue #31, PR #59) (2026-10-18)
|
||
|
||
> **패치 (PATCH Release)**: claude `--purge-conversation` 이 `.jsonl` 만 지우고 `file-history`·`session-env` 등을 남기면서 `residual: none` 으로 보고하던 결함. purge 범위는 정규형 UUID 가 가리키는 세션 소유 경로로 한정된다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **소유 경로 하나의 목록**: claude 어댑터의 `_owned_paths` 를 purge·`has_artifacts`·residual 이 함께 쓴다. 대상은 다음과 같다.
|
||
- `projects/<key>/<uuid>.jsonl` 과 `projects/<key>/<uuid>/`
|
||
- `file-history/<uuid>/`, `session-env/<uuid>/`
|
||
- `state.json` 이 이 세션을 가리키는 `jobs/<uuid[:8]>/`
|
||
- 죽은 프로세스의 `sessions/<pid>.json`·`<pid>.<hex>.key`
|
||
- `<tmp>/claude-<uid>/<key>/<uuid>/` 스크래치패드(`CLAUDE_CODE_TMPDIR`·`TMPDIR`·`/tmp`)
|
||
|
||
공유 파일(`history.jsonl` 등)은 건드리지 않는다. 세션과의 연결 근거는 마지막에 지운다.
|
||
2. **삭제 범위 안전장치 (Mac 리뷰)**:
|
||
- 정규형 UUID 만 어떤 경로에든 닿는다. 그래서 `memory` 같은 id 가 `projects/<key>/memory/` 를 지우지 않는다. 대소문자 표기는 소문자로 정규화한다.
|
||
- 루트 아래 경로 구성요소가 심볼릭 링크면 그 아래는 지우지 않고 residual 로만 보고한다. 루트 자체의 링크(`/tmp → /private/tmp`)는 허용한다.
|
||
3. **residual 보고**: claude 에 `residual_artifacts` 를 신설했다. `stop_session.sh` 는 purge 뒤 residual 을 출력한다. codex·grok·opencode 도 residual 을 보고한다(purge 범위는 그대로).
|
||
4. **살아 있는 프로세스 대기**:
|
||
- 새 CLI `python -m lib_py.agents in-use <agent> <uuid> [--wait N]` 를 추가했다. 판정은 `sessions/<pid>.json` 과 `ps -o lstart=`(`LC_ALL=C`, `TZ=UTC0`)의 시작 시각 비교이며, pid 재사용을 구분한다. `startedAt` 이 유한한 시각이 아니거나 판정할 수 없으면 live 로 본다.
|
||
- `stop --purge-conversation` 은 대화가 아직 쓰이고 있으면 exit 4 로 아무것도 바꾸지 않고, 보유자가 있을 만한 곳을 안내한다.
|
||
5. **`CLAUDE_CONFIG_DIR`**: purge·liveness·residual·projects 루트가 이 값을 따른다. 명시적 `CLAUDE_PROJECT_DIR` 가 우선한다. stop `SKILL.md` 는 purge 가 레지스트리 행을 지운다고 정정했다.
|
||
6. **eager 로드**: `lib_py.delegation.logroot` 를 `eager.CORE_MODULES` 에 명시했다(v4.9.4 리뷰의 비차단 메모).
|
||
|
||
#### ✅ 품질 검증
|
||
- **회귀**: `tests/test_issue31_purge_residual.py`(67 tests). 리뷰어들이 수정 직전 커밋이나 변형 사본에서 실패를 확인했다. 대상은 비-UUID id, 링크 경계, 유한하지 않은 `startedAt`, UTC lstart, 철자 정규화이다.
|
||
- **리뷰 (`--review-only`, `--all-reviewer`, 이 Mac 정합성 중심)**:
|
||
- 1회차(@6f4f40a): agy·grok PASS, claude·codex NOT PASS. 지적은 비-UUID `memory` 가 자동 메모리 폴더를 지움(차단), 상위 링크를 따라 범위 밖 삭제(P1), NaN `startedAt`(P2)이다. PR 댓글로 전달했고 작성자가 커밋 11개로 수정했다.
|
||
- 2회차(@2a8d738): 4/4 가 세 지적 모두 해결을 확인했다. 그러나 새 테스트의 보유자 `sh -c "sleep 120" <uuid>` 가 macOS `/bin/sh`(bash 3.2)의 단일 명령 exec 로 uuid 를 잃어 `[upper|mixed]` 가 이 Mac 에서 항상 실패해 NOT PASS 였다. 오케스트레이터가 이 한 줄을 Python 보유자로 바꿨다(de6f3fc).
|
||
- 3회차(@de6f3fc): **4/4 PASS**. 리포트는 `.agents/reports/<session>/` 에 있다.
|
||
- **전체 스위트 (macOS, Python 3.9.6, @de6f3fc)**: 1934 passed, 56 skipped(tomllib·서브모듈·Linux 전용), 1 error. error 는 #55 가드가 다른 워크스페이스에서 돌던 agy 의 대화 파일을 잡은 알려진 오탐이다. 실제 `~/.claude`(projects·file-history·session-env·sessions·jobs)·`/private/tmp/claude-<uid>`·herdr 서버 pane 목록은 바뀌지 않았다.
|
||
- **알려진 비차단**:
|
||
- `projects/<key>` 자체가 링크인 경우, transcript 는 purge 되지 않고 residual 로만 보고된다.
|
||
- 소문자 질의는 대문자 argv 보유자를 보지 못한다.
|
||
- `purge_artifacts` 를 직접 호출하면 in-use 게이트가 없다(CLI·stop 은 먼저 확인한다).
|
||
|
||
### 🚀 `v4.9.4` — 감사 로그가 옮긴 레지스트리를 따라간다 (Issue #57, PR #58) (2026-10-15)
|
||
|
||
> **패치 (PATCH Release)**: `--registry-dir`·`MAM_JOBS_DIR` 로 레지스트리만 옮긴 실행이 실제 `.mam/delegate_job_logs` 에 레지스트리 없는 감사 로그를 남기던 결함. 레지스트리를 옮기지 않은 평소 실행의 경로는 바뀌지 않는다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **형제 규칙 하나**: `DELEGATE_JOB_LOGS_DIR` 가 없으면 감사 로그 루트는 사용 중인 레지스트리(realpath)의 형제 `delegate_job_logs` 다. 규칙은 stdlib 전용 `lib_py/delegation/logroot.py` 한 곳에 있고(`mqtt_common.get_logs_dir(registry_dir)` 가 재노출), 감사 로그를 쓰고 읽는 모든 지점(register·pick·상태 미러·충돌 거부·publish_event·subscriber·remote·reconcile·루프 러너)이 레지스트리를 넘긴다.
|
||
2. **bash 기본값 제거**: 래퍼의 `${DELEGATE_JOB_LOGS_DIR:-$WORKDIR/delegate_job_logs}` 16 곳과 `lib.sh` 사본을 없앴다. 래퍼는 루트를 한 번 해석해 자식에게 export 하고(해석 실패 시 fail-closed), `logs` 는 `--registry-dir` 를 받았을 때만 넘긴다.
|
||
3. **세션 바인딩**: `control_plane.from_env()` 는 로그 루트를 `mam_jobs_dir` 에서 파생하고, `forwarded_keys()` 는 파생 가능한 형제 루트를 행에만 기록하고 pane 에 넘기지 않는다. `worktree_prepare.sh` 는 호출자가 준 경우만 넘긴다. `status.sh` 는 레지스트리 옆 감사 로그도 조회한다.
|
||
4. **디스크 폴백**: `job_subscriber._check_disk_fallback` 이 `status.json` 의 `status` 를 읽고(이전엔 dict 를 비교해 아무것도 맞지 않음), 버전 있는 레코드는 감사 `transition_seq` 가 같거나 새로울 때만, 그리고 채택 직전에 레코드를 다시 읽어 같은 dispatch 일 때만 감사 상태를 받아들인다.
|
||
5. **결정**: `submit --dry-run` 의 의미(등록 + 감사 로그 시드)는 유지, 쓰기 시 레지스트리 레코드 존재 가드는 두지 않는다(#20 가드가 실제 루트의 레지스트리 없는 감사 로그를 잡는다).
|
||
|
||
#### ✅ 품질 검증
|
||
- **회귀**: 새 `tests/test_issue57_logs_follow_registry.py`(19 tests)는 명시적 `DELEGATE_JOB_LOGS_DIR` 우선 불변식 하나를 빼고 수정 전 트리에서 실패하며, 모든 테스트가 워크스페이스의 `.mam/delegate_job_logs` 가 생기지 않았음을 단언한다. 기존 테스트 5개의 단언을 새 규칙에 맞췄다(PR #58 본문).
|
||
- **리뷰 (`--review-only`, `--all-reviewer`, 이 Mac 정합성 중심)**: 1회차(@63d8164) agy·grok PASS, claude·codex NOT PASS — paho 없는 `/usr/bin/python3` 폴백에서 바인딩·status 실패(F1), `logs` 기본 경로 변경(F2), 디스크 폴백의 재디스패치 interleaving(R1), 심볼릭 링크 레지스트리(R2). PR 댓글로 전달했고 작성자가 커밋 4개로 수정했다. 2회차(@a7cb90e) **4/4 PASS**(1회차 재현이 모두 해소). 리포트는 `.agents/reports/<session>/`.
|
||
- **전체 스위트 (macOS, Python 3.9.6, @a7cb90e)**: 1867 passed, 56 skipped(tomllib·서브모듈·Linux 전용), 1 error — #55 가드가 다른 워크스페이스에서 돌던 agy 의 대화 WAL/SHM 을 잡은 알려진 오탐. 실제 herdr 서버 pane 목록·`~/.claude/projects` 불변, 실제 감사 로그에는 리뷰 잡 것만 생겼다.
|
||
|
||
### 🚀 `v4.9.3` — u1e 테스트가 체크아웃의 `.mam/shim/herdr` 를 쓰지 않는다 (2026-10-13)
|
||
|
||
> **패치 (PATCH Release, 테스트 전용)**: v4.9.2 README 리뷰(claude 38a64423)가 재현한 결함. 제품 코드는 버전 줄 외에 바뀌지 않았다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **원인**: `tests/test_issue50_dispatch.py::test_u1e_a_dry_run_record_is_not_an_open_job`(PR #56)이 delegate-job 래퍼를 `WORKSPACE_ROOT` 없이 실행했다. 래퍼가 source 하는 `lib.sh` 는 그 값을 체크아웃으로 기본값 처리하고 `<checkout>/.mam/shim/herdr` 를 다시 쓴다(Issue #17 후속과 같은 유형).
|
||
2. **수정**: 테스트가 래퍼에 자기 `tmp_path` 를 `WORKSPACE_ROOT` 로 준다. 이 노드를 W1(`tests/test_issue17_workspace_root_isolation.py`, 노드들을 체크아웃 복사본에서 돌리고 `.mam` 이 생기면 실패)의 목록에 넣었다.
|
||
|
||
#### ✅ 품질 검증
|
||
- **회귀**: 수정 전 트리에서 W1 이 `['.mam/shim', '.mam/shim/herdr']` 로 실패하고 수정 후 통과한다(리뷰어 4명이 각자 재현). 깨끗한 워크트리에서 #50·#55·누수 모듈 7개(112 tests)를 돌린 뒤 체크아웃에 `.mam` 이 없었다(수정 전에는 u1e 의 shim 하나).
|
||
- **리뷰 (`--review-only`, `--all-reviewer`)**: 1회차(@3a30d7d) **4/4 PASS**. claude 는 25 개 파일을 파일별로 돌려 다른 누출이 없음을 확인했다. 리포트는 `.agents/reports/<session>/`.
|
||
|
||
### 🚀 `v4.9.2` — README 를 현재 코드에 맞춰 다시 쓴다 (2026-10-13)
|
||
|
||
> **패치 (PATCH Release, 문서 전용)**: 코드 변경 없음. `README.md` 를 v4.9.1 의 코드와 대조해 다시 쓰고 `README.ko.md` 를 같은 내용으로 맞췄다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **낡은 설명 정정**: shim 은 `/tmp/multi-agent-herdr-shim/<server>` + `herdr -L` 이 아니라 `<workspace>/.mam/shim/herdr` + `--session`; 레지스트리 쓰기는 시스템 `python3` 이 아니라 `AGENT_PYTHON_BIN` → 가상환경 → 가장 가까운 `.venv` → `PATH` 의 `python3` 순; `install.sh` 의 실제 동작(검사 항목, `MQTT_BROKER=localhost` 초기 `.mam.env`, 기존 파일 보존, `.env` 복사); 저장소 구조와 Quick Start.
|
||
2. **빠졌던 내용 추가**: 위임 계약(전달 T·heartbeat·K×N·예산·바쁜 세션 대기·원격 전송·종료 코드 표), 리뷰 루프(선택한 리뷰어 전원 PASS, 옵션이 없으면 creator self-review, 엄격한 판정, 리뷰 identity, 하위 잡), `create --onboard` 44/45, 서버별 pane claim, 워크스페이스 이름의 기본 서버와 조건부 자동 기동, restore-off 와 그 폴백, 보안 조건(토큰은 넘기거나 TLS/username 일 때만; 원격 디스패치에는 토큰이 실림), 테스트 격리.
|
||
|
||
#### ✅ 품질 검증
|
||
- **리뷰 (`--review-only`, `--all-reviewer`, 사실 대조)**: 1회차(@20b1014) agy PASS, claude·grok·codex NOT PASS(설치 동작, 보안 조건, 서버 격리, 리뷰어 선택, 인터프리터 등); 2회차(@423abc7, `README.ko.md` 포함) claude·agy PASS, grok·codex NOT PASS(종료 코드 5 의 superseded, `.env` 복사, restore 폴백); 3회차(@ae28137) grok NOT PASS(샌드박스 픽스처 비율, generate-env); 4회차(@62da805) **4/4 PASS**. 리포트는 `.agents/reports/<session>/`.
|
||
- **리뷰 중 발견(별도 수정)**: `tests/test_issue50_dispatch.py::test_u1e_a…` 가 `WORKSPACE_ROOT` 없이 래퍼를 실행해 체크아웃의 `.mam/shim/herdr` 를 쓴다(claude 재현). 다음 릴리스에서 고친다.
|
||
|
||
### 🚀 `v4.9.1` — 테스트가 실제 herdr 서버에 세션을 만들지 않는다 (2026-10-13)
|
||
|
||
> **패치 (PATCH Release)**: v4.9.0 병합 검증 중 에이전트 pane 에서 `--basetemp .../pytest-pr56-shim` 으로 돌린 스위트가 그 pane 의 실제 서버(`multi-agent-mux`)에 워크스페이스 7개를 남겼다(정리 완료). 제품 코드 변경은 실제 herdr 해석기의 `-shim` 규칙 한 가지.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **원인 1 — 조상 `*-shim` 이 mock 을 가렸다**: 실제 herdr 해석기(`lib.sh` 의 `_is_shim_path`, heredoc 으로 생성되는 `.mam/shim/herdr`)가 경로 어디에든 `-shim` 이 든 PATH 디렉터리를 건너뛰었다. basetemp 이름이 mock 의 `bin/` 조상이라 mock 이 건너뛰어지고 실제 herdr 가 실행됐다. **수정**: 디렉터리 자신의 이름이 `*-shim` 일 때만 래퍼로 본다(`.mam/shim`, `multi-agent-herdr-shim`, `.agents/skills/.bin` 규칙은 그대로). 생성 shim 이 바뀌었으므로 `test_s11` 의 기준 커밋을 525df2b 로 옮겼다(#45 와 같은 방식).
|
||
2. **원인 2 — 샌드박스가 pane 의 소켓을 남겼다**: 실제 herdr 는 sandbox `HOME` 보다 `HERDR_SOCKET_PATH` 를 따른다(실측). `mam_sandbox` 가 `HERDR_SOCKET_PATH`·`HERDR_PANE_ID`·`HERDR_TAB_ID` 와 `HERDR_SESSION`(호출자 서버 판정이 읽어 pane 안/밖에서 분기가 달라짐, 리뷰 비차단 메모)을 지운다.
|
||
|
||
#### ✅ 품질 검증
|
||
- **회귀 테스트**: `tests/test_herdr_real_server_leak.py` — L1(조상 `*-shim`, 두 해석기; 실제 herdr 대신 디코이)·L2(pane 라우팅 변수 제거)는 수정 전 실패, L1b(자기 이름이 `*-shim` 인 래퍼는 계속 제외).
|
||
- **리뷰 (`--review-only`, `--all-reviewer`)**: 1회차(@325b056) **4/4 PASS**, 2회차(@db7f4ca, `HERDR_SESSION`) **4/4 PASS**. 비차단으로 남긴 기존 차이: 생성 shim 은 `.agents/skills/.bin` 을 제외하지 않음(codex), README 의 레거시 `/tmp/multi-agent-herdr-shim/<server>` 레이아웃 설명(claude). 리포트는 `.agents/reports/<session>/`.
|
||
- **전체 스위트 (macOS·Python 3.9·bash 3.2·herdr 0.9.3, 에이전트 pane 환경 그대로, MQTT 환경변수 없이)**: 325b056 **1845 passed / 0 failed / 56 skipped**(74 분; 새 워크트리라 `nats-docker` 서브모듈이 없어 `test_deploy_freshness` 18건이 추가로 건너뛰어졌고, 병합 뒤 main 에서 따로 실행). `~/.claude/projects` 에 새 항목 없음. 실행 전후 실제 서버(`default`, `multi-agent-mux`)의 pane 목록이 같았다.
|
||
|
||
### 🚀 `v4.9.0` — Issue #50·#55: 온보딩·위임이 레지스트리로 기다리고, pane 배치는 서버마다 하나씩, 테스트는 실제 에이전트 홈에 쓰지 않는다 (2026-10-12)
|
||
|
||
> **마이너 (MINOR Release)**: 새 종료 코드(`create --onboard` 의 44·45)와 새 대기 동작(위임 래퍼의 레지스트리 대기, pane 배치 잠금). PR #56.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **#55 테스트 격리**: lib.sh 가 에이전트 pane 에 내보내는 `CLAUDE_PROJECT_DIR` 등을 테스트마다 지우는 autouse 픽스처(`tests/conftest.py`)와, 실행 중 실제 에이전트 홈에 새 항목이 생기면 세션을 실패시키는 읽기 전용 가드(`tests/_agent_home_guard.py`). 운영용 lib.sh export 는 그대로다.
|
||
2. **#50 A — `create --onboard` 가 온보딩 잡의 종결을 기다린다**(`worktree_prepare.sh --onboard` 도): 상한 `MAM_ONBOARD_WAIT_S`(기본 600 s). 새 종료 코드 **44**(온보딩이 error/cancelled 로 끝남)·**45**(시간 안에 끝나지 않음); 둘 다 세션을 남기고 행에 `last_visible_status: onboarding-<status>` 를 쓴다.
|
||
3. **#50 C — 위임 래퍼가 주입 전에 레지스트리로 기다린다**: 대상 세션의 앞선 열린 잡(온보딩 포함)이 끝날 때까지 `MAM_DISPATCH_WAIT_S` 만큼; 끝나지 않으면 전처럼 `cancelled`(session_busy), 종료 코드 5. 세션당 한 번에 한 잡만 승인(레지스트리 잠금 아래 기록), herdr idle 대기는 보조 확인으로만 남는다. `submit --dry-run` 레코드는 주입되지 않으므로 pending 인 동안 세션을 잡지 않는다(pick-pending 이 가져가면 잡는다).
|
||
4. **같은 폴더 동시 create**: pane 배치를 herdr 서버마다 `lib_py/pane_lock.py`(fd 9 의 `fcntl.flock`, macOS 에는 flock(1) 이 없음)로 직렬화한다. 대기 상한 `MAM_PANE_CLAIM_WAIT_S`, 보유자가 죽으면 풀린다. create 는 monitor 가 자기 서버에 등록해 둔 행만 이어받고, monitor 는 `default` 조회에 실제로 답한 서버(상속된 `HERDR_SESSION_NAME`)를 행에 적는다.
|
||
|
||
#### ✅ 품질 검증
|
||
- **PR 작성자 검증 (Linux·Python 3.14·herdr 0.9.3)**: 320aa9f 전체 스위트 1864 passed / 12 failed(#28 의 알려진 `test_issue10_13_purge.py` 기준선) / 20 skipped, 같은 폴더 동시 `create --onboard` 3 개 실제 시험 모두 exit 0. 리뷰 루프 3 개, 320aa9f 에서 만장일치 PASS.
|
||
- **병합 전 macOS 검증 (macOS·Python 3.9·bash 3.2·herdr 0.9.3)**: 320aa9f 에서 macOS 전용 실패를 고침(a9ffeda: AF_UNIX 경로 104 바이트, tty canonical 1024 바이트, bash 3.2 의 마지막 명령 미-exec, `_adopted_own` 의 서버 확인, 경합만 재시도하는 flock, dry-run 레코드). 리뷰 1회차 4명 모두 NOT PASS(macOS 실패 수정·dry-run·서버 확인·flock 범위), 2회차 codex F1(가져간 dry-run 잡은 세션을 잡는다, efd27de), 3회차 4/4 PASS(리뷰 중 워크트리 수정으로 루프가 폐기), @21f0c12(monitor 서버 표기) **4/4 PASS**: 4회차에서 claude·agy·grok PASS, codex 는 스트림 끊김으로 판정 없이 중단되어 5회차 단독 재리뷰에서 PASS. 리포트는 `.agents/reports/<session>/`(루프가 승격한 codex 리포트만 identity 사이드카가 있고, 나머지 셋은 4회차 잡의 report-final 을 복사).
|
||
- **전체 스위트 (21f0c12, MQTT 환경변수 없이, `CLAUDE_PROJECT_DIR` 설정)**: **1860 passed / 0 failed / 38 skipped**(75 분). 실행 뒤 `~/.claude/projects` 에 새 항목 없음.
|
||
|
||
### 🚀 `v4.8.9` — 테스트가 체크아웃의 실제 루프 잠금을 잡지 않는다 (2026-10-09)
|
||
|
||
> **패치 (PATCH Release, 테스트 전용)**: v4.8.8 센티널 실행 뒤 리뷰 워크트리에 빈 `.mam/` 이 남은 원인. 제품 코드(`.agents/skills`)는 버전 줄 외에 바뀌지 않았다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **원인**: `tests/test_o3_scoped_guard.py` 의 `test_b13_reexec_preserves_original_argv`·`test_b13_no_freeze_switch_disables_reexec` 는 체크아웃의 `run_loop.sh` 를 `--creator` 와 함께 실행해 인자 해석을 지나 `mam_acquire_loop_lock` 까지 간다. 잠금 위치 기본값은 `$MAM_REAL_ROOT/.mam/loop-guard-active` 이고 `MAM_REAL_ROOT` 는 스크립트 자신의 체크아웃이라 `WORKSPACE_ROOT` 를 따르지 않는다. 세션이 없어 곧 끝나며 marker 는 지우지만 `.mam/` 은 남는다. 본 체크아웃에서는 그 순간 실제 루프의 잠금을 잡았다 놓는다(실제 루프가 거부되거나 테스트가 거부됨).
|
||
2. **수정**: 두 테스트가 `MAM_LOOP_MARKER` 를 자기 `tmp_path` 아래로 둔다. `run_loop.sh` 를 쓰는 16 개 모듈을 테스트마다 체크아웃의 새 `.mam` 을 감시하며 훑었고, 나머지는 이미 marker 를 지정하거나 잠금 전에 끝나거나(`--help`, 인자 오류) 복사본 트리를 실행한다. 잠금 동작을 시험하는 테스트(`test_o2_race_free_lock` 등)는 그대로다.
|
||
|
||
#### ✅ 품질 검증
|
||
- **회귀 테스트**: `tests/test_loop_marker_isolation.py` L1 — 체크아웃의 `run_loop.sh` 로 잠금까지 가는 테스트들을 체크아웃 복사본에서 돌려 `.mam` 이 생기지 않아야 한다. v4.8.8 의 W1(`tests/test_issue17_workspace_root_isolation.py`)은 파일만 보아 빈 잠금 디렉터리를 놓쳤으므로 이제 `.mam` 자체가 없어야 한다. 97c2946 에서 둘 다 실패.
|
||
- **리뷰 (병합 워크스페이스, 루프 `--all-reviewer`)**: 1회차(@a93c9bf) **4/4 PASS**. 리포트와 identity 사이드카는 `.agents/reports/<session>/`.
|
||
- **전체 스위트 (병합 워크스페이스, macOS·Python 3.9·herdr 0.9.3, MQTT 환경변수 없이, `WORKSPACE_ROOT` = 빈 센티널)**: a93c9bf **1752 passed / 0 failed / 38 skipped**(73 분). 실행 뒤 센티널은 비었고, 리뷰 워크트리에 `.mam/` 이 없었고, 본 체크아웃의 `.mam/shim/herdr` 수정 시각은 그대로였다.
|
||
|
||
### 🚀 `v4.8.8` — Issue #17 후속: lib.sh 를 source 하는 테스트가 상속된 `WORKSPACE_ROOT` 에 쓰지 않는다 (2026-10-09)
|
||
|
||
> **패치 (PATCH Release, 테스트 전용)**: v4.8.7 리뷰(claude 비차단)가 남긴 후속. 제품 코드(`.agents/skills`)는 바뀌지 않았다. lib.sh 는 source 될 때마다 `$WORKSPACE_ROOT/.mam/shim/herdr` 를 다시 쓰는데(`_init_herdr_isolation`), 셸 환경을 그대로 물려받는 테스트는 그 값이 가리키는 실제 워크스페이스(에이전트 pane 에서는 그 pane 의 워크스페이스, PR #51 리뷰 때는 다른 워크스페이스)의 shim 을 덮어썼다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **item-4 헬퍼 (`tests/test_issue17_item4_decisions.py`)**: `_env` 가 `WORKSPACE_ROOT` 를 테스트의 임시 디렉터리로 지정한다. 지우기만 하면 lib.sh 가 체크아웃 자신을 루트로 계산하고, conftest 가 이 체크아웃을 가리키는 값을 남겨 두므로 체크아웃의 shim(본 체크아웃이면 실제 shim)을 쓰게 된다.
|
||
2. **공통 fixture (`tests/conftest.py`)**: `own_workspace_root` — `WORKSPACE_ROOT` 를 `<tmp_path>/.mam-test-state/workspace` 로 둔다. autouse 가 아니다(runner.py·mqtt_common 은 프로세스 안에서 그 값을 읽고, 일부 테스트는 일부러 지운다). `mam_sandbox` 와 명시 env 가 뒤에서 이긴다.
|
||
3. **같은 결함의 모듈들**: `test_version_consistency`, `test_b19_headless_reconcile_fixes`, `test_b8_send_keys_verification`, `test_issue6_fail_closed`, `test_loop_cli`, `test_issue5_phase3_loop`, `test_issue5_phase4_runtime`, `test_o3_scoped_guard`, `test_issue25_plan_reuse`, `test_orc_onboard`(import 시점의 `SCRUBBED_ENV` 가 더는 `WORKSPACE_ROOT` 를 담지 않고 호출마다 그 호출의 루트로 env 를 만든다), `test_o1_rebuttal`, `test_o2_race_free_lock` 은 fixture 를 쓰고, `test_workspace_scope`·`test_issue21_worktree_prereqs._mutate` 는 이미 만든 루트를 고정한다. lib.sh·run_loop.sh·래퍼·스킬 스크립트를 실행하는 나머지 테스트 파일은 표시되었거나 `mam_sandbox`/고정 루트를 쓰거나 lib.sh 를 source 하지 않는다.
|
||
|
||
#### ✅ 품질 검증
|
||
- **회귀 테스트 (`tests/test_issue17_workspace_root_isolation.py` 외)**: 센티널 `WORKSPACE_ROOT` 아래 item-4 헬퍼가 아무것도 만들지 않음; W1 — 체크아웃 복사본에서 위 모듈마다 한 테스트를 그 복사본을 가리키는 `WORKSPACE_ROOT` 로 돌려 복사본에 `.mam` 이 생기지 않음; W2 — `test_orc_onboard` 를 센티널 루트로 import 한 뒤 다른 루트로 호출해도 센티널이 빔. 수정 전 커밋에서는 모두 실패(`.mam/shim/herdr` 생성).
|
||
- **리뷰 (병합 워크스페이스, 루프 `--all-reviewer`)**: 1회차(@d198dd5) claude·agy PASS, grok·codex NOT PASS(같은 결함이 다른 모듈 11 곳에 남음, `test_orc_onboard` 의 import 시점 env). 8b47cc7 에서 수정. 2회차 agy·codex PASS, claude·grok NOT PASS(`test_o2_race_free_lock`·`test_o1_rebuttal` 누락). ac48cb7 에서 수정. 3회차(@ac48cb7) **4/4 PASS**. 리포트와 3회차 identity 사이드카는 `.agents/reports/<session>/`.
|
||
- **전체 스위트 (병합 워크스페이스, macOS·Python 3.9·herdr 0.9.3, MQTT 환경변수 없이, `WORKSPACE_ROOT` = 빈 센티널 디렉터리)**: ac48cb7 **1751 passed / 0 failed / 38 skipped**(73 분). 실행 뒤 센티널은 비었고, 본 체크아웃의 `.mam/shim/herdr` 수정 시각은 그대로였다. skip 38 건은 v4.8.7 과 같다.
|
||
- **codex 리뷰어 환경**: 1·2회차 동안 codex 세션은 공유 app-server 데몬 재시작으로 샌드박스 우회가 풀려(workspace-write, 네트워크 제한) 로컬 브로커 테스트와 MQTT 발행을 하지 못했고, 브로커 없는 실행과 정적 검토로 판정했다. 루프 뒤 `codex resume --no-daemon --dangerously-bypass-approvals-and-sandbox` 로 재기동해 루프백 bind 와 `danger-full-access` 를 확인했다.
|
||
|
||
#### 📝 후속
|
||
- codex 어댑터의 resume 명령에 `--no-daemon` 이 없다: 공유 데몬을 쓰면 재기동이 "Cannot use the shared background server … rerun with --no-daemon" 으로 실패하고, 데몬이 재시작되면 샌드박스 우회가 조용히 풀린다.
|
||
- 센티널 실행 뒤 리뷰 워크트리에 빈 `.mam/` 디렉터리가 생겼다(ignore 대상, shim 없음). 어느 테스트가 만드는지는 확인하지 않았다.
|
||
|
||
### 🚀 `v4.8.7` — Issue #17 후속: 래퍼가 판정을 못 내리면 1 로 끝나고, resume 이 herdr 0.9.3 의 자리표시 pane 을 닫는다 (2026-10-08)
|
||
|
||
> **패치 (PATCH Release)**: v4.8.6(PR #51) 리뷰가 남긴 후속. 판정 모듈이 실패할 때의 래퍼 종료 코드, herdr 0.9.3 에서 드러난 #45 B 의 빈틈, 문서 두 곳. 정상 경로의 종료 코드·출력·레지스트리·이벤트는 같다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **판정 실패는 1 (`multi-agent-mux-delegate-job`, `lib_py/delegation/wrapper.py`)**: `exit "$(_decide exit-code …)"` 는 판정 모듈이나 `$PY` 가 깨지면 `exit ""` → bash 3.2 에서 255 였고, 다른 판정도 저마다 다르게 실패했다(wait-rc 255, finalize-begin 실패 시 finalize-end 의 argparse 2, set -e 아래 인터프리터 자신의 코드 1·127). 이제 값을 내는 판정은 `_decided`(0 이 아닌 rc·빈 답이면 실패), 종료는 `_exit_for`(숫자가 아니면 실패)를 거치고, 실패는 `_decision_failed` 가 `ERROR:` 한 줄과 함께 1 로 끝낸다(fail-closed).
|
||
2. **예/아니오 판정 (리뷰 codex R1)**: `subscriber-up`·`stall-watch` 는 rc 로만 답해, 실행조차 못 한 호출(import 오류 rc 1, 인터프리터 없음 127)이 "아니오"로 읽혔다 — 0 으로 끝난 구독자에서 제출이 0(completed)으로 끝났고, stall-watch 실패는 감시를 조용히 껐다. 두 서브커맨드는 `--print` 로 `yes`/`no` 를 출력하고(rc 0), 래퍼는 `_decided_yes` 로 묻는다. 그 밖의 답·실패는 1 로 끝난다. `--print` 없는 rc 계약(예 0, 아니오 1)은 그대로.
|
||
3. **herdr 0.9.3 의 자리표시 pane (`lib_py/herdr_restore.py`)**: herdr 0.9.3 은 저장된 디렉터리가 사라진 pane 을 `$HOME` 의 셸이 아니라 원래 cwd 의 자리표시(`restore_error` "Saved directory is unavailable…", 프로세스 없음)로 복원하고, 그 pane 의 `pane process-info` 는 `pane_not_found` 로 답한다. `close_restored_shells` 는 여기서 예외로 exit 1 하며 다른 pane 을 보지 않아, #45 B 가 닫아야 할 지워진 워크트리의 pane 이 남았다. `restore_error` 가 있고 **그리고** `pane_not_found` 인 경우만 "실행 중인 것 없음"으로 보고 닫는다. 다른 process-info 실패는 전처럼 exit 1. docstring 도 버전별 복원 위치로 정정.
|
||
4. **테스트 전제 (`tests/test_issue45_restore.py`)**: 지워진 cwd 의 복원 위치를 herdr 0.9.1(`$HOME`)·0.9.3(원래 경로) 둘 다 받는다.
|
||
5. **문서**: `wrapper.py` 머리말 — 모듈은 stdlib 만 import 하지만 `python -m lib_py.delegation.wrapper` 는 패키지 `__init__` 을 거쳐 paho 를 싣는다(N1). `lib_int.sh` — 단독 source 될 때 PATH 의 `python3` 를 쓴다(N3).
|
||
|
||
#### ✅ 품질 검증
|
||
- **새 테스트**: 판정 실패(traceback·rc 127·빈 답) × 판정(exit-code, sub-status, finalize-begin·-end, wait-rc, subscriber-up 직접·loop/discuss, stall-watch)이 1 과 `ERROR:` 줄로 끝남 — 수정 전 코드에서 255 또는 0 으로 실패. 예/아니오 두 분기 보존, `--print` 단위표, rc 로 읽는 예/아니오 판정을 금지하는 정적 가드. herdr stub 으로 자리표시 닫기(56d0bfd 에서 실패)와, `restore_error` 없는 `pane_not_found`·다른 오류의 `restore_error` 는 여전히 예외.
|
||
- **리뷰 (병합 워크스페이스, 루프 `--all-reviewer`)**: 1회차(@8241bd5) claude·grok PASS, agy NOT PASS(래퍼 문자열을 고정한 기존 정적 테스트 2 건이 `_decided`/`_exit_for` 로 바뀐 호출을 못 따라감), codex NOT PASS(R1 위 2). 541c3fe·65b78b6 에서 수정. 2회차(@65b78b6) **4/4 PASS**. 리포트와 2회차 identity 사이드카는 `.agents/reports/<session>/`.
|
||
- **전체 스위트 (병합 워크스페이스, macOS·Python 3.9·herdr 0.9.3, MQTT 환경변수 없이)**: 65b78b6 **1730 passed / 0 failed / 56 skipped**(72 분). skip 중 18 건은 새 워크트리에 nats-docker 서브모듈이 없어서였고, 서브모듈을 받은 뒤 `tests/test_deploy_freshness.py` **31 passed**. 나머지 38 skip 은 v4.8.6 과 같다(tomllib 전용 34 등). v4.8.6 의 herdr 0.9.3 실패(`test_issue45_restore`)는 이번에 통과.
|
||
|
||
#### 📝 후속
|
||
- `tests/test_issue17_item4_decisions.py` 의 `_env` 가 상속된 `WORKSPACE_ROOT` 를 비우지 않아, 옛/새 bash 비교 헬퍼가 에이전트 pane 의 워크스페이스로 lib.sh 를 초기화할 수 있다(claude 비차단).
|
||
|
||
### 🚀 `v4.8.6` — Issue #17 항목 4: delegate-job 래퍼의 판정이 Python 으로 옮겨진다 (2026-10-08)
|
||
|
||
> **패치 (PATCH Release)**: 동작 변화 없는 리팩터링. 래퍼(bash)의 판정 로직을 `lib_py/delegation/wrapper.py` 로 옮기고, #26 정수 규칙의 두 구현을 `lib_py/ints.py` 하나로 합쳤다. 종료 코드·라벨·레지스트리 기록·이벤트·멈춤 감시 동작은 같다. #17 의 나머지 항목은 열린 채 남는다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **판정의 이동 (`lib_py/delegation/wrapper.py`)**: `finalize_unfinished`(시계 선택, 디스크 heartbeat 유예, 브로커 재관측, `wall_clock_timeout`/`deadline_timeout`/`cancelled` 라벨, close 와 이벤트) → `finalize-begin`/`finalize-end`, `start_stall_watch` 의 감시 여부 → `stall-watch`, `_map_sub_rc` → `sub-status`, `_rc_for`(+`MAM_DELEGATE_FAIL_CLOSED`) → `exit-code`, 구독자 조기 종료 검사 → `subscriber-up`, `cmd_wait` 의 rc 정규화 → `wait-rc`. 래퍼는 `_decide` 하나로 부르고 서브커맨드의 종료 코드나 한 줄 출력에만 분기한다.
|
||
2. **정수 규칙 하나 (`lib_py/ints.py`)**: `lib_int.sh` 의 `mam_int`/`mam_int_knob` 와 runner 의 `_positive_int` 가 같은 구현(`positive_int`, `knob`)을 부른다.
|
||
3. **bash 에 남는 것**: spawn·wait·kill, 키 주입, 시그널 정리, 진단용 herdr busy 확인 하나, CLI 호환 층.
|
||
4. **finalizer 의 수명 (PR #41 계약과 같게)**: `finalize-end` 는 `--watch-pid "$$"` 를 받아 두 대기 내내와 close·이벤트 직전에 래퍼를 확인하고, 래퍼가 없으면 닫지도 발행하지도 않고 끝난다. 종료했지만 회수되지 않은 래퍼(좀비, `ps -o stat=` 의 Z)도 없는 것으로 본다.
|
||
5. **리뷰 R1 (병합 워크스페이스, codex)**: `finalize-end` 의 대기(`_sleep`)가 벽시계로 길이를 재서, 대기 중 시계가 뒤로 가면 bash `sleep N` 과 달리 늘어났다(10 초 뒤로 → 1 초 대기가 11 초). 대기 길이는 monotonic 시계로 재고 마감 판정은 벽시계에 남긴다(4c86107, 회귀 테스트 `test_u_a_wait_is_not_stretched_by_a_wall_clock_step_back`).
|
||
|
||
#### ✅ 품질 검증
|
||
- **옛 bash 대 새 래퍼 (`tests/test_issue17_item4_decisions.py`)**: 7209750 의 bash 함수를 그대로 복사한 `tests/data/issue17_item4_old_decisions.sh` 와 새 래퍼를 같은 입력에 돌려 출력·`FINAL_JOB_STATUS`·레지스트리/발행 호출을 비교한다(각 유예 중 TERM, 회수 없는 TERM 포함). 판정표 전체·잘못된 입력·경계값 단위 테스트.
|
||
- **독립 대조 (오케스트레이터)**: 7209750 대 518beda 실제 CLI, 오프라인으로 179 경우 모두 종료 코드·출력 일치(`mam_int`/`mam_int_knob` 19 입력, `submit --dry-run` 정수 플래그 6 개 × 19 입력, 8 상태의 레코드에 대한 `wait`).
|
||
- **리뷰 (병합 워크스페이스, 네 리뷰어)**: 1회차(@518beda) claude·agy·grok PASS, codex NOT PASS(R1 위 5). 4c86107 에서 수정. 2회차 델타(@4c86107) claude·agy·grok PASS. codex 는 첫 잡(817d1650)이 판정 없이 `deadline_timeout` 으로 닫혀 다시 돌렸고(24b21b9c), 그 리포트는 "R1 닫힘, 새 코드 결함 없음"이지만 자기 샌드박스가 루프백 브로커의 bind 를 막아 필수 두 파일 스위트(278 건)를 실행하지 못했다는 이유로만 NOT PASS 를 냈다. 같은 스위트를 병합 워크스페이스에서 4c86107 로 돌려 **278 passed** 를 확인하고 병합했다. 리포트는 `.agents/reports/<session>/`.
|
||
- **전체 스위트 (병합 워크스페이스, macOS·Python 3.9, MQTT 환경변수 없이)**: 518beda **1711 passed / 1 failed / 38 skipped**(72 분). 실패한 `tests/test_issue45_restore.py::test_b_only_the_resumed_sessions_restored_shell_is_closed` 는 main(7209750)에서도 실패한다: herdr 0.9.3 은 지워진 cwd 의 pane 을 `$HOME` 이 아니라 원래 경로로 복원해 테스트의 전제가 바뀌었다(이 PR 과 무관). 4c86107 의 델타는 관련 테스트 321 passed.
|
||
|
||
#### 📝 후속
|
||
- herdr 0.9.3 에 맞춘 `test_issue45_restore` 의 cwd 전제 수정.
|
||
- 래퍼 모듈이 패키지 경유로 paho 를 import 하는데 문서는 "stdlib only" 라고 한다; `_decide` 실패 시 `exit ""` 가 255 가 된다(`${rc:-1}` 제안); `lib_int.sh` 가 PATH 의 `python3` 를 쓴다; PR 본문은 0.2 초, 코드는 0.5 초 간격(claude 1회차 비차단 지적 N1·N2·N3·N5).
|
||
|
||
### 🚀 `v4.8.5` — Issue #45 A·B: 재기동된 격리 서버가 저장된 에이전트를 다시 띄우지 않고, resume 이 복원된 빈 셸을 닫는다 (2026-10-04)
|
||
|
||
> **패치 (PATCH Release)**: 격리 herdr 서버가 에이전트를 정지하지 않은 채 꺼졌다 다시 뜨면, herdr 가 저장 레이아웃을 복원하며 에이전트를 자기 재개 명령으로(권한 플래그 없이, 지운 에이전트까지) 다시 띄웠다. herdr 0.9.1 의 `[session] resume_agents_on_restore = false` 로 MAM 이 띄우는 서버에서 이를 끈다(A). 끈 뒤에도 pane 은 빈 셸로 복원되므로, resume 이 그 세션 자신의 빈 셸을 닫는다(B). 이슈의 제안 2·4 는 범위 밖.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **서버 설정 (`lib_py/herdr_config.py`)**: herdr 가 읽는 사용자 설정(`HERDR_CONFIG_PATH` → `XDG_CONFIG_HOME` → `~/.config`)에서 그 키만 false 로 바꾼 `mam-server-config.toml` 을 원본 옆에 만든다(상대 경로가 같게 풀린다). 사용자 파일은 고치지 않는다. 이 병합에는 `tomllib`(Python 3.11+)이 필요하다. PATH 의 `python3` 가 그보다 오래되면(macOS 기본 3.9) 복원은 꺼지지만 파일에 그 키만 담겨, MAM 서버는 사용자의 다른 herdr 설정 없이 뜨고 경고가 남는다: 출력이 원본 자신(별칭 포함)이거나 같은 원본으로 만든 파일이 아니면 원본별 이름을 쓴다. 파싱 → 키 설정 → 직렬화하고, 결과가 원본 + 키와 정확히 같아야 한다(헤더·점 표기·인라인 표 모두). 쓴 파일은 그 herdr 의 `config check` 를 통과해야 한다. herdr 는 파싱·스키마 오류면 파일 전체를 버리고("using defaults") 복원을 켜기 때문이다. 통과하지 못하면(깨짐, 비-bool 값, 표가 아닌 `session`, UTF-8 아님) 키 하나만 있는 파일을 쓰고 경고한다. MAM pane 이 물려받은 생성 파일은 기록된 원본으로 거슬러 올라간다.
|
||
2. **shim bootstrap (`lib.sh` heredoc)**: 서버를 띄울 때마다 설정을 다시 만들어 `HERDR_CONFIG_PATH` 로 넘긴다. 못 만들면 경고하고 예전처럼(복원 켜짐) 띄운다. 기본 `default` 서버와 사용자가 직접 띄운 서버는 바꾸지 않는다.
|
||
3. **복원된 빈 셸 (`lib_py/herdr_restore.py`, `resume_session.sh`)**: spawn 직전 한 번, `agent_session` 이 resume 하는 id 와 같고 live 에이전트가 없고 전경이 셸뿐인 pane 을 닫는다. 등록부는 보지 않는다(서버가 죽은 세션도 running 으로 남는다, Creator 이의 job 9f5f1ebe). `CP_PREFIX` 가 붙는 resume 이 재사용하지 못해 남던 빈 셸과 `$HOME` 으로 떨어진 빈 셸이 대상이다.
|
||
4. **S11 (`tests/test_issue23_24_startup_path.py`)**: 생성 shim 이 우연히 바뀌지 않게 하는 비교 기준을 heredoc 을 의도적으로 바꾼 ed73f67 로 옮겼다.
|
||
|
||
#### ✅ 품질 검증
|
||
- **실서버 테스트 (`tests/test_issue45_restore.py`, herdr 0.9.1)**: 짧은 일회용 HOME·세션과 가짜 claude 로, MAM bootstrap 경로에서 복원이 꺼짐(사용자 설정에 `[session]` 표가 있을 때 포함)과 같은 레이아웃을 일반 서버로 띄우면 `claude --resume` 이 뜨는 양성 대조, 닫기 조건, create → crash → resume 뒤 pane 하나만 남음을 확인한다. 수정을 끄면 A·B 테스트가 각각 실패함을 확인했다.
|
||
- **단위 (`tests/test_issue45_herdr_config.py`)**: 합치기 12 가지 경우와 실제 `herdr config check`, 원본 경로 규칙, 쓰기·폴백.
|
||
- **관련 스위트**: 새 두 파일 40 passed. issue9·shim contract·issue42·issue24 resume·uuid_target·issue21 prereqs·issue23_24 199 passed / 1 skipped(S11 기준 이동 후). tier1~4·issue21_phase2_loop_a 148 passed. 전체 스위트는 돌리지 않았다.
|
||
- **리뷰 (병합 워크스페이스, 네 리뷰어)**: 1회차(@35935f9) claude·agy·grok NOT PASS(테스트 이식성: Python 3.9 의 `tomllib`, macOS 의 `/proc`·`/tmp`), codex 판정 없음(모델 용량 초과로 1 시간 예산 소진). 36f0b8d·e8d9151 에서 수정. 2회차(@e8d9151) claude·agy·grok PASS, codex NOT PASS(R1 `config check` 를 실행하지 못하면 검사 안 된 병합을 넘김 → 스키마 오류면 herdr 가 파일 전체를 버려 복원이 다시 켜짐, R2 0·False·-1 같은 pid 응답에도 pane 을 닫음, R3 TOML `nan` 이 재파싱 비교를 깨 사용자 설정을 모두 버림). ab93776 에서 수정: 검사 못 한 병합은 쓰지 않고 MINIMAL(그 검사도 못 하면 MINIMAL 을 경고와 함께 — 아무것도 안 넘기면 복원이 켜진 채 남는다), pid 는 양의 정수만, nan 끼리 같다고 보는 재귀 비교. 3회차(@ab93776) **4/4 PASS**. 리포트와 identity 사이드카는 `.agents/reports/<session>/`.
|
||
- **전체 스위트 (병합 워크스페이스, macOS·Python 3.9, MQTT 환경변수 없이)**: ab93776 **1456 passed / 0 failed / 38 skipped**(65 분; skip 중 34 건은 Python 3.9 의 tomllib 전용 검사). 1회차 head 35935f9 는 herdr_config 테스트가 수집 단계에서 스위트를 멈췄고, 그 파일을 빼면 1418 passed / 5 failed(1회차 지적 테스트). purge 테스트도 통과(PR 본문의 12 건 기준 실패는 Linux, #28).
|
||
- **Python 3.9 (병합 워크스페이스)**: `tests/test_issue45_herdr_config.py` 가 모듈 머리에서 `import tomllib` 을 해 3.11 미만에서는 스위트 전체가 수집 단계에서 멈췄다. import 를 선택적으로 바꾸고 결과를 파싱하거나 사용자 설정 유지를 기대하는 29 건만 `tomllib` 없으면 skip(3.9: 22 passed / 29 skipped, 3.11: 51 passed).
|
||
- **리뷰**: 1회차 codex NOT PASS(R1 herdr 스키마 검증 없음, R2 점 표기·인라인 표에서 설정 유실, R3 원본과 출력 이름 충돌 시 덮어씀). 811b080 에서 수정. 2회차 codex NOT PASS(R3 의 폴백 경로는 같은 검사를 거치지 않음), 폴백도 같은 이름 선택을 거치도록 수정.
|
||
|
||
### 🚀 `v4.8.4` — `.claude/skills` 링크 검사가 두 로컬 배치를 받고 깨진 링크를 잡는다 (2026-10-04, PR #47)
|
||
|
||
> **패치 (PATCH Release)**: v4.8.3(PR #46)이 넣은 `test_claude_skills_link_resolves_to_agents_skills` 가 병합 후 본 체크아웃에서 실패했다. 테스트는 `.claude/skills` 를 `.agents/skills` 로 가는 링크 하나로 가정했지만, 이 로컬(git-ignored) 디렉터리는 스킬별 링크를 담은 디렉터리일 수도 있다. PR #46 의 리뷰·스위트는 워크트리에서 돌아 이 테스트가 skip 이었다. 테스트만 고친다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **두 배치 (`tests/test_issue42_skill_blocks.py`)**: 단일 링크(→ `.agents/skills`) 또는 여덟 스킬 각각이 `.agents/skills/<skill>` 로 해석되는 디렉터리를 모두 받는다.
|
||
2. **깨진 링크는 실패 (리뷰 codex R1)**: `Path.exists()` 는 링크를 따라가므로 깨진 최상위 링크가 "없음"으로 skip 됐다. 부재는 "존재하지도 않고 링크도 아님"일 때만이다.
|
||
3. **워크트리에서도 검사**: 검사를 `claude_skills_problems()` 로 분리하고 합성 배치 6 개(없음, 단일 링크, 스킬별 링크, 다른 스킬을 가리키는 링크, 깨진 최상위 링크, 깨진 스킬 링크)로 시험한다.
|
||
|
||
#### ✅ 품질 검증
|
||
- **리뷰 (네 리뷰어)**: 1회차(@e1ed270) claude·agy·grok PASS, codex NOT PASS(R1 깨진 최상위 링크 skip). 150b493 에서 수정. 2회차 **4/4 PASS**. 리포트와 2회차 identity 사이드카는 `.agents/reports/<session>/`.
|
||
- **테스트**: `tests/test_issue42_skill_blocks.py` 37 passed / 1 skipped(워크트리). codex 의 탐침(트리에 깨진 `.claude/skills`)은 이제 실패, 본 체크아웃의 스킬별 배치는 문제 없음. 테스트 함수 하나만 바뀌어 전체 스위트는 돌리지 않았다(직전 전체 스위트: v4.8.3 후보 1408 passed / 0 failed).
|
||
|
||
### 🚀 `v4.8.3` — Issue #42 단계 1·2: SKILL.md 코드 블록 목록을 테스트 데이터로, 스크립트가 이미 하는 블록 삭제 (2026-10-04, PR #46)
|
||
|
||
> **패치 (PATCH Release)**: 스킬 절차를 SKILL.md 의 코드 블록으로 다시 적어 에이전트가 손으로 따라 하게 하던 곳 가운데, 진입 스크립트가 이미 그대로 하는 블록을 지우고 호출 한 줄과 옵션·종료 코드 표로 바꿨다(Issue #42 의 단계 1·2). 동작 변경 없음. 단계 3~5(스크립트에 없는 절차의 코드화, bash 진입점 정리, 문서 가드 확장)는 #17 항목 4 다음에 진행한다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **목록이 테스트 데이터다 (`tests/data/skill_code_blocks.json`)**: 45d1621 의 SKILL.md 8 개에 있던 펜스 블록 37 개를 (a) 스크립트 중복, (b) 스크립트에 없는 절차, (c) 예시·호출·도식으로 나누고 스크립트 file:line 근거를 달았다. (a) 5 개는 삭제(`removed`), 다른 호출 블록에 합쳐진 (c) 1 개는 `superseded`, 나머지는 현재 블록 33 개로 추적한다. (b) 목록은 단계 3 과 #17 항목 4 의 입력이다.
|
||
2. **문서 가드 (`tests/test_issue42_skill_blocks.py`)**: 블록이 목록 없이 바뀌거나, 지운 블록의 본문이 돌아오거나(정확 sha 와 공백 무시 fingerprint, 목록과 무관하게), 지운 블록의 진입점 호출이 빠지거나, 종료 코드 표가 스크립트와 어긋나거나, 근거가 존재하지 않는 곳을 가리키면 실패한다.
|
||
3. **중복 블록 삭제 (resume·stop·create SKILL.md)**: resume Workflow, stop 사전 확인, create 사전 확인·Workflow·YAML 수동 추가를 진입점 호출 한 줄과 옵션·종료 코드 표로 바꿨다.
|
||
4. **문서-스크립트 불일치 정정**: resume 은 attach 하지 않으며, attach 는 서버를 명시해야 한다(`herdr --session <server> agent attach <name>`). stop `--purge-conversation` 은 `--yes` 가 필요하다. `--dry-run` 은 기동·레지스트리 갱신을 하지 않지만 macOS 에서 에이전트 바이너리의 quarantine 속성을 지운다. 그 밖에 status `--json` 키, create `cmd_full`, 존재하지 않던 yaml 편집 스크립트 언급.
|
||
|
||
#### ✅ 품질 검증
|
||
- **리뷰 (작성 워크스페이스)**: planner + 네 리뷰어 3 회차 만장일치 PASS.
|
||
- **리뷰 (병합 워크스페이스)**: 1회차(@8d26106) claude·agy·grok PASS, codex NOT PASS(R1 분리된 attach 안내가 격리 서버를 잃음, R2 `--dry-run` "무쓰기" 설명이 실제와 다름, R3 지운 블록이 목록에 다시 등록되면 돌아와도 가드가 못 잡음). 17eba09 에서 수정(문서·테스트 데이터·가드 테스트만, 제품 코드 변경 없음). 2회차 **4/4 PASS**. 리포트와 2회차 identity 사이드카는 `.agents/reports/<session>/`.
|
||
- **전체 스위트 (병합 워크스페이스, 8d26106, MQTT 환경변수 없이)**: **1408 passed / 0 failed / 4 skipped**(61 분). PR 본문의 "기준 실패 12 건"(`tests/test_issue10_13_purge.py`)은 Linux 호스트의 것으로(#28), 이 macOS 호스트에서는 통과. 17eba09 의 변경(문서 3 개·테스트 데이터·가드 테스트)은 문서 테스트 6 개 파일 132 passed / 1 skipped 로 확인.
|
||
- **후속**: 종료 코드 스캐너가 heredoc 안 주석·`sys.exit`·`|| true` 를 일반적으로 다루지 못함(현재 표는 맞음, codex C2); 목록이 블록 줄 번호에 묶여 산문 편집에도 갱신이 필요(claude O2); `$(( 1 << 2 ))` 를 heredoc 시작으로 오인(현재 스크립트엔 없음, claude O3); Linux purge 12 건은 #28.
|
||
|
||
### 🚀 `v4.8.2` — PR #41·#43 리뷰 후속 정리: 날짜 테스트, 설정 파일 오류 한 줄, BOM, 정수 설정값 규칙 확장, 테스트 훅 제거 (2026-10-03, PR #44)
|
||
|
||
> **패치 (PATCH Release)**: v4.8.0(PR #41)·v4.8.1(PR #43) 리뷰에서 비차단으로 남긴 후속 사항을 이 워크스페이스의 루프(creator-claude-01 구현, 네 리뷰어 리뷰)로 처리했다. 동작 변경은 아래 항목에 한정한다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **F1 날짜 의존 테스트 (`tests/test_issue21_acceptance.py`)**: `test_a8` 가 매달 1~9 일에 실패했다. 테스트가 마커에 쓰는 `ps -o lstart` 는 `Oct 1` 처럼 공백 두 칸인데 `loop_lock.sh` 는 공백을 한 칸으로 줄여 비교한다. 테스트가 제품과 같은 정규화로 쓰고, 두 칸 공백 마커로 날짜와 무관하게 고정하는 테스트를 더했다. 제품 코드는 그대로.
|
||
2. **F2 Python 진입점의 설정 파일 오류 (`mqtt_common.load_env_or_report`)**: 존재하지만 읽을 수 없는 `.mam.env` 에서 publish_event·registry·job_subscriber·cli·bench·heartbeat·remote 가 트레이스백 대신 `ERROR: <경로>: cannot read (<예외 종류>)` 한 줄과 rc 1. 라이브러리 함수는 종료하지 않는다. 리뷰(codex R1)에서 실패를 "이미 읽음"으로 캐시해 같은 프로세스의 재호출이 검사를 건너뛰던 문제를 찾아, 읽기에 성공했을 때만 캐시한다.
|
||
3. **F3 `.mam.env` 세부 (`envfile.py`, `.mam.env.example`, `MESSAGING.md`)**: 첫 줄 UTF-8 BOM 을 벗긴다. 한 파일 안 중복 키는 첫 값이 이긴다고 명시. "명시한 `MAM_ENV_FILE` 이 없으면 오류" 설명을 실제(없으면 건너뜀, 있는데 못 읽으면 오류)에 맞췄다.
|
||
4. **F4 정수 설정값 규칙 확장 (`runner.py`, `lib_int.sh`)**: 러너의 `MAM_WAIT_GRACE_S`·`MAM_CHILD_WAIT_S`·`MAM_JOB_TREE_MAX` 가 `_positive_int` 규칙(잘못된 값은 경고 후 기본값; `MAM_CHILD_WAIT_S=abc` 의 ValueError 제거). `[ -ge ]`/`[ -le ]`/`[ -gt ]`/`sleep` 에 쓰이던 bash 정수 노브도 `mam_int_knob` 으로. `mam_int`/`mam_int_knob` 은 `lib.sh` 없이 source 되는 스크립트도 쓰도록 `lib_int.sh` 로 분리. `MAM_WAIT_GRACE_S` 를 loop SKILL.md 에 문서화.
|
||
5. **F5 제품 코드의 테스트 훅 제거 (`stall_watch.py`)**: `MAM_TEST_UNRELIABLE_AGENT` 분기를 없애고, S13 은 테스트 seam 으로 같은 상황(`agent_status_reliable=False`)을 만든다.
|
||
6. **F6 문서·테스트 정밀도**: 감시자 수명 계약의 "≤ 60 s" 를 herdr wait 에 한정. "대기 중 래퍼 소멸" 테스트가 두 번째 wait 안에서 소멸을 일으킨다.
|
||
|
||
#### ✅ 품질 검증
|
||
- **회귀 테스트**: 제품 코드를 바꾸는 항목(F2·F3·F4·F5)은 제품 파일만 main 으로 되돌린 사본에서 47 건 실패(claude 독립 확인). F2 재호출 캐시 회귀는 별도.
|
||
- **리뷰 (이 워크스페이스, 네 리뷰어)**: 1회차 claude·agy·grok PASS, codex NOT PASS(R1 실패 캐시). 75f364e 에서 수정. 2회차 **4/4 PASS**. 리포트와 2회차 identity 사이드카는 `.agents/reports/<session>/`, creator 리포트는 `.agents/reports/creator-claude-01/`.
|
||
- **전체 스위트 (브로커 환경변수 없이, 별도 워크트리)**: 75f364e 전체 **1380 passed / 0 failed / 3 skipped**(61 분, MQTT 환경변수 없이). 매달 1~9 일에 실패하던 `test_a8` 이 10월 3일에 통과(F1 확인). 첫 실행은 맥 이동으로 93% 에서 중단돼 다시 돌렸다.
|
||
- **후속**: `tests/test_o2_race_free_lock.py` 의 호출되지 않는 `get_lstart` 헬퍼(정규화 없음) 정리(claude T1); `reconcile.sh` 내장 구독자는 진입점 목록 밖이라 읽기 실패 시 여전히 트레이스백(claude T2).
|
||
|
||
### 🚀 `v4.8.1` — Issue #26 `.mam.env` 를 Python 파서 하나로 읽고, 정수 설정값을 10진수로, 테스트를 로컬 MQTT 브로커로 (2026-10-02, PR #43)
|
||
|
||
> **패치 (PATCH Release)**: 같은 `.mam.env` 를 delegate-job 래퍼는 bash `source` 로, Python 도구는 `KEY=VALUE` 로 읽어서, 한쪽에서 정상인 줄(`KEY= value`)이 다른 쪽에서 래퍼를 즉시 종료시키고 값 속 `$(...)` 는 실행되던 문제(Issue #26)를 고친다. 0 으로 시작하는 정수 설정값이 bash 에서 8진수로 읽히던 문제와, 공개 MQTT 브로커에 기대던 테스트도 함께 정리했다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **파서는 하나 (`lib_py/delegation/envfile.py`, 래퍼, `mqtt_common.py`)**: 주석·빈 줄 무시, 키와 값의 앞뒤 공백 제거, 감싸는 따옴표 한 쌍 제거, 셸 해석 없음. 키는 `[A-Za-z_][A-Za-z0-9_]*`. 형식이 어긋난 줄, `export` 접두어, NUL 이 든 줄, bash 자체 변수(`IFS`, `PS4`, `RANDOM`, `BASH_*` 등)는 값 없이 줄 번호로 경고하고 건너뛴다. 래퍼는 `source` 대신 파서가 만든 셸 인용 `export` 문을 eval 한다. 이미 있는 환경변수가 파일보다 우선한다(전에는 래퍼만 파일이 덮어썼다). 존재하지만 읽을 수 없는 파일은 오류다: 래퍼는 rc 1, Python 진입점은 `EnvFileError` 로 멈춘다.
|
||
2. **정수 설정값은 10진수 (`lib.sh` `mam_int`/`mam_int_knob`, `runner.py` `_positive_int`)**: `MAM_JOB_TIMEOUT_S=0700` 이 래퍼에서 448 초가 되던 문제. 한 규칙(ASCII 공백·숫자만, 10진수, 유효 9 자리 이하, 값별 최솟값)을 로케일과 무관하게 적용한다. 잘못된 환경변수는 경고 후 기본값, 잘못되거나 빈 값으로 명시한 플래그는 rc 1.
|
||
3. **테스트가 외부 브로커에 기대지 않는다 (`tests/_local_mqtt.py`)**: 공개 `broker.hivemq.com` 왕복에 기대던 테스트 22 개를 테스트 안의 로컬 MQTT 3.1.1 브로커로 옮겼다. "브로커에 닿지 않음" 건너뛰기는 실패가 됐고, 정적 검사가 공개 브로커 주소의 재유입을 막는다.
|
||
4. **문서**: `.mam.env.example` 머리말에 형식 규칙과 우선순위.
|
||
|
||
#### ✅ 품질 검증
|
||
- **신규 테스트**: `tests/test_issue26_envfile.py`(두 실제 로더의 같은 입력·같은 결과, 예약 변수, 읽기 실패·NUL, 래퍼에서 명령 미실행), `tests/test_issue26_ints.py`(래퍼·lib.sh·러너의 정수 규칙 표, 두 로케일), `tests/test_local_mqtt.py`(로컬 브로커의 3.1.1 적합성과 실제 publish_event → job_subscriber 경로).
|
||
- **리뷰 (병합 워크스페이스)**: 1회차(@b58edac) claude·agy PASS, codex NOT PASS(R1 읽을 수 없는 파일인데 래퍼가 rc 0 으로 기본값 진행, R2 로케일에 따라 bash·Python 의 정수 해석이 다름, R3 빈 값으로 명시한 정수 플래그가 검증을 우회, R4 NUL 이 든 값을 두 로더가 다르게 처리), grok 은 컨텍스트 압축 중 응답 시간 초과로 판정 없음(늦게 낸 리포트도 R1 과 같은 지적). 0eec795 에서 네 건과 claude 비차단 R1·R2(문서·메시지)를 고치고 회귀 테스트 8 건(모두 수정 전 코드에서 실패)을 추가. 2회차(@0eec795, grok 은 PR 전체) **4/4 PASS**. 네 리뷰어 모두 "읽을 수 없는 파일이면 Python 진입점도 멈춘다"에 동의. 리포트와 identity 사이드카는 `.agents/reports/<session>/`.
|
||
- **전체 스위트 (병합 워크스페이스, 브로커 환경변수 없이)**: 0eec795 전체 1315 passed / 1 failed / 3 skipped(60 분, MQTT 환경변수 없이). 실패 1건은 `test_issue21_acceptance.py::test_a8` 로 이 PR 과 무관한 날짜 의존 테스트 버그(main 에서도 같음, 아래 후속). 1회차 head b58edac 전체도 1302 passed / 같은 1건 실패. 공개 브로커 때문에 실패하거나 건너뛴 테스트 없음. PR 본문의 "purge 12건 기준 실패"는 이 호스트에서 재현되지 않음.
|
||
- **후속**: Python 진입점의 `EnvFileError` 를 트레이스백 대신 `ERROR:` 한 줄로(claude S1); 파일 첫 줄의 BOM 이 "잘못된 키"로 건너뛰어짐(grok); 한 파일 안 중복 키는 첫 값이 이김 — 문서화(grok); `MESSAGING.md` 의 "없는 `MAM_ENV_FILE` 은 오류" 설명이 실제(조용히 건너뜀)와 다름(grok); `MAM_WAIT_GRACE_S`·`MAM_CHILD_WAIT_S`·`MAM_JOB_TREE_MAX` 를 `_positive_int` 로(grok); `[ -ge ]` 전용 노브의 비숫자 값(claude R3); `mam_int` 와 플래그 검증을 #17 항목 4 이전 목록에(claude R5); `test_issue21_acceptance.py::test_a8` 가 매달 1~9 일에 실패 — 테스트가 마커에 쓰는 `ps -o lstart` 의 두 칸 공백(`Oct 1`)을 제품 코드처럼 한 칸으로 줄이지 않는다(제품 동작은 정상).
|
||
|
||
### 🚀 `v4.8.0` — Issue #33 위임 잡 예산 조절과 벽시계 라벨 분리, Issue #30 멈춘 에이전트 알림(알림 전용) (2026-09-26, PR #41)
|
||
|
||
> **마이너 마일스톤 (MINOR Release)**: 위임 잡의 벽시계 예산(기본 3600 초)을 조절할 수 없고 벽시계 초과와 무응답이 같은 `deadline_timeout` 으로 기록되던 문제(Issue #33)와, 살아 있지만 멈춘 에이전트(권한 확인 창에 막힘, 종결 이벤트 없이 턴 종료)를 잡 제한 시간이 지나야만 알 수 있던 문제(Issue #30)를 한 브랜치로 고쳤다. 새 모듈 `lib_py/stall_watch.py`, 루프 옵션 `--job-timeout`, 새 종결 사유 `wall_clock_timeout`, 구독자 종료 코드 4 가 추가되어 MINOR 로 올린다. 예산 **연장**은 범위에서 빼고 #39(에이전트가 프로토콜 이벤트로 요청)로 분리했다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **잡 예산이 이동한다 (#33, delegate-job 래퍼, `lib_py/loop/runner.py`, `remote.py`)**: 래퍼의 예산은 `--timeout` > `MAM_JOB_TIMEOUT_S` > 3600. 루프는 `--job-timeout <sec>` 을 받아 모든 위임 잡에 넘긴다. 러너의 잡 대기 상한과 #34 하위 잡 대기(`MAM_CHILD_WAIT_S`) 기본값은 상수 3900 이 아니라 **잡 예산 + 300 초**로 계산한다(명시값은 존중). 예산은 잡 하나 단위다. 원격 잡을 포함한 모든 transport 에 적용.
|
||
2. **두 시계가 다른 라벨을 받는다 (#33, `job_subscriber.py`, 래퍼, `registry.py`, `engine.py`)**: 구독자는 원래 두 시계를 따로 셌지만 둘 다 rc 2 를 돌려줘 래퍼가 모두 `deadline_timeout` 으로 기록했다. 이제 벽시계 초과는 rc 4 → `wall_clock_timeout`, 무응답(K×N 초 이벤트 없음)은 rc 2 → `deadline_timeout`. 종료 코드는 구독자와 래퍼 양쪽에서 이름 붙은 상수(`RC_*` / `SUB_RC_*`)로 참조하고 두 쪽 값의 일치를 테스트로 고정. 디스크 heartbeat 유예가 deadline 에 닿아 끝나는 경우도 벽시계(`FINALIZE_CLOCK`). 등록부의 `close --request` 종류와 #34 하위 잡 표의 `CLOSED-UNCONFIRMED` 분류에 새 라벨 반영. `delegate-job wait` 는 두 시계 모두 기존대로 rc 2(하위 호환).
|
||
3. **멈춤 감시자 — 알림 전용 (#30, `lib_py/stall_watch.py`)**: 래퍼 옆에서 잡의 에이전트 pane 을 herdr `agent wait` 로 지켜본다. **잡을 닫는 코드 경로가 없다** — 유일한 발행은 `progress` 이벤트(`_notify`)와 재주입 한 번이며 테스트로 고정. `blocked` → `progress --detail stalled:blocked` + herdr detection 화면의 문구 첨부, **자동 응답 없음**(#30 의 동기 사례가 "Dangerous rm operation… proceed?" 창). `idle` 이 `MAM_STALL_IDLE_S`(기본 600 초) 이어지고 에이전트에게 백그라운드 자식 프로세스가 없고 입력창이 비어 있으면 → `stalled:idle` + "이어서 하라" 신호 한 번(잡당 1회). 백그라운드 자식은 herdr 로 얻은 에이전트 pid 의 OS 자손으로 판정하고(Claude Code 는 백그라운드 명령을 별도 프로세스 그룹으로 띄워 herdr 에는 보이지 않는다), 상주 자식(codex 의 `codex-code-mode-host` 등)은 어댑터 필드(`resident_children`)로 제외. `agent_status` 신뢰도도 어댑터 필드. 원격·human 잡은 감시하지 않고 로그 한 줄. herdr 실패는 `MAM_STALL_HERDR_FAIL_S`(120 초) 뒤 경고. `MAM_STALL_WATCH=0` 으로 끔.
|
||
4. **어댑터·입력창 (`lib_py/agents/*`, `input_region.py`)**: 상주 자식 목록과 `agent_status` 신뢰도 필드, 입력창이 비었는지 판정(여러 줄 초안, 프롬프트 글리프가 초안 안에 있는 경우). pane 픽스처 5종.
|
||
5. **문서**: delegate-job/loop SKILL.md 에 `--job-timeout`, `MAM_JOB_TIMEOUT_S`, `wall_clock_timeout`, 감시자 환경변수 행.
|
||
|
||
#### 🧭 설계 결정 (이슈 댓글의 계획에서 달라진 점)
|
||
- 예산 연장(벽시계에 닿으면 멈춰서 묻고 정책이 연장)은 뺐다: 루프 러너가 래퍼를 동기 호출해 결정을 쓸 위치에 없고, 연장 거부 근거로 제안된 `state_change_seq` 정체는 한 시간 넘게 같은 상태로 전체 스위트를 도는 정상 creator 도 거부한다. #39 는 에이전트가 스스로 연장을 요청하는 방식을 다룬다.
|
||
- 멈춤 판정은 알림 전용(#30 댓글 수정본, 2026-09-25): 재주입 실패 후 `stalled` 종결과 다이얼로그 자동 수락은 철회.
|
||
- #17 항목 4(래퍼 종결 처리의 Python 이전)는 하지 않았다. bash 변경은 라벨을 나르는 부분으로 최소화. 리뷰어 합의(A–D, claude·agy·grok·codex): A 알림 전용 4/4 동의. B #39 분리 동의 — 연장 횟수·누적 예산 상한은 오케스트레이터 쪽, token/attempt 로 인증된 요청만 멱등 처리해 새 절대 deadline 확정, heartbeat 만 있고 `state_change_seq` 가 그대로면 거절, 원격 잡도 같은 규칙. C #40 트리거는 `completed` 를 검증하는 수신 측 — 래퍼 구독자가 기본 진입점, 루프 `wait_for_job` 은 누락 복구 경로(job/token/attempt 단위 중복 방지), #17 항목 4 에 흡수하거나 바로 다음. D 항목 4 를 미룬 것 동의 — 옮길 bash 판정: `FINALIZE_CLOCK`·디스크 유예 종료 결정, 감시 여부 판정, `MAM_JOB_TIMEOUT_S` 검증, rc→close_req/라벨 매핑, `_map_sub_rc`·`cmd_wait` 정규화, 감시자 수명 관리.
|
||
|
||
#### ✅ 품질 검증
|
||
- **신규 테스트** (`tests/test_issue30_stall_watch.py` 789줄, `tests/test_issue33_budget.py` 358줄): 예산 우선순위·전달·대기 상한, 두 시계의 라벨과 rc 상수 일치, 감시자의 판정(`judge`)·발행이 `progress` 뿐임·백그라운드 자식·입력창·재주입 1회·원격 제외·herdr 실패. 기존 테스트 조정: `test_tier1_unit.py` G-8(두 시계를 같은 값으로 주면 벽시계가 먼저 닿아 새 코드를 받던 결정적 회귀 → 값 분리), registry CLI 스냅샷·help 캡처, `test_issue18_*`, `conftest.py`.
|
||
- **실제 에이전트 시험 (작성 워크스페이스, claude, `MAM_STALL_IDLE_S=45`)**: A. `started` 만 보내고 완료 없이 턴을 끝낸 에이전트 → 48 초 뒤 `stalled:idle` 경고와 신호 한 번, 에이전트가 받자마자 `completed`. B. 150 초 명령을 백그라운드로 돌리며 쉬는 에이전트 → 경고도 신호도 없이 명령이 끝난 뒤 스스로 `completed`. `blocked` 는 테스트로만 확인.
|
||
- **리뷰**: 작성 워크스페이스 claude·agy·grok·codex 전원 PASS(opencode 는 Ollama 한도로 첫 회차 PASS 뒤 탈락). 병합 워크스페이스에서 PR head 를 detached 워크트리에 받아 재검토: 1회차(@1673818) claude NOT PASS(N1: 래퍼가 감시자를 무한 wait), codex NOT PASS(R1: 래퍼가 TERM 으로 죽으면 감시자 고아, R2: herdr 대기 중 닫힌 잡에 재주입), agy·grok PASS → af0a2a2 에서 유계 stop·`--watch-pid`·`_dispatch_open` 재검사. 2회차(델타) claude·agy PASS, grok NOT PASS(TERM 5 초 예산이 close 시계를 늦춤), codex NOT PASS(D1: 알림 발행 중 닫힌·재디스패치된 잡에 재주입, D2: 대기 중 래퍼 소멸 뒤 1회 재주입) → 9200562 에서 KILL 즉시 정지·prompt 직전 strict 게이트(`_may_prompt`)·wait 직후 래퍼 확인. 3회차(델타 @9200562) 4/4 PASS. 회귀 테스트 `tests/test_issue30_stall_watch_stop.py` 11건은 모두 수정 전 코드에서 hang/실패. 2회차 첫 실행은 claude 세션 사용량 한도로 끊겨 그 잡을 `cancelled` 로 닫고 다시 돌렸다. 리포트와 identity 사이드카는 `.agents/reports/<session>/`.
|
||
- **전체 스위트 (병합 워크스페이스, 자체 venv 가 있는 워크트리)**: af0a2a2 전체 1230 passed / 4 failed / 5 skipped(73 분) — 실패 3건은 단독 재실행 통과, 1건은 B3(공개 브로커 heartbeat 지연, 1회차 head 에서도 같음). 최종 head 9200562 영향 파일(stall_watch_stop·stall_watch·issue14·issue33·tier1·version) 159 passed / 1 failed(B3) / 1 skipped(S6, Linux /proc).
|
||
- **후속**: #17 항목 4 목록(합의 D), #39 연장 보완책(합의 B), #40 트리거(합의 C); `MAM_TEST_UNRELIABLE_AGENT` 테스트 훅(M7); B3·S9·S10·issue14 idle-grace·issue18:497 의 공개 브로커 의존(로컬 events.log 로 단언하거나 등록부터 브로커 격리); `MAM_JOB_TIMEOUT_S=08` 이 bash 에서 8진수로 읽힘; `MAM_WAIT_GRACE_S` 미문서·러너의 잘못된 `MAM_JOB_TIMEOUT_S` 무언; SKILL.md 의 ≤60 s 문구를 herdr wait 로 한정; 부모 소멸 테스트의 alive 대역을 wait 안에서 바꾸기.
|
||
|
||
### 🚀 `v4.7.4` — Issue #34 루프가 creator 의 하위 위임 잡까지 기다리고 리뷰어에게 하위 잡 상태를 근거로 제공 (2026-09-25, PR #38)
|
||
|
||
> **패치 마일스톤 (PATCH Release)**: creator 가 작업 일부를 delegate-job 으로 다른 에이전트에게 맡기면, 그 하위 잡이 아직 돌고 있어도 루프가 리뷰로 넘어가 **미완성 산출물이 PASS** 되던 문제(Issue #34)를 고쳤다. 루프는 자신이 만든 잡만 감시했고 잡 레코드에는 부모·자식 관계가 없었다. 필드 하나(`parent_job`)와 옵션 둘(`--parent-job`, `--wait-open-jobs`)이 늘지만 잘못된 판정 경로를 고치는 수정이라 PATCH 로 올린다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **잡이 부모를 기록 (`lib_py/delegation/registry.py`, delegate-job 래퍼)**: `submit --parent-job <id>` 가 이 잡이 어느 잡에서 위임됐는지 남긴다. 부모가 등록부에 없으면 등록을 거부한다(오타가 조용히 루트 잡이 되지 않도록). 기본값은 원격 러너가 주는 `MAM_JOB_ID` 이며 이 등록부에 없으면 경고만 하고 부모 없이 등록한다. `registry.py list --parent <id>`, `children_of`/`descendants_of`(너비 우선, 한 번 방문, 순환 안전, `depth`).
|
||
2. **에이전트에게 부모 ID 전달**: 로컬 에이전트는 pane 에 오래 떠 있어 잡마다 환경변수를 넣을 수 없으므로, 잡마다 새로 주입되는 안내문과 `brief.md` 에 "하위 위임 시 `--parent-job <이 잡 ID>` 를 붙여라" 를 적는다(`create_session.sh --submit-job` 도 같음). 실제 creator 에게 방법을 알려주지 않고 위임을 시켰을 때 스스로 붙였다.
|
||
3. **루프가 자손 잡까지 대기 (`lib_py/loop/runner.py: wait_for_job_tree / wait_for_descendants`)**: creator 잡(구현·수정·순응)이 끝난 뒤 자손 잡이 모두 종결될 때까지 기다린 다음 리뷰로 넘어간다. 자손은 폴링마다 다시 읽어 손자 잡도 잡는다. 상한은 둘: `MAM_CHILD_WAIT_S`(creator 잡 종결부터, 기본 3900 초; 각 하위 잡은 #33 의 3600 초 예산으로 닫힌다)와 `MAM_JOB_TREE_MAX`(기본 50, 넘으면 기다리지 않음). `--wait-open-jobs` 는 부모 없이 등록된 열린 잡까지 기다린다(무관한 잡이 루프를 붙잡을 수 있어 기본 꺼짐).
|
||
4. **리뷰어가 하위 잡 상태를 봄 (`_subjobs_block`)**: 리뷰 프롬프트 앞에 하위 잡 표(세션·역할·상태·종료 사유·깊이)를 넣고, 열린(OPEN)·미확인 종결(CLOSED-UNCONFIRMED: `deadline_timeout`/`agent_blocked` 로 오케스트레이터가 닫은 잡, 에이전트는 계속 일하고 있을 수 있음)·실패 하위 잡이 있으면 NOT PASS 를 내도록 규칙을 준다. `session_busy`/`target_not_found`/롤백은 NOT-DISPATCHED. 수정 라운드에서도 앞선 creator 잡의 하위 잡이 표에 남는다. 부모 미표기 열린 잡은 `[귀속되지 않은 열린 잡]` 으로 따로 보여준다. 하위 위임이 전혀 없으면 프롬프트는 바이트 단위로 이전과 같다.
|
||
5. **하위 잡이 있을 때 리뷰 중 소스 변경은 승인 무효 (`_fingerprint`)**: 하위 잡이 리뷰 도중 파일을 고칠 수 있으므로, `--worktree`/`--base` 없는 루프에도 identity 모드의 B7/B10 에 해당하는 가드를 둔다. 제어 평면(`.mam`, jobs, 감사 로그) 디렉터리는 지문에서 제외하고, **하위 잡이 있는 실행에서만** 켠다(하위 잡 없는 기존 루프의 동작 불변; 켜 두면 샌드박스 e2e 가 매 라운드 무효화됐다).
|
||
6. **같은 서버의 다른 워크스페이스 에이전트에게 위임 (`lib.sh: _herdr_scope_is_pane_inherited`)**: herdr pane 이 내보내는 `HERDR_WORKSPACE_ID` 가 이름 조회(`agent`/`has-session`/`kill-session`)의 스코프로 쓰여 다른 워크스페이스의 에이전트가 보이지 않았다(워크트리 creator 의 하위 위임이 기본적으로 걸림). pane 안의 호출자가 이름으로 대상을 찾을 때 그 ID 가 `HERDR_PANE_ID` 의 워크스페이스와 같으면 뗀다(살아 있는 에이전트 이름은 서버 단위로 유일). pane 밖의 명시 스코프·pane 과 어긋나는 스코프·pane/세션 생성 호출은 그대로. 대상이 보이지 않아 취소된 잡은 `session_busy` 가 아니라 `target_not_found` 로 기록된다.
|
||
7. **문서**: delegate-job/loop SKILL.md, MULTI_AGENT_RULES(ko).
|
||
|
||
#### ✅ 품질 검증
|
||
- **신규 테스트** (`tests/test_issue34_job_tree.py`, 598줄): 실제 래퍼로 하위 잡을 등록하고 실제 등록부를 읽는다 — 부모 검증·`descendants_of`·자손 대기·상한·하위 잡 표와 상태 분류·stale 가드·같은 서버 다른 워크스페이스 조회. 첫 루프 시점의 14건은 수정 전 코드(7a4a0c2)에서 모두 실패.
|
||
- **리뷰**: 작성 워크스페이스 5인 전체 루프 2회차 전원 PASS + 델타 전원 PASS(codex 1차: 수정 라운드에서 앞선 creator 잡의 하위 잡이 표에서 빠짐 → 3fdbb62; 오케스트레이터: stale 가드를 옵션 없는 루프에 무조건 켜자 `test_tier4_e2e.py` 시나리오 5 회귀 → 1d4f338; 실제 시험: 같은 서버 다른 워크스페이스 스코프 → ed293dc). 병합 워크스페이스에서 PR head 를 detached 워크트리에 받아 재검토: 1회차 @ed293dc claude PASS(비차단 K1–K4)·agy PASS, grok·codex NOT PASS — 래퍼가 투입 전에 `cancelled --detail target_not_found|session_busy` 로 닫은 하위 잡의 사유를 `get_feedback` 이 읽지 않아 표에 사유 없는 `cancelled` 로만 나옴(grok N1·codex R1), 원격 러너의 마감 문구 `deadline_timeout (remote runner)` 가 완전 일치 비교에 걸려 일반 실패로 집계됨(codex R2), 귀속되지 않은 열린 잡에 판정 규칙 문장이 없음(claude K1) → 8a9101e·e80186f 로 정정(`get_feedback(events=…)`, 사유의 첫 단어로 종류 판정, `STRAY_RULE`; 회귀 T13·T14·T6 은 정정을 되돌리면 실패). 델타 리뷰 @e80186f claude·agy·grok·codex **4인 전원 PASS**(identity 4건 모두 base ed293dc → head e80186f, evidence review-tree). codex 는 1회차에서 자손 502개 등록부의 `descendants_of` 조회가 약 0.01 초임을 실측. 리포트와 identity 사이드카는 `.agents/reports/<session>/`.
|
||
- **전체 스위트 (병합 워크스페이스, 자체 venv 가 있는 워크트리 @ed293dc)**: @ed293dc 1156 통과 / 3 skip / 실패 2(공개 브로커 `no CONNACK` 재시도로 11 초 타이밍 한계를 넘긴 flake 2건, 단독 재실행 통과; 1시간 1분). @e80186f 관련 스위트(job-tree·delegation 계약·transport·loop CLI·e2e·liveness·version) 125 + 54 통과.
|
||
- **비차단 후속**: `MAM_JOB_TREE_MAX` 초과 시 대기를 건너뛰고 리뷰로 가며 표에 50행 넘게 붙을 수 있음(K2); `target_not_found` 도 래퍼 rc 가 `session_busy` 와 같은 5 라 루프의 busy 1회 재시도가 없는 대상에도 돌아감(K3, rc 분리 권고); `descendants_of`/귀속되지 않은 잡 조회가 폴링(3 초)마다 등록부 전체를 읽음(K4, 등록부 정리 정책 전까지 정보); 귀속되지 않은 열린 잡 목록은 시간 기반 휴리스틱이라 다른 루프의 잡이 섞일 수 있음(codex).
|
||
- **범위 밖**: 멈춘 에이전트 감지(#30)와 잡 제한 시간 조절(#33). 하위 잡이 권한 확인 창에 멈추면 루프는 그 잡이 #33 의 예산으로 닫힐 때까지 기다린다.
|
||
|
||
### 🚀 `v4.7.3` — Issue #36 위임 프롬프트를 argv 가 아닌 파일로 전달, 위임 실패는 종료 코드 1 (2026-09-24, PR #37)
|
||
|
||
> **패치 마일스톤 (PATCH Release)**: 리뷰 루프가 diff 를 리뷰 프롬프트에 넣어 bash 에 **단일 argv 원소**로 넘기던 탓에, 프롬프트가 Linux 의 단일 인자 상한(`MAX_ARG_STRLEN` 131,072 바이트)을 넘으면 `OSError: [Errno 7]` 로 죽고 **종료 코드 0** 으로 끝나던 문제(Issue #36)를 고쳤다. 수집기 상한 `MAM_DIFF_MAX_BYTES`(기본 200000)가 커널 상한보다 높아 그 사이 크기는 걸러지지 않았고, 커밋 10여 개 규모의 브랜치(PR #35 가 실제로 133 KB)는 루프로 리뷰할 수 없었다. 옵션 하나(`--prompt-file`)가 늘지만 잘못된 전달 경로를 고치는 수정이라 PATCH 로 올린다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **프롬프트는 파일로 전달 (`lib_py/loop/runner.py`, delegate-job 래퍼, `lib_py/delegation/registry.py`·`cli.py`, `lib.sh`, `run_loop.sh`)**: Python 러너는 모든 위임 프롬프트를 제어 루트 `.mam/` 아래 임시 파일에 쓰고 래퍼에 `--prompt-file` 로 넘긴 뒤 래퍼가 끝나면 지운다. 래퍼와 등록부는 `--prompt`/`--prompt-file` 을 상호 배타로 받고, 래퍼는 받은 경로를 등록부에 그대로 넘기며 loop 형 반복의 `update` 도 `prompt.md` 파일로 넘긴다. `lib.sh` 의 제출 도우미(`delegate_submit_job` 계열)와 `cli.py` 사슬, 옛 셸 러너(`MAM_LOOP_PY=0`)의 위임 11곳이 거치는 `delegate_job_safe` 도 파일로 넘긴다(쓰기 실패 시 부분 파일을 지우고 stderr 로 진단). `create_session.sh` 는 주입 안내문에 과제 본문 대신 잡 디렉터리의 `brief.md` 절대경로만 넣는다(herdr 주입문도 argv 하나다). 기존 `--prompt` 는 그대로 받는다.
|
||
2. **위임 실패는 성공처럼 끝나지 않음 (`runner.py`)**: 래퍼를 실행할 수 없으면(OSError) rc 126 의 실패한 제출로 다루고 첫 시도·재시도 모두 원인을 남기며 루프는 rc 1 로 끝난다. 반론 이후의 재심·중재 잡이 등록되지 않은 경우와 순응 잡이 등록되지 않거나 끝나지 않은 경우도 판정이 아니라 실패(rc 1)다.
|
||
3. **수집기 상한의 의미 (`diff_collect.sh`)**: `MAM_DIFF_MAX_BYTES`·`MAM_DIFF_MAX_LINES` 는 리뷰어에게 보여줄 증거의 크기만 제한한다. 전달 자체에는 크기 한계가 없고, 잘린 diff 를 증거로 쓸 때의 기존 규칙(diff-only 원격 리뷰어 거부)은 그대로다.
|
||
4. **문서**: delegate-job SKILL.md(`--prompt-file`), loop SKILL.md.
|
||
|
||
#### ✅ 품질 검증
|
||
- **신규 테스트 22개** (`tests/test_issue36_prompt_transport.py`): 131,072 바이트를 넘는 실제 프롬프트가 실제 subprocess/exec 경로(러너 → 래퍼 → 등록부, `lib.sh` 도우미, 옛 셸 러너, `create_session`)를 지나는지, 임시 파일의 생성·정리·쓰기 실패 처리, 래퍼를 실행할 수 없을 때 rc 1 과 트레이스백 부재. 수정 전 코드에서는 13건 중 11건이 실패.
|
||
- **리뷰**: 작성 워크스페이스 5인 3라운드 전원 PASS(codex 1차: 반론 경로에서 잡 등록 실패 시 루프 계속 → 14d11bf; 오케스트레이터: 옛 셸 러너·`lib.sh` 도우미의 argv 잔존 → f26952c; codex 델타: strace 로 `create_session` 의 E2BIG 와 셸 러너의 부분 파일 잔존 재현 → 59be1c8). 병합 워크스페이스에서 PR head 를 detached 워크트리에 받아 재검토: 1회차 @59be1c8 claude PASS(비차단 H1–H5), agy·grok·codex NOT PASS — 새 테스트 S9 가 stderr 의 리터럴 `File too large` 를 요구해 macOS bash 3.2 에서 실패(agy·grok·codex R2), Python 러너의 프롬프트 파일 **쓰기** 실패가 정리·오류 처리 범위 밖이라 부분 파일이 남고 예외가 루프까지 올라옴(claude H1·codex R1), 래퍼가 빈 `--prompt-file` 을 받아들임(claude H3) → 708262a 로 정정(쓰기 실패는 exec 실패와 같은 rc 126 실패 제출, libc 문구는 Linux 에서만 단언, 빈 파일 거부; 회귀 S4c·S1e 는 정정을 되돌리면 실패). 델타 리뷰 @708262a claude·agy·grok·codex **4인 전원 PASS**(identity 4건 모두 base 59be1c8 → head 708262a, evidence review-tree). codex 는 1회차에서 이 호스트의 `SC_ARG_MAX` 를 넘는 1,114,112 바이트 프롬프트가 argv 로는 `OSError(errno=7)` 이고 파일로는 공백 경로·다른 `--workdir` 조합에서도 원문 그대로 등록됨을 실측. 리포트와 identity 사이드카는 `.agents/reports/<session>/`.
|
||
- **전체 스위트 (병합 워크스페이스, 자체 venv 가 있는 워크트리 @59be1c8)**: @59be1c8 1139 통과 / 2 skip / 실패 1(S9, 위 R2; 54분 7초) → @708262a 관련 스위트(transport·delegation 계약·loop CLI·delivery ack·version) 125 통과 / 1 skip(Linux 전용 S1b).
|
||
- **비차단 후속**: `MAM_DIFF_MAX_*` 의 "증거 상한, 전달 무제한" 의미가 loop SKILL.md 에 없음(H2); 래퍼의 `$(<file)` 이 후행 개행을 잃어 등록부 프롬프트와 브리프 본문이 그 점에서 다를 수 있음(H4); 순응 잡의 실행 실패도 rc 1 로 끝내는 강화에서 로그 문구가 "Failed to register" 로 등록 실패와 구분되지 않음(grok·codex).
|
||
|
||
### 🚀 `v4.7.2` — Issue #23·#24 세션 생성 경로: 교차 서버 herdr 스코프, 에이전트 무관 시작 다이얼로그, 실제 에이전트가 충족하는 온보딩 전달 판정 (2026-09-23, PR #35)
|
||
|
||
> **패치 마일스톤 (PATCH Release)**: `create_session.sh` 로 새 세션을 만드는 첫 1분 안의 결함 둘을 고쳤다. 오케스트레이터 pane 이 물려주는 herdr 환경(`HERDR_WORKSPACE_ID`·`HERDR_PANE_ID`·`HERDR_TAB_ID`)이 격리 서버 조회를 오염시켜 온보딩 프롬프트가 전달되지 않고, 전달 판정이 화면 기준이라 살아 있는 에이전트를 실패로 판정한 뒤 롤백까지 실패하던 문제(#23)와, 시작 신뢰(trust) 다이얼로그를 claude 에게만 처리해 agy·grok·codex 가 준비 대기 초과로 온보딩을 건너뛰던 문제(#24)다. 옵션 하나(`--no-auto-trust`)와 종료 코드 둘(42·43)이 늘지만 잘못된 기본 동작을 고치는 수정이라 PATCH 로 올린다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **시작 다이얼로그가 에이전트 무관 (#24, `lib_py/agents/base.py: startup_dialogs`, 어댑터 4종, `lib.sh: handle_startup_dialogs`)**: 모달의 시그니처·수락 키·종류(`trust`/`notice`)를 어댑터가 제공하고(`MAM_STARTUP_DIALOGS`), TUI 준비 대기 중 감지되면 수락한다. agy(Enter)·grok(`y`)·codex(`1. Yes, continue` Enter)의 신뢰 창과 codex 0.155.1 업데이트 안내(Skip)를 처리한다. 수락 판정은 다이얼로그 시그니처가 아니라 준비 토큰으로 한다(`--source visible` 이 수락된 창을 한동안 계속 보여준다). `--no-auto-trust` / `MAM_AUTO_TRUST=0` 은 신뢰 창만 사람에게 남기고 안내는 계속 처리한다(codex 의 trust 는 프로젝트 로컬 설정·훅·exec 정책을 로드한다). resume 도 create 와 같은 스위치·계약이다.
|
||
2. **교차 서버 호출의 스코프 제거 (#23, `lib.sh`)**: herdr pane 이 내보내는 세 id 는 발급한 서버에서만 뜻이 있으므로 **다른 서버를 겨냥한 호출에서만** `env -u` 로 제거한다. 호출자 서버는 `HERDR_SESSION` 이 있으면 그것, 없으면 `HERDR_SOCKET_PATH` 의 모양에서 구한다(herdr 0.8.2 는 기본 서버 pane 에 `HERDR_SESSION` 을 내보내지 않는다).
|
||
3. **온보딩 전달 판정 = 에이전트 자신의 `started` (#23, `lib.sh: delegate_publish_cmd / delegate_started_recorded / delegate_wait_started`, `create_session.sh`, delegate-job 래퍼)**: create 는 더 이상 에이전트 대신 `started` 를 발행하지 않는다. 발행 명령 빌더 하나가 create 온보딩과 래퍼에 같은 모양(dispatch_token·attempt 포함)을 주고, `started` 판정 하나를 두 대기 지점이 공유한다. 이번 dispatch 의 `started` 만 증거이며(로컬: events.log 의 dispatch_token, 원격: 구독자가 HMAC·토큰 검증 뒤 감사 기록에 남기는 `dispatch_fingerprint`(검증한 토큰의 SHA-256)+attempt), 신원 없는 과거 기록은 fail closed 다. 래퍼의 "레코드 상태만 보고 먼저 성공" 우회는 사라졌다. send_keys 의 rc 4(Enter 수락 미관측)는 경고로 낮추고 에이전트의 `started` 가 결정한다.
|
||
4. **실패 처리 (#23, `create_session.sh`)**: 롤백이 kill 결과를 확인하고 `has-session` 으로 재확인한다. 못 죽였으면 행에 `rollback-failed` 를 적고 exit 43. 온보딩이 `MAM_ONBOARD_ACK_S`(기본: 래퍼의 주입 ack 유예 → 잡의 ack timeout 180) 안에 확인되지 않아도 세션은 보존하고 잡만 error 로 표시한다(살아 있는 에이전트를 느린 온보딩 때문에 죽이지 않는다; 이미 종료된 잡은 덮어쓰지 않는다). 롤백 시 온보딩 잡은 `cancelled`, YAML 행은 `terminated`/`rollback-failed` 로 표시한다. TUI 가 준비되지 않고 프로세스도 없으면 exit 42.
|
||
5. **문서**: create/resume SKILL.md(`--no-auto-trust`, `started` 기반 판정, exit 42·43, `MAM_ONBOARD_ACK_S`, 미확인 온보딩 시 세션 보존 규칙).
|
||
|
||
#### ✅ 품질 검증
|
||
- **신규 테스트** (`tests/test_issue23_24_startup_path.py` 1,268줄, `tests/test_issue24_resume_dialogs.py`): 어댑터 다이얼로그 facts, 준비 토큰 기반 수락, `--no-auto-trust`, 교차 서버 스코프 제거(같은 서버는 유지), 공유 `started` 판정(이전 시도·신원 없는 기록 거부, 종료된 잡 보존), 롤백의 kill 확인·exit 43, 미확인 온보딩 시 세션 보존. 픽스처는 실제 `registry.py`·`publish_event.py` 를 쓴다(작성 워크스페이스 리뷰에서 손으로 만든 픽스처가 결함 3건을 가렸던 교훈). 기존 테스트 6파일 조정(G9 가드는 코드만 읽음).
|
||
- **실제 herdr 수용 시험 (작성 워크스페이스)**: 오케스트레이터 pane 의 상속 환경을 그대로 둔 채 격리 서버에 agy·grok·codex·opencode 를 순차 생성 — 4건 모두 사람 개입 없이 TUI 준비, 온보딩 전달, `started`(dispatch_token 포함) → `completed`.
|
||
- **리뷰**: 작성 워크스페이스 5인 5라운드(차단 지적 3건 — `HERDR_SESSION` 에만 의존한 교차 서버 판정, `data.dispatch_token` 을 요구했으나 실제 발행 명령엔 없던 온보딩 ack, 래퍼의 레코드 상태 우회와 신원 없는 원격 감사 기록 — 모두 해소) 전원 PASS. 병합 워크스페이스에서 PR head 를 detached 워크트리에 받아 재검토: @f4ba205 claude·agy·grok PASS; codex 는 리뷰 도중 호스트가 반복 절전(pmset 로그)에 들어가 API 스트림이 끊기는 바람에 리포트를 남기지 못했고, 대화 기록에서 복원한 탐침 2건(환경변수 `MAM_AUTO_TRUST=0` 이 스크립트의 기본값 export 에 덮여 핸들러에 1 로 도달, 쿨다운에 걸린 다이얼로그가 같은 화면의 다음 다이얼로그를 가림)을 05b0ea4 로 정정. 델타 리뷰 @05b0ea4 claude·agy·grok·codex 4인 전원 PASS(identity 4건 모두 base f4ba205 → head 05b0ea4, evidence review-tree; codex 는 두 정정을 되돌린 사본에서 새 회귀가 실패함을 역검증). 리포트와 identity 사이드카는 `.agents/reports/<session>/`.
|
||
- **전체 스위트 (병합 워크스페이스, 자체 venv 가 있는 워크트리 @f4ba205)**: 1111 통과 / 1 skip / 실패 0 (51분 21초, 리뷰 루프와 병행); 05b0ea4 에서 startup-path·resume-dialogs·TUI readiness·headless reconcile·version 스위트 123 통과.
|
||
- **별건**: 리뷰 루프가 diff 를 단일 argv 로 넘겨 Linux 의 단일 인자 상한(131,072 바이트)을 넘는 브랜치 diff 에서 `OSError: [Errno 7]` 로 죽고 rc 0 으로 끝난다(수집기 상한 `MAM_DIFF_MAX_BYTES` 기본 200000 이 더 높다). 작성 측이 별도 이슈로 등록. 비차단: 감사 기록의 새 필드(`dispatch_verified`·`dispatch_fingerprint`·`attempt_id`)가 MESSAGING.md·delegate-job SKILL.md 에 없음, `MAM_INJECT_ACK_GRACE_S` 가 create 의 대기에도 2순위로 작용한다는 점이 문서에 없음, `MAM_AUTO_TRUST` 는 `0` 만 끈다는 점과 플래그 우선 규칙이 resume SKILL.md 에 없음, codex 업데이트 안내에 `2`(이번만 건너뛰기)를 고르므로 다음 시작 때 다시 뜸(`3` 이면 다음 버전까지 조용함), 다이얼로그 시그니처는 화면의 디렉터리 경로를 검사하지 않음(pane 이 `-c $WORKSPACE` 로 뜨므로 실무 위험 낮음), `test_issue24_resume_dialogs.py` 4건이 각 ~40 s.
|
||
|
||
### 🚀 `v4.7.1` — Issue #25 승격된 계획서 재사용은 `--plan-file` 로만: 암묵적 자동 로드 제거, 빈 계획 리포트는 조용히 넘어가지 않음 (2026-09-22, PR #32)
|
||
|
||
> **패치 마일스톤 (PATCH Release)**: `run_loop.sh` 를 `--plan` 없이 실행하면 러너가 planner 를 자동으로 고르고 `.agents/reports/<planner>/plan-*.md` 중 mtime 이 가장 최근인 파일을 "이미 수립된 계획서"로 creator 에게 넘기던 문제(Issue #25)를 고쳤다. 저장소에는 다른 이슈의 계획서가 수십 개 커밋돼 있고 체크아웃 직후의 mtime 은 의미가 없어, 무관한 계획이 구현 프롬프트에 실렸다(v4.7.0 병합 루프에서도 Issue #6 계획서가 실린 것을 관측). 옵션이 하나 늘지만 잘못된 기본 동작을 없애는 수정이라 PATCH 로 올린다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **암묵적 계획 로드 제거 (`lib_py/loop/runner.py`, `multi-agent-mux-loop/scripts/run_loop.sh`)**: `--plan` 없는 실행은 어떤 계획서도 읽지 않는다. Python 러너와 레거시 셸 러너(`MAM_LOOP_PY=0`) 모두에서 mtime 기반 선택을 지웠고, creator 는 스스로 계획한다. 체크포인트의 `current_plan` 은 쓰기 전용이라 옛 계획을 되살리는 경로가 없다.
|
||
2. **`--plan-file <path>` (Python 러너 전용)**: 승격된 계획서의 재사용은 파일을 명시할 때만 한다. 상대경로는 **제어 루트 기준**(워크트리에서 돌려도 같음)이고, 잡이 하나도 등록되기 전에 읽어 검사한다(없는 파일·디렉터리·빈 파일·읽기 실패는 exit 1). `--plan` 과 함께 쓸 수 없고, `--review-only` 에서는 경고 후 무시한다. 레거시 셸 러너는 이 옵션을 루프 락 전에 명시적으로 거부한다.
|
||
3. **계획 텍스트 검사 하나를 모든 로드 경로에 (`LoopRunner._plan_text`)**: planner 의 첫 리포트가 비어 있으면 루프를 멈춘다(계획 단계에 비용을 쓰고도 조용히 자율 계획으로 강등되던 문제). 토론(refine)·재계획 리포트가 비어 있으면 경고하고 기존 계획을 유지한다.
|
||
4. **문서**: loop SKILL.md 의 옵션 표·Workflow·"계획서가 비어 있을 때" 절.
|
||
|
||
#### ✅ 품질 검증
|
||
- **신규 테스트 17개** (`tests/test_issue25_plan_reuse.py`, P1–P11): 옵션 없는 실행이 승격된 계획서를 읽지 않음(Python·셸), `--plan-file` 의 제어 루트 기준 해석·실패 경로·`--plan`/`--review-only` 조합·셸 러너의 거부, 빈 planner 리포트의 중단, 빈 refine 의 기존 계획 유지. 병합 워크스페이스 리뷰에서 17건 중 13건이 수정 전 소스에서 실패함을 확인.
|
||
- **리뷰**: 작성 워크스페이스 루프(`--worktree --base --review-only`) 1회차 @5fa5923 claude·agy·opencode PASS, grok·codex NOT PASS(상대경로 `--plan-file` 이 실행 cwd 기준으로 해석) → 1e82651 로 정정, 2회차 5인 전원 PASS. 병합 워크스페이스에서 PR head 를 detached 워크트리에 받아 같은 방식으로 재검토: @1e82651 claude·agy·grok·codex 4인 전원 PASS(1회차, identity 4건 모두 base 6fc9194 → head 1e82651, evidence review-tree). 리포트와 identity 사이드카는 `.agents/reports/<session>/`.
|
||
- **전체 스위트 (병합 워크스페이스, 자체 venv 가 있는 워크트리 @1e82651)**: 1037 통과 / 1 skip / 실패 0 (35분 26초, 리뷰 루프와 병행).
|
||
- **비차단 후속**: 레거시 셸 러너의 plan 모드 읽기에는 빈 계획서 검사가 없음(E1), `--plan-file` 실행에서 `[ESCALATE: PLANNER]` 가 재계획으로 이어지지 않음(E2, 기존 게이트), 리뷰 격리 워크트리에 `.venv` 가 없어 `run_loop.sh` 를 하위 프로세스로 띄우는 테스트는 `AGENT_PYTHON_BIN` 없이는 실패(E3, #21 항목 5 운영 후속), Phase 1 의 `getmtime` 경합(E4), 공백이 든 경로 전용 테스트 없음.
|
||
|
||
### 🚀 `v4.7.0` — Issue #21 워크트리 운영 모델 2단계: 워크트리 헬퍼, 리뷰 identity, 수용 사례 A1–A8, #20 테스트 격리, #27, #28 호스트 이식성 (2026-09-20, PR #29)
|
||
|
||
> **마이너 마일스톤 (MINOR Release)**: 워크트리 운영 모델(#21, `docs/design/worktree-creator-model.md`)의 작업 항목 4·5·7 을 구현했다. 이슈별 워크트리를 만들고 지우는 헬퍼 2개, 리뷰 라운드를 `(loop_id, base, head, tree)` 에 묶는 리뷰 identity 와 루프 옵션 2개(`--review-isolation`, `--remote-review`), 수용 사례 8건이 추가되어 MINOR 로 올린다. 항목 8(다중 루프·리뷰어 스케줄러)은 보류로 남아 #21 은 열어 둔다. 함께 #20(테스트가 실제 `.mam` 을 오염), #27(`.gitignore` 관리 블록의 `.venv`), #28(Linux/py3.14 호스트의 테스트 실패 33건)을 닫는다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **워크트리 준비·해체 헬퍼 (항목 4, `multi-agent-mux-create/scripts/worktree_prepare.sh`, `multi-agent-mux-stop/scripts/worktree_remove.sh`)**: prepare 는 제어 루트 옆에 워크트리를 멱등하게 만들고(안쪽 경로는 거부), 워크트리 **자체의** `.venv` 를 제어 루트의 `.venv/bin/python` 으로 만들어 `requirements*.txt` 를 설치하며(심링크는 만들지 않고 남아 있으면 exit 1 — 링크가 `git add -A` 로 커밋된 사고의 재발 방지), `/.venv`·`.pytest_cache/` 를 공유 `info/exclude` 에 넣고, 서브모듈은 `--submodules` 로만 초기화한다(실패한 시도는 사용 가능한 체크아웃으로 롤백). `--creator-agent` 는 제어 루트 바인딩과 `MAM_NO_SUBSCRIBER_DAEMON=1` 로 creator 세션을 만들고 `BASE=<sha>` 를 출력한다. remove 는 잠금·실행 중 세션(`--stop`)·열린 잡·수정(untracked 포함)·서브모듈(미push 커밋: HEAD 와 tag·notes 를 포함한 모든 로컬 ref, stash, deinit 된 저장소, 부분 저장소) 을 **아무것도 바꾸기 전에** 판정하고, `--stop` 뒤 제거 직전에 한 번 더 판정한 뒤, 서브모듈 상태가 있는 워크트리만 검증된 단일 `--force` 로 지운다.
|
||
2. **resume 계약 (항목 4, `multi-agent-mux-resume`)**: 세션은 기록된 원래 cwd 에서만 재개한다(다른 경로는 exit 2, cwd 없는 기존 행은 호환). 이미 실행 중 경로에서도 제어 평면 바인딩을 영속화하고, 워크트리 바인딩 계산 실패는 삼키지 않고 exit 2 로 알린다. create 는 즉시 reconcile·watchdog 을 제어 루트의 코드로 띄운다.
|
||
3. **리뷰 identity (항목 5, `lib_py/loop/runner.py`, `run_loop.sh --review-isolation worktree|env`, `--remote-review refuse|diff-only`)**: `--worktree`/`--base` 에서 매 리뷰 라운드 전에 소스의 미커밋·untracked 상태까지 담은 ref 없는 스냅샷 커밋을 만들고, 리뷰어의 diff 도 `base..snapshot` 에서 만들며, 기본은 스냅샷의 detached 리뷰 워크트리에서 검증한다. 라운드가 끝난 뒤 소스가 바뀌었으면 PASS 를 버리고 다시 리뷰하고(마지막 반복이면 exit 1), 승격 직전에도 재확인하며, 리포트 옆에 `report-<jid>.identity.json`(loop_id·base·head·tree·스냅샷·리뷰어·transport·evidence)을 둔다. 추적되지 않는 중첩 저장소와 dirty 서브모듈(`--ignore-submodules=none --untracked-files=all`, 어떤 `submodule.*.ignore`·`diff.ignoreSubmodules`·`status.showUntrackedFiles` 설정도 숨기지 못함)은 거부한다. 원격 리뷰어는 이 호스트의 리뷰 트리에 닿을 수 없으므로 어떤 잡도 위임하기 전에 거부하거나 `diff-only` 로만 받는다. 옵션이 없으면 이전 동작 그대로다.
|
||
4. **테스트 격리 (#20, `tests/conftest.py`)**: 수집 전에 ambient 제어 평면 바인딩을 기록·제거하고 테스트마다 레지스트리·감사 로그 기본 루트를 tmp 로 보낸다. 기록된 제어 평면의 `.mam/jobs`·`.mam/delegate_job_logs` 에 새 항목이 생기면 세션 가드가 실패시키고, 기본 해석 규칙을 검증하는 #15 테스트는 명시적으로 옵트아웃한다. `MAM_ENV_FILE` 은 존재하지 않는 경로로 고정해 실 `.mam.env` 를 읽지 않는다.
|
||
5. **호스트 이식성 (#28)**: `tests/test_workspace_scope.py` 는 `bash -c` 로 실행(dash 에 `source` 없음), argparse 도움말 비교는 `_normalize_help` 로 py3.10+ 의 "options:" 를 흡수, `test_deploy_freshness.py` 의 private-server 문서·docker 자산 의존 테스트는 자산이 없으면 skip, `lib_py/agents/base.py`·`lib_py/delegation/heartbeat.py` 의 프로세스 스캔은 POSIX `ps -eo pid=,ppid=,args=` 로 통일(BSD 전용 인자가 Linux 에서 purge 12건을 깨뜨리던 원인 가설). `test_issue14_delivery_ack.py` 의 벽시계 close 여유는 `timeout_s + 15`.
|
||
6. **`.gitignore` 관리 블록 (#27, `deploy/install.sh`, `deploy/install_mam.sh`)**: `.venv` 를 파일·디렉터리·링크 어느 형태로도 무시한다(재설치는 옛 규칙만 바꾸고 사용자 줄은 보존).
|
||
7. **문서**: 설계 문서(항목 4·5·7 상태, 서브모듈 제거 규칙, resume cwd 규칙, 루트 3종·리뷰 identity), create/stop/resume/loop SKILL.md, 호환성 표.
|
||
|
||
#### ✅ 품질 검증
|
||
- **신규 테스트** (`tests/test_issue21_phase2_loop_a.py`, `tests/test_issue21_loop_b.py`, `tests/test_issue21_acceptance.py`(A1–A8), `tests/test_issue20_state_isolation.py`, `tests/test_issue21_worktree_prereqs.py` 확장): prepare 의 멱등성·거부·dry-run·venv(생성/유지/링크 거부/인터프리터 부재)·서브모듈 롤백, remove 의 R1–R6(종료 중 쓰기, 서브모듈 stash, 부분 init 롤백, 기존 파일, deinit 저장소, tag 로만 보존한 커밋)과 `status.showUntrackedFiles=no` 로 숨긴 untracked 파일, 리뷰 identity 의 스냅샷·stale·승격 재확인·격리 실패·원격 거부·dirty 서브모듈(숨김 설정 포함), #20 가드. 수용 사례 A1–A8 은 mock herdr 기준이며 실제 pane 의 env 값 확인은 단계 C 로 남긴다.
|
||
- **리뷰 (병합 워크스페이스, PR head 를 detached 워크트리에 받아 4인 독립 검토)**: @46ea4cc claude·agy·grok PASS, codex NOT PASS(R1 `status.showUntrackedFiles=no` 가 숨긴 서브모듈 untracked 파일이 force 제거를 통과, R2 같은 결함으로 리뷰 스냅샷이 미포착 내용을 승인, R3 prepare 의 `.venv` 심링크) → 08db1da 로 정정. 델타 리뷰 @08db1da claude·agy·grok PASS(비차단 D1–D3 → 80a5614 반영), codex 는 루프의 프롬프트 주입 실패(잡 14c7dc38, `started` 없음)로 직접 재위임해 @80a5614 PASS. 리포트 8건은 `.agents/reports/<session>/report-<jid>.md`(291e38b5·f27519ff·5f1486ee·b880c1f2·f1174fc0·d9275506·19d56cde·f6b21a0d).
|
||
- **전체 스위트 (병합 워크스페이스)**: @46ea4cc 1013 통과 / 1 skip / 실패 1건(부하 시 벽시계 경계, 90a289d 로 여유 확대). @08db1da(자체 venv 가 없는 워크트리에서 실행) 1015 통과 / 1 skip / 4 실패 — 4건 모두 워크트리의 `.venv` 부재로 스크립트가 system python(paho 없음)을 잡은 탓이며, 워크트리 자체 venv 를 만든 뒤 4건 모두 통과(80a5614 가 prepare 의 설치 목록에 delegate-job requirements 를 넣은 동기).
|
||
- **#28 후속**: Linux/py3.14 호스트에서 `ps -eo` 수정이 purge 12건을 실제로 고치는지 확인이 필요하다(가설 단계).
|
||
|
||
### 🚀 `v4.6.0` — Issue #21 워크트리 운영 모델 1단계 선행 작업: 래퍼 절대경로, 루프 `--worktree`, 제어 평면 바인딩, 설계 문서 (2026-09-14, PR #22)
|
||
|
||
> **마이너 마일스톤 (MINOR Release)**: "워크스페이스 = 체크아웃 경로" 가정을 느슨하게 하는 1단계다. creator 는 이슈별 git 워크트리에서, 리뷰어 풀은 본 체크아웃에서, 잡 브리프·레지스트리·리포트·체크포인트(제어 평면)는 본 체크아웃 하나에 두는 운영 모델(#21, `docs/design/worktree-creator-model.md`)의 선행 조건 4건(작업 항목 1·2·3·6)을 구현했다. 루프 러너 옵션 3개와 `lib_py/control_plane.py` 가 추가되어 MINOR 로 올린다. 옵션 없이 쓰면 종전 동작과 프롬프트가 바이트 단위로 같다. 항목 4(워크트리 준비/종료 헬퍼)·5(리뷰 identity)·7(시험 운영)·8(다중 루프)은 #21 에 남아 있다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **래퍼 절대경로화 (`multi-agent-mux-delegate-job`)**: 주입 문구의 brief 경로, 시작/성공/오류 명령의 `--registry-dir`, 감사 로그 루트(`DELEGATE_JOB_LOGS_DIR`), 브리프의 Output Report Path 가 `$REGISTRY_DIR` 기준 절대경로가 됐다(`_abs_registry_dir`·`_pub_cmd`·`_instructions`, `printf %q` 인용). 워크트리 cwd 의 creator 가 `started` 를 발행하고 리포트를 본 체크아웃의 잡 디렉터리에 쓴다.
|
||
2. **루프 러너 `--worktree <path>` / `--base <ref>` / `--review-only` (`lib_py/loop/runner.py`, `run_loop.sh`)**: diff 수집·`base_commit`·head 는 워크트리 기준, wrapper·레지스트리(`MAM_JOBS_DIR` 존중)·체크포인트·리포트 승격은 본 체크아웃 기준. 워크트리는 같은 저장소의 top-level 이어야 하고 `--base` 는 head 의 조상이어야 한다. 위임 프롬프트 앞에 `[워크트리 컨텍스트]`(소스 경로·브랜치·base·head·커밋 수·미커밋 여부·제어 평면) 블록을 붙인다. 변경 집합이 비면 fail-closed. `MAM_LOOP_WORKTREE` env 동치.
|
||
3. **제어 평면 바인딩 (`lib_py/control_plane.py`, create/resume/stop/status/reconcile)**: `WORKSPACE_ROOT`/`AGENT_SESSIONS_YAML`/`MAM_JOBS_DIR`/`DELEGATE_JOB_LOGS_DIR` 4개 경로를 세션 행의 `control_plane` 필드로 영속화하고, `--workspace` 만으로 유도되는 기본값과 다를 때만 pane 생성 호출의 `--env` 로 전파한다(재사용 idle pane 에는 env 를 에이전트 인자로 넘기지 않으므로 env 가 필요하면 pane 을 새로 만든다 — h25 계약 갱신). 상대 `MAM_JOBS_DIR` 은 jobs dir 하나로 정규화, stop 은 감사 로그 루트를 유지, status/reconcile 은 `MAM_JOBS_DIR` 을 존중한다. 경로에 공백이 있으면 전파를 거부(rc 3)한다.
|
||
4. **설계 문서 정정 (`docs/design/worktree-creator-model.md`)**: 4인 리뷰(#21 댓글, `docs/design/reviews/`)를 반영해 "현재 코드 기준" 을 부분 지원 + 필요한 수정으로, 1단계 리뷰 전용 흐름, 스킬별 호환성 표, 루트 3종(제어/소스/런타임)과 리뷰 identity 를 명시했다.
|
||
|
||
#### ✅ 품질 검증
|
||
- **신규 테스트**: `tests/test_issue21_worktree_prereqs.py`(래퍼 절대경로·러너 옵션·워크트리 검증·제어 평면 바인딩·전파·호환성), `tests/test_herdr_shim_contract.py` h25 갱신, T2-8 샌드박스화.
|
||
- **리뷰**: 작성 워크스페이스 루프에서 claude·agy·grok PASS @13259ed, codex PASS @4a81d15; 병합 워크스페이스에서 PR head 를 detached 워크트리에 받아 claude·agy·codex 재검토 PASS @4a81d15(리포트 `.agents/reports/`). 병합 워크스페이스 전체 스위트 @4a81d15: 940 통과, 실패 1건은 부하 시 경계값에 걸리는 시간 판정(단독 재실행 통과).
|
||
- **후속(비차단, #21·#20)**: create/resume 의 `mam_control_plane_json … || true` 가 실패를 삼킴 → 경고/exit 2; resume 의 "herdr 이미 살아 있음" 경로가 `CONTROL_PLANE_JSON` 미전달; T2-6 의 `os.environ` 직접 변경; issue14 `_job_for_inprocess_publish` 가 ambient `WORKSPACE_ROOT` 로 실제 감사 로그에 기록; acceptance5 가 공용 브로커에서 동시 실행 시 같은 세션 이름의 러너가 서로의 dispatch 를 소비(세션 이름 uuid4 화 필요); create 레시피에 `MAM_NO_SUBSCRIBER_DAEMON=1`.
|
||
|
||
### 🚀 `v4.5.0` — Issue #18 생존·전달 판정을 프로토콜 계약으로 완결: heartbeat 자동화, K×N 무응답, T 내 `started`, retained 상태, 원격 전송 (2026-09-13)
|
||
|
||
> **마이너 마일스톤 (MINOR Release)**: 에이전트의 생존·전달 판정을 TUI 스크래핑(herdr `agent_status`, pane 문자열, 화면 변화)에서 떼어내어, 에이전트가 스스로 발행하는 이벤트(`started`/`progress`/`completed`/`error`)에 **의무·주기·타임아웃 규칙이 있는 계약**을 부여했다(Issue #18, #17 항목 1). 새 이벤트 종류는 없다. 원격 호스트의 에이전트(pane·디스크 공유 없음)를 같은 계약으로 다루는 `remote:` 전송을 추가했다. 래퍼 옵션 3개(`--heartbeat`, `--heartbeat-misses`, `--ack-timeout`)와 `publish_event --heartbeat`, `job_subscriber --remote`, `remote.py` CLI 가 추가되어 MINOR 로 올린다. 기본값(N=30, K=4 → idle 120 s, T=180)은 종전 idle 기본값과 같으므로 기존 호출은 동작이 유지된다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **heartbeat 의무의 자동화 (`lib_py/delegation/heartbeat.py`, `publish_event.py --heartbeat N`)**: `started --heartbeat N` 이 분리 세션의 타이머를 띄워 N초마다 `progress`(detail `heartbeat`)를 같은 `dispatch_token`/`attempt_id` 로 서명·발행한다. 터미널 이벤트는 먼저 attempt 범위의 barrier(`<registry>/<job>.heartbeat.barrier`, seq 를 떼는 레지스트리 lock 안에서 기록)를 세우고, pidfile(`<registry>/<job>.heartbeat.pid`)로 타이머를 멈춘 뒤 진행 중인 beat 가 끝날 때까지 기다린 다음(`MAM_HEARTBEAT_DRAIN_S`, 기본 1 s 뒤 강제 종료 — 와이어에 남은 beat 는 취소되며 터미널을 붙잡아 두지 않는다; pid 재사용 대비 cmdline 확인) 자기 seq 를 뗀다. beat 는 같은 lock 안에서 barrier·터미널 레코드를 확인해 거부되므로 터미널 뒤에는 heartbeat 가 디스크에도 와이어에도 없고, 늦은 비터미널 이벤트의 retained state 가 터미널 스냅샷을 `running` 으로 덮지 않는다(리뷰 2회차 codex R3). 같은 잡에 `started --heartbeat` 를 다시 보내 타이머를 교체할 때도 이전 타이머를 같은 방식으로 drain·강제 종료한 뒤에야 pidfile 을 넘기므로, 이미 admission 을 통과해 와이어 직전에 있던 beat 가 터미널 뒤에 나가지 않는다(리뷰 3회차 codex R3). 타이머의 생성·교체·정지는 잡별 lifecycle lock(`<registry>/<job>.heartbeat.lock`, seq lock 과 별개)으로 직렬화되어 동시 `started --heartbeat` 도 추적되는 소유자 하나만 남기고, 끝난 attempt 에는 타이머를 arm 하지 않는다. 종료하는 타이머는 그 lock 을 기다리지 않으며(바쁘면 소유 연산이 pidfile 을 치운다), spawn 뒤 pidfile 기록이 실패하면 새 타이머를 곧바로 종료·회수한다(리뷰 4·5회차 codex). 타이머는 레코드 터미널·재dispatch·`timeout_sec` 초과·**감시 대상 에이전트 프로세스 소멸**(`--watch-pid`, 기본은 시작 명령을 실행한 가장 가까운 에이전트 CLI 조상; 원격 러너는 자기 자신) 시 스스로 끝난다. 죽은 에이전트는 heartbeat 를 멈추고 K×N 뒤 닫히며, 살아 있지만 멈춘 에이전트는 계속 heartbeat 를 낸다(프로세스 생존 계약). 각 beat 는 와이어 이전에 events.log 에 기록되므로 디스크 생존 판정에도 그대로 쓰인다. 래퍼가 주입하는 시작 명령이 `--heartbeat N` 을 포함하고, 브리프에 계약(N/K/T)이 명시된다.
|
||
2. **무응답 판정 = 마지막 이벤트로부터 K×N (`registry.py`, 래퍼 `finalize_unfinished`)**: 레지스트리 레코드에 `heartbeat_sec`·`heartbeat_misses`·`ack_timeout_sec`·`transport` 가 추가되고 `idle_timeout_sec` 은 명시하지 않으면 K×N 으로 유도된다. 래퍼의 R1 유예는 events.log 의 최근 이벤트(K×N 이내)만을 증거로 삼고, herdr 상태·pane busy 토큰은 진입 시 한 번 `liveness: ... (diagnostic only)` 로 기록만 한다. 헤드리스 에이전트(항상 idle, 빈 pane)도 heartbeat 만으로 K×N 을 넘겨 살아 있고, heartbeat 가 끊기면 `deadline_timeout` 으로 닫히며 이후 `completed` 는 CAS 가 거부한다.
|
||
3. **전달 확인 = T 내 `started` (`_wait_started`, `lib.sh: _sks_job_started`)**: 주입 후 T초(`--ack-timeout`, 기본 180 — 실제 에이전트는 브리프를 읽고 나서 시작 명령을 실행하므로 여유 있게; 벽시계 상한) 안에 레지스트리가 `pending` 을 벗어나거나 events.log(이번 attempt 의 dispatch_token 을 가진 `started` 만)/구독자 로그에 `started` 가 없으면 rc 1·`agent_blocked` 로 fail-closed 하고 재주입하지 않는다. `send_keys_safe` 는 accepted-but-unconfirmed 제출을 `agent_status working` 또는 job 의 `started` 로만 확인하며 pane 변화·busy 토큰 신호는 제거됐다(무신호 rc 4 는 유지). `MAM_INJECT_ACK_GRACE_S` 는 T 의 override 로 남는다.
|
||
4. **와이어 내구성 (`publish_event.py: build_state_payload`, `job_subscriber.py`)**: 모든 이벤트 뒤에 같은 연결로 retained `<prefix>/state`(status·last_event·seq, HMAC 서명)를 발행한다. 구독자는 state 토픽도 구독해 (재)접속 즉시 터미널 상태를 복구하고, 이벤트, 그리고 지금까지 받은 어떤 이벤트·state 보다 seq 가 앞선 state 로만 idle 창을 리셋하며(재접속 때 다시 오는 스냅샷이나 더 오래된 state 는 터미널 복구에만 쓰고 생존으로 치지 않는다 — 리뷰 2회차 codex R4), seq 점프를 감사 로그 `seq_gap` 으로 남기고, 재접속을 `reconnected` 로 기록한다. `--no-state` 로 끌 수 있다.
|
||
5. **원격 전송 (`lib_py/delegation/remote.py`, `scripts/remote.py`, 래퍼 `remote:<name>`)**: `--agent-session remote:<name>` 이면 preflight·idle 핸드셰이크·pane 주입을 건너뛰고 `MAM_DISPATCH_SECRET` 로 서명한 retained `<prefix>/dispatch`(브리프·auth_token·dispatch_token·attempt_id·계약)를 발행한 뒤 T 내 `started` 를 기다린다. 다른 호스트의 `remote.py run --session <name> [--exec CMD] [--once]` 러너가 자기 레지스트리에 같은 id·identity 로 잡을 미러링하고, dispatch 를 retire 하고, `started --heartbeat N` 후 명령을 실행해 터미널 이벤트를 발행한다. 구독자의 `--remote`(transport=remote 잡은 자동)가 `started`·터미널을 오케스트레이터 레지스트리에 반영한다. 서명 없는 dispatch 는 거부된다. 러너는 dispatch 를 레지스트리 lock 안에서 받아들인다: 같은 토큰은 멱등(끝난 attempt 는 다시 돌리지 않음), 더 낮은 attempt 나 같은 attempt 의 다른 토큰은 레코드·events.log 를 건드리지 않고 거부, 더 새 attempt 만 교체한다(`register_job(supersede_older_attempt=True)`, events.log 는 보존). `--exec` 명령은 자기 세션(프로세스 그룹)에서 돌고, `timeout_sec` 이나 러너 중단(SIGTERM/SIGINT) 때 그룹 전체를 TERM→KILL 로 끝낸 뒤 오류를 발행한다(리뷰 2회차 codex R1·R2). 원격 행은 기본 Python 루프 러너에서만 지원된다(레거시 `MAM_LOOP_PY=0` 은 `herdr:` 전용). 루프 러너는 YAML 행의 `transport: remote` 를 `remote:<name>` 으로 넘기고, `MAM_HEARTBEAT_SEC`/`MAM_HEARTBEAT_MISSES`/`MAM_ACK_TIMEOUT_S` 를 계약 옵션으로 전달한다.
|
||
6. **문서**: MESSAGING.md §2.5 계약 표·원격 전송, delegate-job SKILL.md(옵션·종료 코드·프로토콜), registry.md(계약 필드), loop SKILL.md(환경변수·원격 행).
|
||
|
||
#### ✅ 품질 검증
|
||
- **신규 테스트 31개** (`tests/test_issue18_liveness_contract.py`): 레지스트리 계약 필드·idle 유도(2), 자동 heartbeat 디스크 기록·터미널 정지·재dispatch 정지·터미널 이벤트와의 조합 거부(3), retained state 서명·seq 갭·state 로 터미널 복구·위조/stale state 드롭(3), `started` 기반 전달 확인과 pane 신호 무시(2), 수용 기준 1·2·3(헤드리스 heartbeat 생존, T 내 미`started` fail-closed·단일 주입, heartbeat 소실 후 deadline_timeout·늦은 completed 거부), 수용 기준 5(브로커만 공유하는 원격 러너 시뮬레이션), 서명 없는 dispatch 거부, 루프 러너 계약·`remote:` 라벨. 리뷰 교정분 — 1회차: 감시 에이전트 프로세스 소멸 시 타이머 정지, 재사용된 pid 미신호, 이번 attempt 의 `started` 만 인정, 끝난 attempt 의 재전송 dispatch 무시; 2회차: 더 낮은·충돌 attempt 의 dispatch 거부와 레코드·로그 무롤백, 명령 타임아웃 시 프로세스 그룹 전체 종료, in-flight beat 가 터미널 뒤에 발행되지 않음, state 는 seq 가 앞설 때만 생존 갱신(단위·재전달 종단 2건); 3회차: 타이머 교체 시 이전 타이머가 터미널 뒤에 발행하지 않음; 4·5회차: 동시 `start_timer` 는 추적되는 소유자 하나만 남김, barrier 뒤 start 는 arm 안 함, idle 타이머의 정상 종료(drain 기한 전·SIGKILL 없음), spawn 전·후 실패 시 추적되지 않는 타이머 없음.
|
||
- **기존 테스트 조정** (`tests/test_issue14_delivery_ack.py`): busy 토큰·pane 변화 확인 테스트를 `started` 기반으로 뒤집고(수용 기준 6), R1 유예의 pane busy 토큰 테스트를 "pane 은 생존 증거가 아니다"로 전환. registry CLI 스냅샷·help 캡처(publish_event/job_subscriber)를 의도된 확장으로 갱신.
|
||
|
||
### 🚀 `v4.4.5` — Issue #19 구독 데몬 수명 계약: 샌드박스 옵트아웃·회수, 워크스페이스 소실 시 자기 종료, SIGTERM 전달 (2026-09-13)
|
||
|
||
> **패치 마일스톤 (PATCH Release)**: `create_session.sh` 가 워크스페이스마다 분리 세션으로 띄우는 구독 데몬(`reconcile.sh --subscribe --idle-timeout 0`)이 테스트 샌드박스에서 회수되지 않아 스위트 1회당 약 30개씩 쌓이던 문제(Issue #19, 실측 고아 프로세스 202개)를 해결했다. 데몬은 이제 자기 워크스페이스가 사라지면 스스로 끝나고 `SIGTERM` 에 즉시 응답하며, 샌드박스는 데몬을 띄우지 않고 띄워진 것은 회수한다. 공개 계약·종료 코드 변경은 없다(옵트아웃 환경변수 1개 추가).
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **`MAM_NO_SUBSCRIBER_DAEMON=1` 옵트아웃 (`lib_py/delegation/daemon.py`, `lib.sh: _start_watchdog_legacy`)**: 리터럴 `1` 일 때만 데몬을 새로 띄우지 않는다. 이미 도는 데몬의 PID 는 그대로 보고하므로 `start_watchdog`/`ensure_subscriber_daemon` 의 dedup 계약은 유지되고, 미기동 시 stderr 에 한 줄 남긴다(stdout 은 비움). 테스트 샌드박스(`mam_sandbox`)가 기본으로 켜고, 데몬 자체를 검증하는 테스트만 `0`/삭제로 되돌린다.
|
||
2. **데몬 자기 종료 (`reconcile.sh --subscribe`)**: MQTT 루프는 5초 주기 wake 마다 워크스페이스 디렉터리 존재와 `.mam/monitor.lock` 의 `(st_dev, st_ino)` 가 자신이 잡은 것과 같은지 확인하고, 사라졌거나 교체됐으면(`.mam/` 재생성 → 새 데몬이 새 lock 을 잡을 수 있는 상태) 정리 후 rc 0 으로 끝난다. 브로커 불가 폴링 폴백은 매 회 디렉터리와 lock 파일의 **존재만** 확인한다(python 자식이 끝나며 lock 을 놓았으므로 폴백 중 두 번째 데몬을 배제하지는 못한다 — 종전 동작). 삭제된 pytest 임시 디렉터리나 `rm -rf` 된 프로젝트를 바라보며 공개 브로커 구독을 영원히 유지하던 데몬이 사라진다.
|
||
3. **`SIGTERM`/`SIGINT` 즉시 전달 (`reconcile.sh`)**: python 구독자를 백그라운드 자식으로 두고 bash 가 `wait` 하도록 바꿔 시그널이 미뤄지지 않는다(종전엔 포그라운드 자식이 끝날 때까지 bash 가 시그널을 보류해 `SIGKILL` 만 통했다). 트랩이 자식에 `SIGTERM` 을 전달하고 rc 143 으로 끝나며(첫 백그라운드 자식 전의 시그널 포함), python 쪽 핸들러는 브로커 연결(`connect`/`loop_start`) 전에 설치되어 핸드셰이크·CONNACK 대기 중이나 구독 직후의 시그널도 이벤트를 깨워 `loop_stop`/`disconnect` 를 거친 뒤 rc 0 으로 끝난다(시그널을 받은 뒤에는 폴링 폴백으로 가지 않는다). 폴백 루프의 대기도 `sleep … & wait` 로 바꿔 시그널이 곧바로 처리된다(끊긴 대기의 `sleep` 자식은 남은 간격 안에 스스로 끝나고, 이미 돌고 있는 `--once` 조정 1회는 끝난 뒤 처리된다).
|
||
4. **샌드박스 회수·회귀 가드 (`tests/conftest.py`)**: `mam_sandbox` finalizer 가 argv 형태(`bash|sh <샌드박스>/…/reconcile.sh --subscribe`)로 선정한 데몬과 그 자식을 `SIGTERM`→`SIGKILL` 로 회수한다(브리프를 argv 에 실은 래퍼나 접두 충돌 형제 루트는 선정되지 않음). `ps -o args=` 는 argv 를 따옴표 없이 공백으로 이어 붙이므로 스크립트 경로는 공백 토큰이 아니라 셸 단어부터 첫 `/reconcile.sh --subscribe` 까지 통째로 잡는다 — 공백이 든 샌드박스·`--basetemp` 경로의 데몬도 선정되고, 공백이 든 후보는 실제 파일일 때만 인정해 뒤쪽 인자에 같은 낱말을 실은 bash 래퍼는 배제한다. 세션 스코프 `subscriber_daemon_leak_guard` 는 스위트 종료 시 basetemp 아래 데몬이 남아 있으면 회수한 뒤 실패시킨다(#15 레지스트리 가드와 같은 방식).
|
||
|
||
#### ✅ 품질 검증
|
||
- **신규 테스트 16개** (`tests/test_issue19_subscriber_daemon_lifecycle.py`): 샌드박스 기본값에서 두 helper 본체 모두 데몬 미기동(2), 옵트아웃 중에도 기존 데몬 PID 보고, 리터럴 `1` 만 인정, `create_session.sh` 샌드박스 실행 후 데몬 없음, 옵트아웃 해제 시 누수 재현 + 회수기 제거, 회수기 단위 검증·argv 선정 기준 검증, 공백이 든 경로의 argv 선정·실제 데몬 회수(2), 폴링 폴백의 lock 소실 종료·`SIGTERM` 즉시 종료(2, 브로커 불필요), CONNACK 대기 중 `SIGTERM` 정상 종료(응답 없는 로컬 리스너, 브로커 불필요), 첫 백그라운드 자식 전의 시그널에도 `_sub_stop` 트랩 rc 143(bash 3.2 `set -u`), MQTT 루프의 lock 교체 종료·`SIGTERM` 정상 종료(2, 브로커 미도달 시 skip).
|
||
- **기존 테스트 조정**: `tests/test_issue5_phase1_delegation.py` 의 watchdog/daemon 특성화 테스트가 `MAM_NO_SUBSCRIBER_DAEMON=0`(또는 delenv)으로 옵트아웃을 해제.
|
||
- **실측**: 전체 스위트(870 통과) 후 pytest basetemp 아래 `bash …/reconcile.sh --subscribe` 데몬 0건(argv 형태 기준; raw `pgrep -f` 는 브리프를 실은 루프 드라이버·래퍼도 매치하므로 판정에 쓰지 않음).
|
||
|
||
### 🚀 `v4.4.4` — Issue #12 레지스트리 비밀번호 제거, Issue #8 업데이트 스탬프 보존, Issue #7 형제 워크스페이스 패킹 (2026-09-13)
|
||
|
||
> **패치 마일스톤 (PATCH Release)**: 모든 서브에이전트가 읽을 수 있는 잡 레코드에 MQTT 브로커 비밀번호가 평문으로 복사되던 문제(Issue #12), `deploy/update.sh` 가 인스톨러가 새로 쓴 `version.txt`·`asset_hashes.txt` 를 업데이트 전 값으로 되돌리던 문제(Issue #8), 같은 cwd 의 첫 워크스페이스만 검사해 다섯 번째 에이전트부터 에이전트마다 워크스페이스를 새로 열던 문제(Issue #7)를 해결했다. 새 `registry.py scrub-secrets` 는 추가 서브커맨드이며 공개 계약·종료 코드 변경은 없다. 브로커 비밀번호는 이제 레코드가 아니라 환경(`MQTT_PASSWORD`, `.mam.env`)에서만 온다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **잡 레코드의 브로커 비밀번호 제거 (Issue #12, `mqtt_common.BrokerConfig.to_registry_block`, `registry.py`)**: `to_registry_block` 이 더 이상 `password` 를 레코드의 `broker` 블록에 넣지 않는다. `broker_config_from_job` 은 레코드 블록을 환경 설정 위에 겹치므로 접속 시 비밀번호는 `MQTT_PASSWORD`(`.mam.env`)에서 채워지고, `publish_event` 는 여전히 레코드만으로 접속한다. `_atomic_write_record` 는 쓰는 모든 레코드에서 `broker.password` 만 지우므로(다른 필드는 그대로) 이전 규칙으로 쓰인 레코드는 다음 쓰기 때 정리된다. `auth_token`·`dispatch_token` 은 에이전트 자신의 `publish_event` 가 서명과 attempt 고정에 쓰므로 레코드에 남긴다.
|
||
2. **`registry.py scrub-secrets [--dry-run]` 마이그레이션 (Issue #12)**: 레지스트리 lock 아래에서 **비밀번호 값이 실제로 저장된** 레코드만 골라 지우고 재기록한다. 이전 규칙이 무인증 모드에서도 남긴 `"password": null` 키는 비밀이 아니므로 대상도 보고도 아니며, 그 레코드의 다음 쓰기 때 `_atomic_write_record` 가 정리한다. `--dry-run` 은 대상 잡 목록만 출력하고 아무것도 바꾸지 않는다. `registry.md` 가 브로커 블록 규칙(비밀번호 비저장, 환경에서 복원, 토큰 유지 이유)을 문서화한다.
|
||
3. **업데이트 후 설치 스탬프 보존 (Issue #8, `deploy/update.sh`)**: 성공 경로가 업데이트 전 `asset_hashes.txt`·`version.txt` 를 되돌리지 않는다. 그 복원 탓에 `version.txt` 가 이전 커밋을 가리키고 `remove.sh` 가 갱신된 원본 파일을 "로컬 수정"으로 오판해 백업하던 문제가 사라진다. `install_state` 는 재설치가 쓰지 않은 키(예: `gitignore_created`)만 `key=value` 줄 단위로 병합하며(이미 있는 키는 건너뜀, 마지막 줄에 개행이 없어도 처리), 실패 경로(`restore_on_failure`)는 종전대로 세 파일을 모두 그대로 복원한다.
|
||
4. **같은 cwd 형제 워크스페이스 패킹 (Issue #7, `lib.sh` 쉼 `new-session`)**: 첫 번째로 일치한 pane 의 워크스페이스만 보던 탓에 가득 찬 첫 워크스페이스를 매번 overflow 로 판정해 에이전트마다 새 워크스페이스를 열던 문제(4 + 1 + 1)를 고쳤다. 이제 같은 cwd 의 모든 워크스페이스를 pane 순서대로 순회해 idle pane 이 있거나 2xK 그리드에 여유가 있는 첫 워크스페이스에 배치하고(overflow 인 후보는 다음 후보로 넘어감), 모두 가득 찼을 때만 새 워크스페이스를 만든다(4 + 2).
|
||
|
||
#### ✅ 품질 검증
|
||
- **신규 테스트 8개**: `tests/test_issue7_workspace_packing.py` 3개(여유 있는 형제 워크스페이스로 패킹, 모든 후보가 가득 찼을 때만 새 워크스페이스, 여유 있는 첫 워크스페이스 우선 — mock herdr 에 워크스페이스별 2x2 기하를 흉내 내는 opt-in `layout_geometry: "2x2"` 추가), `tests/test_issue8_update_state.py` 1개(실제 install→update 사이클에서 새 `version.txt`·해시 유지와 `install_state` 병합), `tests/test_issue12_registry_secrets.py` 4개(registry 블록에 비밀번호 없음, 등록된 레코드에 비밀번호가 없어도 환경 비밀번호로 접속, 레거시 레코드의 다음 쓰기 때 정리, `scrub-secrets` CLI 마이그레이션).
|
||
- **기존 테스트 조정**: registry CLI 계약 스냅샷(`tests/test_issue5_phase1_delegation.py`)과 도움말 캡처(`tests/fixtures/issue_5/phase1/help-registry.txt`)에 의도된 `scrub-secrets` 서브커맨드를 반영하고, G6 가드에 `_cmd_scrub_secrets` 를 선언된 예외로 추가.
|
||
- **실측 (커밋 `e629cc1`·위임 브리프 기록)**: 관련 스위트 325개와 전체 스위트(worktree 기준) 통과.
|
||
|
||
### 🚀 `v4.4.3` — Issue #10·#13 대화 purge 완결, Issue #11 메시지 정정, Issue #15 레지스트리 워크스페이스 고정 (2026-09-13)
|
||
|
||
> **패치 마일스톤 (PATCH Release)**: agy 대화 purge 가 resume 가능한 부속 파일을 남기던 문제(Issue #10), herdr 세션 행이 없는 오케스트레이터 대화를 어떤 스킬로도 지울 수 없던 문제(Issue #13), `orc_onboard.sh --remove` 의 "removeed" 오타(Issue #11), 호출자 cwd 를 따라가던 잡 레지스트리·감사 로그 때문에 pytest 스위트가 실제 `.mam/jobs` 에 테스트 레코드를 남기던 문제(Issue #15)를 해결했다. 새 서브커맨드·옵션은 모두 추가 기능이다. 레지스트리·감사 로그의 **기본** 경로는 `WORKSPACE_ROOT` 가 있으면 cwd 대신 그 워크스페이스를 따르며, 명시적 `--registry-dir` 와 `MAM_JOBS_DIR`/`DELEGATE_JOB_LOGS_DIR` 는 그대로 우선한다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **agy purge 부속 파일 완결 (Issue #10, `lib_py/agents/adapters/agy.py`)**: `purge_artifacts` 가 대화 DB·`brain/<uuid>` 에 더해 SQLite 부속 파일(`-wal`/`-shm`/`-journal` — DB 를 열 때마다 생기고 stop 경로의 ID 캡처도 이를 만들며, 체크포인트되지 않은 WAL 에는 대화 내용이 남을 수 있다), 제목 annotation(`annotations/<uuid>.pbtxt`), presence lock(`presence/<uuid>.lock`)까지 지운다. presence lock 은 `conversation_in_use` 가 거짓일 때만 지우고, 사용 중이면 남긴 채 경고한다. agy 의 `conversation_in_use` 는 DB 만이 아니라 부속 파일·annotation·presence lock 전부를 `lsof` 점유 검사에 넘기므로, uuid 를 어디에도 적지 않은 채 lock 만 열어 둔 보유자도 잡는다. DB 가 이미 사라지고 WAL·annotation·lock 만 남은 고아 상태도 `has_artifacts` 가 인식해 purge 대상이 된다. 새 `residual_artifacts()` 는 `~/.gemini/antigravity-cli` 아래에 그 uuid 를 이름에 가진 경로가 남아 있으면 보고한다. db+brain 만 있는 경우 2건을 돌려주는 어댑터 계약은 유지.
|
||
2. **오케스트레이터 대화 purge CLI (Issue #13, `python -m lib_py.agents purge <agent|auto> <uuid> [workspace]`)**: `orchestrator_uuids` 는 uuid 만 저장하므로 디스크에서 어댑터를 판별한다. `auto` 는 `agy → claude → hermes → grok → codex → opencode` 순으로(opencode 는 모든 uuid 에 대해 존재하는 공유 DB 라 마지막) 각 어댑터의 `verify_artifact`(revalidate 모드 — 오케스트레이터 대화는 워크스페이스 캐시에 없음)를 먼저, 그다음 uuid 가 들어간 artifact 경로의 존재를 확인한다. 살아 있는 프로세스가 그 uuid 를 참조하면 거부한다(`base.conversation_in_use`: ① 호출 환경의 세션 id — `CLAUDE_CODE_SESSION_ID`·`ANTIGRAVITY_CONVERSATION_ID`·`HERMES_SESSION_ID`·`GROK_SESSION_ID`·`OPENCODE_SESSION_ID` — 가 그 uuid 이면 사용 중, 즉 오케스트레이터가 자기 세션 안에서 자기 대화를 purge 하는 경우를 막는다(claude 는 transcript 를 열어 두지 않아 아래 두 검사로는 보이지 않는다). ② 프로세스 명령줄 검사에서 purge 자신의 헬퍼 사슬(python·셸·스크립트)만 빼되 **가장 가까운 에이전트 CLI 조상에서 멈추고**, 그 조상의 명령줄에 uuid 가 있으면 보유자로 센다. ③ `lsof` 로 artifact 점유 확인 — 어댑터가 넘긴 모든 경로(agy 는 DB·부속 파일·annotation·presence lock)). 결과는 JSON 한 줄(`agent`·`purged`·`residual`·`in_use`)이며 종료 코드는 0 purge 완료 / 4 사용 중이라 거부(아무것도 삭제하지 않음) / 5 해당 uuid 의 artifact 없음.
|
||
3. **`orc_onboard.sh --remove <uuid> --purge-conversation --yes [--agent <name>]` (Issue #13)**: 위 purge 를 **레지스트리 갱신 전에** 실행하므로 거부되면 등록이 바뀌지 않는다. `--yes` 가 없으면 경고 후 exit 4, 대화가 사용 중이면 exit 5(등록 유지), 디스크에 대화가 없으면 경고 후 등록만 해제, `--dry-run` 은 예정 작업만 출력, `--purge-conversation` 은 `--remove` 와 함께일 때만 허용(exit 2). 성공 시 purge 된 경로와 잔여(residual) 검사 결과를 출력한다.
|
||
4. **`orc_onboard.sh` 성공 메시지 과거형 (Issue #11)**: `add`/`remove` 를 `added`/`removed` 로 명시 활용해 "Successfully removeed" 를 없앴다.
|
||
5. **잡 레지스트리·감사 로그의 워크스페이스 고정 (Issue #15, `mqtt_common.default_registry_dir`·`get_logs_dir`)**: 기본 경로가 `MAM_JOBS_DIR`/`DELEGATE_JOB_LOGS_DIR` → `$WORKSPACE_ROOT/.mam/jobs`·`$WORKSPACE_ROOT/.mam/delegate_job_logs` → cwd 기준 `.mam/…` 순으로 정해진다. `registry.py`·`publish_event.py`·`service.py` 와 `create_session.sh`·`stop_session.sh` 가 같은 순서를 쓴다. 저장소 루트에서 돈 pytest 스위트(기본 온보딩 잡을 등록하는 `create_session.sh`)가 호출자 cwd 를 따라 실제 `.mam/jobs` 에 테스트 레코드 593건을 남기던 문제를 해소했고, 샌드박스 픽스처(`tests/conftest.py`)는 두 환경변수를 모두 고정한다.
|
||
|
||
#### ✅ 품질 검증
|
||
- **신규 테스트 26개**: `tests/test_issue10_13_purge.py` 19개(agy 부속 파일·annotation·lock 삭제, 사용 중 lock 보존, 존재한 것만 보고, 자기 계보를 뺀 in-use 판정, 비-에이전트 헬퍼 사슬만 제외하고 에이전트 CLI 조상은 검사, purge CLI 의 auto 판별·사용 중 거부·자기 에이전트 세션 안에서 거부·세션 id 환경변수 5종으로 거부·미해결 uuid, `orc_onboard` purge 의 `--yes` 요구·삭제 후 등록 해제·사용 중 거부·오케스트레이터 자기 세션에서 거부·artifact 없음에도 등록 해제·`--remove` 필수·dry-run 무변경, presence lock 만 연 보유자 거부, DB 가 없는 고아 부속 파일로 agy 해결·purge), `tests/test_issue15_registry_isolation.py` 7개(`MAM_JOBS_DIR` 우선, `WORKSPACE_ROOT` 추종, cwd 폴백, 로그 경로 동일 순서, CLI 기본값의 프로세스별 해석, 명시 env 로 온보딩 잡 샌드박스 유지, `WORKSPACE_ROOT` 만으로도 샌드박스 유지).
|
||
- **기존 테스트 조정**: O-13(`tests/test_orc_onboard.py`)이 정정된 메시지를 단정하고, R1 헬퍼·B-9 로그 경로 테스트와 `publish_event` 스냅샷(`tests/test_issue5_phase1_delegation.py`·`tests/test_tier1_unit.py`)이 워크스페이스 고정 계약에 맞게 조정됨.
|
||
- **실측 (커밋 `c82ea75`·위임 브리프 기록)**: 전체 스위트 839/839 통과, 스위트 실행 전후 실제 `.mam/jobs` 레코드 881건 불변.
|
||
|
||
### 🚀 `v4.4.2` — Issue #9 herdr 서버 환경 격리 및 Issue #14 프롬프트 전달 확인 보강 (2026-09-12)
|
||
|
||
> **패치 마일스톤 (PATCH Release)**: 에이전트 런타임(예: Claude Code 오케스트레이터) 안에서 herdr 서버가 처음 기동될 때 세션 식별 마커가 서버와 이후 모든 에이전트 pane 에 상속되던 문제(Issue #9)와, `working` 을 보고하지 않는 에이전트(herdr 0.9.0 의 codex)에게 정상 전달된 프롬프트를 `send_keys_safe` 가 실패로 오판해 잡을 `agent_blocked` 로 닫고 에이전트의 `started`/`completed` 를 거부하던 문제(Issue #14)를 해결했다. 신규 환경변수는 모두 기본값이 있는 추가 설정이며 공개 계약·종료 코드 변경은 없다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **herdr 서버 기동 env 마커 denylist (Issue #9, `lib.sh` 쉼)**: 쉼이 `herdr --session <name> server` 를 부트스트랩할 때 세션 식별 마커만 제거한 환경을 넘긴다 — `CLAUDECODE`, `CLAUDE_CODE_CHILD_SESSION`, `CLAUDE_CODE_SESSION_ID`, `CLAUDE_CODE_SESSION_ATTENDED`, `CLAUDE_CODE_ENTRYPOINT`, `CLAUDE_PID`, `CLAUDE_CODE_MESSAGING_TOKEN`, `CLAUDE_CODE_MESSAGING_SOCKET`, `CLAUDE_CODE_BRIDGE_SESSION_ID`, `ANTIGRAVITY_CONVERSATION_ID`, `HERMES_SESSION_ID`, `GROK_SESSION_ID`, `OPENCODE_SESSION_ID`. `CLAUDE_PROJECT_DIR`·`CLAUDE_CONFIG_DIR` 등 사용자 설정은 그대로 상속된다. 상속된 `CLAUDE_CODE_CHILD_SESSION` 때문에 서브에이전트 claude 의 transcript 저장이 꺼져 resume 이 불가능하던 증상을 해소.
|
||
2. **조회 경로 자동 기동 금지 `MAM_NO_AUTOSTART=1` (Issue #9)**: 이 값이 설정되면 쉼은 서버를 띄우지 않고 호출만 전달한다. `status.sh`·`reconcile.sh` 가 이를 export 하므로 read-only `multi-agent-mux-status` 조회가 더 이상 서버 데몬을 기동하지 않는다.
|
||
3. **쉼 `ls` 의 "no server running" 번역 (Issue #9)**: 서버가 내려가 있으면 쉼의 `ls` 가 `no server running (herdr session <name>)` 으로 보고하고, `reconcile.sh` 는 이를 빈 서버로 판정해 `herdr_confirmed` 를 유지한다(파싱 실패로 모든 서버의 drift 감지가 멈추는 회귀 방지). 번역은 `agent list` 가 **실패**하고 그 오류 봉투(stderr·stdout 어느 쪽이든)의 `error.code` 가 정확히 `server_not_running` 일 때만 적용한다. 성공한 목록에 같은 문자열이 들어 있으면(예: 에이전트 이름 `server_not_running-codex`) 번역하지 않으므로 살아 있는 서버가 빈 서버로 오판되어 drift A 로 전 세션이 종료 처리되지 않는다. 그 밖의 실패는 출력이 목록 파서에 닿기 전에 비-0 으로 끝나(`… state unknown`, reconcile 의 빈 서버 문구와 겹치지 않음) unknown(`herdr_confirmed=False`)으로 남는다 — stdout 으로 온 오류 문서도 더 이상 빈 목록으로 읽히지 않는다.
|
||
4. **accepted 제출의 전달 확인 `SKS_DELIVERY_GRACE_S` (Issue #14, `send_keys_safe`)**: `agent prompt --wait` 결과가 `timeout`/`agent_prompt_stalled` 이면 herdr 가 입력을 이미 수락한 것으로 보고, `SKS_DELIVERY_GRACE_S`(기본 15초) 동안 `agent_status` working, 제출 전 화면에 없던 busy 토큰(`esc to interrupt`), 제출 전 스냅샷 대비 pane 변화 중 하나를 전달 확인 신호로 인정한다. 신호가 없으면 종전대로 rc 4 로 fail-closed 하고, 그 밖의 오류(`transport_error` 등)는 accepted 로 취급하지 않는다. 단일 주입 계약(재전송 없음)은 유지된다.
|
||
5. **래퍼 레지스트리 ack 유예 `MAM_INJECT_ACK_GRACE_S` (Issue #14, `multi-agent-mux-delegate-job`)**: `send_keys_safe` 가 전달을 확인하지 못해도 잡을 `agent_blocked` 로 닫기 전에 `MAM_INJECT_ACK_GRACE_S`(기본 10초) 동안 레지스트리를 관측하고, `pending` 이 아닌 상태(에이전트의 `started` 등)가 보이면 전달된 것으로 보고 진행한다(`_inject_acknowledged`).
|
||
6. **R1 유예의 pane busy 토큰·디스크 heartbeat 인정 (Issue #14, `_agent_still_working`, `_recent_disk_heartbeat`)**: 구독자 idle 타임아웃 후 R1 유예가 herdr `working` 외에 pane 최근 8줄의 `esc to interrupt` 토큰, 그리고 잡 `events.log` 의 최근 이벤트(`publish_event` 가 **와이어 발행 전에** 로컬에 기록하는 `progress` heartbeat — 브로커가 느리거나 죽어 있어도 발행·재시도를 기다리지 않고 즉시 디스크에 남는다. CLI `publish_event.py` 와 `DelegationService.publish_event` 모두 `record_local_event` 로 먼저 기록하고, 감사 로그의 발행 결과와 상태 동기화만 발행 뒤에 남긴다)도 작업 중 근거로 받아들인다. 유예에 들어가면 `IDLE_TIMEOUT` 창 하나가 열리고, 근거가 관측될 때마다 그 시점부터 창이 다시 열린다. 따라서 **아무 근거도 없는 무응답 에이전트는 마지막 와이어 이벤트 후 약 2×`IDLE_TIMEOUT` 에 닫히고**(디스크 heartbeat 를 인정하기 전에는 `IDLE_TIMEOUT`; Issue #6 수용 기준 "≤ `IDLE_TIMEOUT`+5s 내 `error`"는 "≤ 2×`IDLE_TIMEOUT` + 여유"로 바뀐다), **heartbeat 간격은 약 2×`IDLE_TIMEOUT` 까지 허용된다**(heartbeat 하나가 `IDLE_TIMEOUT` 만큼 오래될 때까지 근거로 인정되므로). 근거는 idle 창만 늘릴 뿐 벽시계는 늘리지 않는다: 루프는 매 반복 시계로 다시 읽은 `START_TIME + TIMEOUT` 에서 끝나고, herdr 관측 호출도 그 시각에 끊는다(`_within_deadline`). `broker_unavailable` 디스크 재관측 루프도 같은 시계 기준이다. 이로써 idle 타임아웃보다 긴 codex 리뷰, heartbeat 간격이 한 번 idle 창을 넘긴 리뷰, 백그라운드 셸로 테스트를 돌려 herdr 에 idle 로 보이는 creator 가 턴 도중 `deadline_timeout` 으로 닫히고 이후 `completed` 가 거부되던 문제를 막는다.
|
||
|
||
#### ✅ 품질 검증
|
||
- **T1-7 트리거 변경** (`tests/test_issue6_fail_closed.py`): 주입 실패 유발값을 `prompt_wait_result=timeout` → `transport_error` 로 변경(Issue #14 이후 `timeout` 은 accepted 제출이므로 더 이상 확정 실패가 아님)하고 `MAM_INJECT_ACK_GRACE_S=1` 로 대기를 단축.
|
||
- **신규 테스트 21개**: `tests/test_issue9_server_env.py` 9개(마커 제거, `MAM_NO_AUTOSTART` 부트스트랩 생략·호출 전달, status 조회 무기동, 정적 opt-out, `ls` 번역(최소·herdr 0.9 실제 오류 봉투·stdout 봉투), 성공 목록 속 `server_not_running` 문자열 비오판, 그 밖의 실패 unknown 유지(stderr·stdout), reconcile drift A 비발생, 다른 실패 시 reconcile `herdr_confirmed=False`), `tests/test_issue14_delivery_ack.py` 12개(busy 토큰·pane 변화 확인, 무신호 fail-closed, 선행 busy 토큰 배제, `transport_error` 비수용, mock herdr timeout 확인, 래퍼 레지스트리 ack, idle 타임아웃 pane busy 유예, idle 타임아웃 디스크 heartbeat 유예(`publish_event` 실제 경로), heartbeat 가 이어져도 벽시계 `TIMEOUT` 안에서 종료, 와이어 발행 전 로컬 기록, 발행 실패·재시도에도 로컬 기록 1회).
|
||
- **테스트 기대값 정정**: `tests/test_a4_adapter_contract.py` 가 `4d444ce` 의 agy `exit_key=/exit` 변경을 반영하도록 수정(그동안 전체 스위트에서 1건 실패하던 문제 해소).
|
||
- **실측 (커밋 `497633f` 기록)**: 새로 부트스트랩한 서버에 마커가 없고 claude pane 의 transcript 저장이 복구됨, codex 잡 `d04ebcb5` 의 `started`/`completed` 가 `agent_blocked` 없이 수락됨.
|
||
|
||
### 🚀 `v4.4.1` — Issue #6 비동기 잡 위임 안정화 및 Fail-Closed / Turn Handshake 완결 (2026-09-08)
|
||
|
||
> **패치 마일스톤 (PATCH Release)**: Issue #6 비동기 잡 위임 파이프라인에서 발생하던 터미널 상태 덮어쓰기 레이스, Herdr 프롬프트 완료 관측 부재, Subscriber 타임아웃 시 pending 방치 버그를 근본 해결. 결정론적 종료 코드 계약(0, 1, 2, 3, 5) 및 4단계 fail-closed finalizer를 완비했다.
|
||
|
||
#### 🌟 주요 개선 사항
|
||
1. **Terminal 상태 단방향 불변성**: 터미널 상태(`completed`, `error`, `cancelled`)에 도달한 잡에 대해 이후 도착하는 이벤트의 임의 상태 변경을 원천 차단. `registry.py close` 서브커맨드로 비정상 중단 시 단방향 종료 처리 보장.
|
||
2. **Herdr 턴 완료 핸드셰이크 (`lib.sh: wait_for_agent_idle`, `watcher.py`)**: `wait_for_agent_idle`을 통해 에이전트 프롬프트 전송 전후 유휴 상태를 확인하고, 턴이 완전히 완료되어 입력 대기 상태로 복귀할 때까지 신뢰성 있게 추적.
|
||
3. **Loop 모드 `attempt_id` 전이 격리**: 동일 iteration 내에서 워커→리뷰어 전환 시 `attempt_id`를 갱신하여 CAS 재전이 충돌(rc=4)을 구조적으로 방지.
|
||
4. **Subscriber 비정상 종료 시 Fail-Closed 및 R1 Working 회복력 보장**: 구독기 프로세스가 타임아웃되더라도 에이전트가 Herdr에서 여전히 작업 중(`working`)이면 벽시계 `--timeout`까지 유예하며 디스크 완료 상태를 재관측(R1)하고, 미종료 잡을 안전하게 `error`로 완결.
|
||
5. **결정론적 종료 코드 계약**: `multi-agent-mux-delegate-job` 종료 코드를 정규화(`0`=성공, `1`=실패/주입 오류, `2`=구독기 타임아웃, `3`=브로커 장애, `5`=세션 선결 조건 미충족/session_busy).
|
||
|
||
### 🚀 `v4.4.0` — Issue #5 Python 코어 마이그레이션 완결 및 `main` 병합 (2026-09-07)
|
||
|
||
> **주요 마일스톤 (MINOR Release)**: Issue #5 Phase 1~4 전 단계 완료 및 `refactor` → `main` 병합. 위임·루프 오케스트레이션의 구현 언어를 Bash에서 Python으로 옮기고, CAS 단일 상태 전이·섀도우 와처·자동 완료 게이트·Eager Import 불변 런타임을 도입했다. **공개 계약(스크립트 경로, CLI 인자·종료 코드, 이벤트 와이어 스키마 `schema_version=1`, YAML 레이아웃)은 한 건도 제거·변경되지 않았으며**, 모든 기본 동작 변경은 환경변수 한 개로 즉시 되돌릴 수 있다.
|
||
|
||
#### ⚖️ SemVer 판정 근거 (MINOR — `v4.4.0`, MAJOR `v5.0.0` 미채택)
|
||
|
||
SemVer 2.0.0 §8은 MAJOR를 **하위 호환을 깨는 공개 API 변경**에 한정한다. 본 릴리스는 규모는 크지만 그 조건을 충족하지 않는다.
|
||
|
||
| 판정 축 | 관측 사실 | 근거 |
|
||
| :--- | :--- | :---: |
|
||
| 공개 계약 제거/이동 | Issue #5 전 구간에서 **삭제·rename 파일 0건** | `git diff --name-status fc0bd96..HEAD` |
|
||
| 와이어 스키마 | `SCHEMA_VERSION = 1` 불변 | `mqtt_common.py:135` |
|
||
| 스크립트 리터럴 경로 | `scripts/*.py` 4종 전부 존속 (호환 shim) | Phase 1 D2 |
|
||
| CLI 인자·종료 코드 | `run_loop.sh`·`publish_event`·`job_subscriber` 계약 회귀 통과 | G-1~G-9 |
|
||
| 기본 동작 변경의 가역성 | `MAM_DELEGATION_PY=0` / `MAM_ENGINE_CAS=0` / `MAM_LOOP_PY=0` / `MAM_LOOP_FREEZE=1` 로 이전 동작 완전 복원 (레거시 본체 존치, 968줄 셸 루프 포함) | `test_issue5_phase3_loop.py`, `test_issue5_phase4_runtime.py` |
|
||
| 최고위험 신규 기능 | 자동 완료는 **기본 비활성** 옵트인 | `MAM_AUTO_COMPLETE` 기본값 `0` |
|
||
|
||
> **운영자 조치 필요 사항 없음**: 마이그레이션 절차, 설정 변환, 재배포가 요구되지 않는다. 이것이 MAJOR를 채택하지 않은 결정적 이유다. 다만 `MAM_VERSION` 불일치는 새로 fail-closed 거부되므로, **프레임워크 트리는 부분 갱신하지 말고 통째로 배포해야 한다**(§ 아래 주의).
|
||
|
||
#### 🌟 신규 기능 및 아키텍처 확장
|
||
* **Phase 1 — `lib_py.delegation` 패키지 추출**: `v4.3.0`에서 선행 완료(하단 항목 참조).
|
||
* **Phase 2a — CAS 단일 상태 전이 엔진 및 섀도우 와처**: 잡 레코드 전이를 `claim`/`redispatch` 시점에 발급되는 불투명 `dispatch_token`을 선결 조건으로 하는 compare-and-set으로 결속. 관측 증거 수집(`attribution.py`)·판정 정책(`policy.py`)·증거 저장소(`store.py`)를 분리하고, `shadow.py`가 실제 전이 없이 "Would-Complete" 판정만 기록한다.
|
||
* **Phase 2b — 자동 완료 게이트 (`autocomplete.py`)**: 섀도우 증거가 충분할 때에만 잡을 자동 종결하는 평가기. `MAM_AUTO_COMPLETE`(기본 `0`), `MAM_AUTO_COMPLETE_BACKENDS`로 백엔드 단위 옵트인. CAS가 롤백된 상태(`MAM_ENGINE_CAS=0`)에서는 스스로 비활성화한다.
|
||
* **Phase 2b — 내구성 있는 보류 알림 (`notifier.py`)**: 알림 의도를 잡 레코드에 저장하고 CAS 락 바깥에서 발행한 뒤 `notification_settled` 부기 요청으로 조건부 정산. 재시도는 새 와이어 시퀀스를 받으므로 단조 시퀀스 가드로는 중복을 식별할 수 없고, 안정적인 `(seq, event, timestamp)` 의도 신원으로 중복을 판별한다.
|
||
* **Phase 3 — `run_loop.sh` 얇은 래퍼 전환 및 `lib_py.loop` 승격**: 936줄 Bash 오케스트레이션을 단일 제어 구조의 Python 모듈로 이식. `run_loop.sh`는 CLI 계약과 기존 정적 검사 앵커를 보존하는 래퍼가 되었고, `MAM_LOOP_PY=0`으로 레거시 셸 루프를 그대로 실행할 수 있다.
|
||
* **Phase 3 — Strict 판정 파서 (`parser.py`)**: 마크다운 코드 펜스(` ``` `, `~~~`) 내부를 제거한 뒤 **마지막 비공백 라인**만을 판정으로 인정한다. 리포트 본문의 예시 코드에 적힌 `[VERDICT: PASS]`가 실제 판정으로 오인되던 결함(`baseline.md` Gap)을 해소했다. 판정 부재·`PASS`/`NOT PASS` 모호 충돌·미지 태그는 모두 fail-closed `NOT PASS`.
|
||
* **Phase 3 — 원자적 체크포인트 및 우아한 정지 (`checkpoint.py`)**: `.mam/loop-checkpoint.json`에 회차·활성 잡·계획·리뷰 상태·기준 커밋을 임시 파일 + `replace` 원자 커밋으로 기록. SIGINT/SIGTERM 수신 시 서브프로세스를 정리하고 체크포인트를 남긴 뒤 가드를 해제하고 종료(130/143).
|
||
* **Phase 4 — Eager Import 불변 런타임 (`eager.py`)**: 기동 최우선 단계에서 표준·프레임워크 코어 모듈 전량을 `sys.modules`에 상주시켜, 루프 실행 중 디스크의 `.py`가 편집되어도 런타임이 영향을 받지 않는다. `PYTHONDONTWRITEBYTECODE=1`로 `.pyc` 파편에 의한 작업 트리 오염을 차단한다.
|
||
* **Phase 4 — B-13 Freeze 스냅샷 제거**: Bash가 실행 중 스크립트를 디스크에서 재독해하며 발생하던 크래시(B-13)의 원인이 Python 상주 프로세스 전환으로 근본 해소되었으므로, 임시 비계였던 `/tmp/mam-loop-freeze.XXXXXX` 트리 복사를 기본 경로에서 철거했다. 턴당 수백 개 파일 복사 오버헤드가 사라진다.
|
||
|
||
#### 🔒 롤백 및 안전장치
|
||
* **단일 환경변수 롤백 4종**: `MAM_DELEGATION_PY=0`(위임 셸 헬퍼 레거시 본체), `MAM_ENGINE_CAS=0`(무조건 병합 복원), `MAM_LOOP_PY=0`(레거시 셸 루프), `MAM_LOOP_FREEZE=1`(Freeze 스냅샷 재활성화). 재배포·revert 불필요.
|
||
* **⚠️ 부분 배포 금지 (신규 fail-closed 게이트)**: `lib.sh`의 `MAM_VERSION`과 `lib_py/loop/eager.py`의 `FRAMEWORK_VERSION`이 다르면 `verify_framework_version()`이 `RuntimeError`로 루프 기동을 거부한다. 두 파일 중 하나만 갱신된 혼재 트리는 실행되지 않으므로, 프레임워크는 항상 트리 단위로 배포한다.
|
||
* **4-Way 락스텝 자동 검증**: `test_version_consistency.py`가 `lib.sh` ↔ `eager.py` ↔ `VERSIONS.md`(현재 버전 + 매트릭스 8행) ↔ 8개 `SKILL.md` frontmatter의 완전 일치를 강제한다. `eager.py` 커버리지는 본 릴리스에서 신규 추가되었다.
|
||
|
||
#### ✅ 품질 검증
|
||
* 전체 테스트 스위트 **750개 통과, 0 실패, 0 skip** (Issue #5 착수 시점 510개 대비 +240).
|
||
* Issue #5 전용 회귀: `test_issue5_phase{0,1,2a,2b,3,4}_*.py`.
|
||
* **테스트 격리 결함 1건 수정 (`tests/conftest.py`)**: `lib.sh`가 export한 `MAM_VERSION`이 pytest를 기동한 셸에서 상속되어, 루프 테스트가 *검사 대상 트리*가 아니라 *러너를 띄운 셸이 언제 `lib.sh`를 source했는지*에 따라 통과·실패하고 있었다. 지금까지는 상속값과 상수가 우연히 같아 잠복해 있었고, 본 릴리스의 버전 상승이 이를 드러냈다(4건 실패). 세션 스코프 fixture가 상속된 값을 제거하여 스위트가 트리를 측정하도록 바로잡았다.
|
||
- 프로덕션 경로는 영향 없음: `run_loop.sh:17`이 실행 최초에 `lib.sh`를 source하고 `lib.sh:32-33`이 `MAM_VERSION`을 무조건 재export하므로, 낡은 상속값은 파이썬 기동 전에 항상 덮어써진다(`MAM_VERSION=4.3.0 bash -c 'source lib.sh; echo $MAM_VERSION'` → `4.4.0`).
|
||
|
||
### 🚀 `v4.3.0` — `lib_py.delegation` 패키지 추출 및 장수 MQTT 연결 프로토타입 (2026-09-06)
|
||
|
||
> **주요 마일스톤 (MINOR Release)**: Issue #5 Phase 1. `lib.sh`의 위임 헬퍼 3종과 `multi-agent-mux-delegate-job/scripts/*.py` 4종을 `lib_py/delegation/` 파이썬 패키지로 추출. 스크립트 경로는 와이어 계약이므로 호환 shim으로 보존하며, 프로덕션 동작·이벤트 스키마·자동완료 정책은 한 줄도 바뀌지 않는다.
|
||
|
||
#### 🌟 신규 기능 및 아키텍처 확장
|
||
* **패키지 바인딩 shim (D2)**: `scripts/{mqtt_common,registry,publish_event,job_subscriber}.py`는 `lib_py.delegation` **패키지**를 자기 옆 트리에 프로세스당 1회 바인딩하고 `sys.path`를 건드리지 않는다. `import mqtt_common`은 `lib_py.delegation.mqtt_common`과 동일한 모듈 객체가 된다.
|
||
- `sys.path.insert(0, ...)` 방식은 pytest에서 최초로 임포트된 샌드박스 사본이 `lib_py.agents`/`layout`/`state`까지 세션 전체에 걸쳐 가로채므로 채택하지 않았다.
|
||
- 모듈 단위 파일 적재 방식은 형제를 임포트하는 모듈이 다른 트리의 의존성을 끌어와 freeze(B-13) 보호를 부분 상실하므로 채택하지 않았다.
|
||
* **`DelegationService` / `cli.py` / `daemon.py`**: 셸 헬퍼 3종의 본체를 `python -m lib_py.delegation.cli`로 교체. `start_watchdog`은 `ensure_subscriber_daemon`의 별칭으로 유지.
|
||
* **`PersistentPublisher` 프로토타입 (`publisher.py`) 및 벤치마크 하니스 (`bench.py`)**: 장수 연결은 Phase 2a 엔진용이며 프로덕션 경로(CLI·셸)는 계속 1회성 연결을 사용한다.
|
||
* **`ensure_env_loaded()` (D6)**: `.mam.env` 로딩을 임포트 시점에서 프로세스 진입점으로 이동. 라이브러리 임포트가 `os.environ`을 변경하지 않는다.
|
||
|
||
#### 🔒 롤백 및 안전장치
|
||
* **`MAM_DELEGATION_PY=0`**: 셸 헬퍼가 보존된 레거시 인라인 본체(`_*_legacy`)로 즉시 분기. 재배포·revert 불필요. 레거시 본체 제거는 Phase 4.
|
||
* **특성화 테스트 선행 (P1-00)**: 이전까지 위임 셸 헬퍼 3종을 직접 검증하는 테스트가 0건이었으므로, 이동 **전에** 골든 테스트를 먼저 추가하고 두 경로(`MAM_DELEGATION_PY` 0/1) 모두에서 통과시켰다.
|
||
|
||
|
||
### 🚀 `v4.2.0` — OpenAI Codex First-Class Agent Backend Integration (2026-09-05)
|
||
|
||
> **주요 마일스톤 (MINOR Release)**: OpenAI Codex CLI(`codex`)를 6번째 정식 1급 에이전트 백엔드로 통합. 46개 전체 아키텍처 결합 지점(Shell 라이프사이클, Python 어댑터, 레지스트리, 상태 머신, 동기화 루프, 테스트 스위트, 문서) 전면 연동 완료.
|
||
|
||
#### 🌟 신규 기능 및 아키텍처 확장 (New Features & Architecture)
|
||
* **A-1: Codex Agent Adapter (`CodexAgentAdapter`) 신규 구현 (`codex.py`, `registry.py`)**:
|
||
- `BaseAgentAdapter` 완전 구현: `name='codex'`, `own_key='codex_session_id_own'`, `ready_tokens='Codex|Ask Codex to do anything'`, `exit_key='Ctrl+C'`, `delegate_agent_key='codex-cli'`.
|
||
- Class A 에이전트 라이프사이클 채택: 세션 생성 시 UUID를 사전 할당하지 않고 런타임에 동적으로 발견.
|
||
- Rev.2 동적 SQLite DB 탐색: 고정 버전 파일 대신 `state_*.sqlite` 패턴을 최신 mtime 순으로 탐색하여 `threads` 테이블에서 `(rollout_path, cwd)`를 O(1)로 색인.
|
||
- Fallback 탐색: SQLite 미존재 시 `~/.codex/sessions/*/*/*/rollout-*-<uuid>.jsonl` date-nested 경로 및 재귀 패턴 지원.
|
||
- Safe Purge: `codex delete <uuid> --force` CLI 호출 및 온디스크 jsonl/DB 레코드 폴백 삭제.
|
||
* **A-2: Shell 라이프사이클 및 오케스트레이션 46개 지점 완전 연동**:
|
||
- `lib.sh`: `resolve_agent_type_from_registry`, `_sanitize_herdr_agent_name`, `send_keys_safe`에 codex 등록.
|
||
- `create_session.sh`: `--agent codex` 허용, `~/.codex/auth.json` / `OPENAI_API_KEY` 프리플라이트 검증, `--dangerously-bypass-approvals-and-sandbox` 플래그 스폰, `codex_session_id_own: null` 초기화.
|
||
- `resume_session.sh`: `codex` 복원 지원, 0-turn 세션에 대한 `FRESH_SPAWN=1` Class A 안전 폴백.
|
||
- `update_yaml_resumed.sh`: `codex` 세션 복원 시 YAML 필드 원자적 갱신.
|
||
- `stop_session.sh`: `codex` 지원, 종료 시 최종 session id 캡처 및 저장.
|
||
- `orc_onboard.sh`: flag-tolerant regex 기반으로 `codex resume [options] <uuid>` 명령행에서 UUID 정확 추출.
|
||
- `reconcile.sh`: drift C (codex) 전용 감지 블록 추가 (state DB 우선 + glob 폴백), discovery sweep 연동.
|
||
- `status.sh`: text/JSON 포맷터 양측에 codex 온디스크 복원 가능 여부 검사 로직 연동.
|
||
* **A-3: 문서 및 표준 헌장 동기화**:
|
||
- `README.md`, `README.ko.md`, `BOOTSTRAP.md`, `BOOTSTRAP.ko.md`, `deploy/INSTALL.md`, `.agents/MULTI_AGENT_RULES.md`, `.mam.env.example`, `docs/NEW_AGENT_INTEGRATION_GUIDE.md` 전면 업데이트.
|
||
|
||
---
|
||
|
||
### 🛠️ `v4.1.3` — Herdr Daemon Isolation & Class A 0-Turn Resume Reliability Fixes (2026-08-31)
|
||
|
||
> **주요 마일스톤 (PATCH Release)**: Issue #3 결함 해소 — Herdr 백그라운드 데몬의 `start_new_session=True` 기반 프로세스 그룹(setsid) 완전 격리, Class A(`agy`, `hermes`, `opencode`) 에이전트 0-turn 중지 세션의 `FRESH_SPAWN` fallback 및 discovery epoch 원자적 재설정, Class B(`claude`, `grok`) 계약 불변 보존, 455개 전체 테스트 스위트 100% PASS 달성.
|
||
|
||
#### 🩹 버그 수정 및 안정성 개선 (Bug Fixes & Resilience)
|
||
* **C-1: Herdr 데몬 프로세스 그룹(PGID/SID) 완전 분리 (`lib.sh`)**:
|
||
- `.mam/shim/herdr` 템플릿의 `nohup ... & disown` 스폰 방식을 파이썬 `subprocess.Popen(..., start_new_session=True)` 기반 전용 스포너로 대체하여 자식 프로세스에서 `os.setsid()` 호출 보장.
|
||
- 부모 셸이나 테스트 러너의 시그널 브로드캐스트(`SIGTERM`/`SIGINT`)로 인해 백그라운드 Herdr 데몬이 예기치 않게 종료되는 결함을 원천 차단 (`test_h26`).
|
||
* **C-2: Class A 에이전트 0-turn resume 자율 Fallback 및 Discovery Watermark 갱신 (`resume_session.sh`, `update_yaml_resumed.sh`)**:
|
||
- 대화 턴이 발생하기 전에 중지된 Class A(`agy`, `hermes`, `opencode`) 세션 resume 시, UUID 미존재로 인한 비정상 hard-fail(RC=1) 대신 `FRESH_SPAWN=1`로 자동 전환하여 `spawn-spec` 기반의 정상 재스폰 수행 (`test_t14`).
|
||
- fresh-spawn resume 시 `update_yaml_resumed.sh`에서 `herdr_session_epoch`를 resume 시점의 타임스탬프로 즉시 갱신하고 `session_id_source: pending-discovery`로 재설정하여, 세션 생성과 복원 사이에 생성된 무관한 이전 대화 파일이 `reconcile.sh`의 discovery sweep에 의해 오인 캡처되는 것을 방지 (`test_t16`).
|
||
- Class B(`claude`, `grok`) 에이전트의 `verify_session.py:99-101` assigned escape hatch 및 `test_t8` 계약은 수정 없이 완벽히 보존 (`test_t15`).
|
||
|
||
---
|
||
|
||
### 🛠️ `v4.1.2` — Pane Resolution & Reuse Reliability Fixes (2026-08-30)
|
||
|
||
> **주요 마일스톤 (PATCH Release)**: `bug-report/mam_upstream_bug_fix_report.md`에 보고된 4건을 실 코드 대비 독립 검증 — 2건은 실재하는 결함으로 확인되어 수정(SemVer 2.0.0 §6 PATCH), 1건(`ready_tokens`)은 v4.1.1에서 이미 해결됨을 확인, 1건(`resume_spec` `materialized` 게이트)은 5개 어댑터 공통의 의도된 안전장치로 판단되어 제안된 수정을 채택하지 않음(근거는 아래 참조).
|
||
|
||
#### 🩹 버그 수정 및 안정성 개선 (Bug Fixes & Resilience)
|
||
* **C-1: `_resolve_herdr_pane_id` Generic `agent_kind` 오인 매칭 방지 (`lib.sh`)**:
|
||
- 세션명 매칭 실패 시 수행되던 `agent_kind` 광역 폴백을 호출 대상 자체가 kind 리터럴(예: `"agy"`)인 경우로만 한정하고, 상위 루프의 exact match 대상에서 `'agent'`를 제외하여 동종 페인 다수 존재 시 R-1 fail-closed 계약이 온전히 적용되도록 보장. 이전에는 대상 세션의 yaml 항목에서 조회한 kind로 "동종 페인 아무거나" 검색을 수행해, 워크스페이스 내 무관한 동종 에이전트(예: 별도 오케스트레이터 `agy` 세션)가 단 1개라도 살아있으면 그 페인을 대상으로 오인 반환하는 결함이 있었음.
|
||
* **C-2: `new-session` 유휴 페인(Idle Pane) 우선 재사용 및 환경변수 주입 보전 (`lib.sh`)**:
|
||
- 기존 워크스페이스가 감지되면 곧바로 `pane split`을 호출하던 기존 동작을 변경, 워크스페이스 내 에이전트 미할당(유휴) 페인이 존재하면 이를 `target_pane`으로 즉시 재사용하고 split을 생략. 재사용 시 호출자가 전달한 `$env_flags` 및 `HERDR_WORKSPACE_ID`가 유실되지 않도록 `env` 접두어로 `$final_cmd`에 주입하여 환경변수 전달 일관성 보장. 오버플로우 시 신규 워크스페이스 생성 정책(W2b)은 유휴 페인이 없는 경우에만 그대로 적용됨.
|
||
* **검토 후 미채택 — `opencode.py::resume_spec` `materialized` 게이트**:
|
||
- 보고서는 `if materialized and session_uuid:` 조건 제거를 제안했으나, 이는 `claude`/`grok`/`agy`/`hermes`/`opencode` 5개 어댑터 전원에 동일하게 존재하는 의도된 계약이며, `materialized`는 `verify_artifact()`(opencode의 경우 실제 SQLite 세션 DB 조회)로 산출됨 — 존재하지 않는 세션 ID로 `--session`을 넘겨 CLI가 실패/행(hang)하는 것을 막기 위한 안전장치. 제안대로 수정 시 5-어댑터 일관성이 깨지고 안전장치가 무력화되므로 채택하지 않음. `resume_session.sh`는 `WORKSPACE`를 필수 인자로 검증하므로 `ctx`가 비어 `materialized`가 거짓이 되는 경로도 확인되지 않음.
|
||
* **재확인(조치 불필요) — `opencode.py::ready_tokens`**:
|
||
- 보고서가 지적한 ASCII 배너 미인식 문제는 v4.1.1(B-1, job `c38ddfcf`)에서 이미 해결되어 현재 `ready_tokens`가 보고서의 "수정 후" 값과 완전히 일치함을 확인. 보고서 작성 시점이 v4.1.1 이전 체크아웃이었던 것으로 추정.
|
||
|
||
---
|
||
|
||
### 🛠️ `v4.1.1` — Agent Creation & Runtime Reliability Fixes (2026-08-30)
|
||
|
||
> **주요 마일스톤 (PATCH Release)**: 에이전트 생성 및 런타임 신뢰성 향상을 위한 4대 보편 버그 수정(SemVer 2.0.0 §6 PATCH), Herdr 격리 환경 `PYTHONPATH` 자동 주입, OpenCode TUI 레디 토큰 보강, 세션 페인 식별자 Fast-Path 및 시작 타임아웃 복원력 강화.
|
||
|
||
#### 🩹 버그 수정 및 안정성 개선 (Bug Fixes & Resilience)
|
||
* **B-1: OpenCode TUI 준비 감지 토큰 보강 (`lib_py/agents/adapters/opencode.py`)**:
|
||
- `ready_tokens`를 `'OpenCode|Chat|Ask anything|tab agents|ctrl\+p|Build auto|commands'`로 확장하여 ASCII 배너 및 `Build auto` 초기 화면에서 30초 대기 없이 즉각 준비 완료 판정.
|
||
* **B-2: Herdr Shim 템플릿 내 독립 `PYTHONPATH` 주입 (`lib.sh`)**:
|
||
- `.mam/shim/herdr` 스크립트 상단에서 자신의 경로를 기준으로 `_SKILL_DIR`을 산출하고 `PYTHONPATH`에 주입하여 부모 셸 환경변수 미상속 서브셸에서도 `lib_py` 모듈 정상 로드 보장.
|
||
* **B-3: `herdr agent start` 타임아웃 복원력 및 send-text 폴백 (`lib.sh`)**:
|
||
- 시작 타임아웃 발생 시 무조건 실패 처리하지 않고, 실제 에이전트 프로세스 생존을 확인하거나 `pane send-text` + Enter 폴백으로 명령을 직접 재주입하여 세션 초기화 성공률 제고.
|
||
* **B-4: `_resolve_herdr_pane_id` 페인 ID Fast-Path 및 워크스페이스 레이블 매칭 (`lib.sh`)**:
|
||
- 이미 해석된 페인 ID(`wN:pM`) 입력 시 즉각 반환하는 Fast-Path 추가.
|
||
- `herdr workspace list`를 활용하여 워크스페이스 레이블(예: `mam-agents`)을 실제 `workspace_id`로 자동 변환 매칭.
|
||
|
||
---
|
||
|
||
### 🚀 `v4.1.0` — OpenCode AI Agent Integration (2026-08-29)
|
||
|
||
> **주요 마일스톤 (MINOR Release)**: OpenCode AI 에이전트(`anomalyco/opencode`) 백엔드 완전 통합(SemVer 2.0.0 §7 MINOR), 5대 핵심 에이전트(`claude`, `agy`, `hermes`, `grok`, `opencode`) 표준화, 29개 CLI 스크립트/스킬 연동 및 447개 전체 테스트 100% PASS 달성.
|
||
|
||
#### ✨ 주요 변경 사항 (New Features & Enhancements)
|
||
* **F-1: OpenCode 에이전트 어댑터(`OpenCodeAgentAdapter`) 구현**:
|
||
- `lib_py/agents/adapters/opencode.py` 신규 구현 및 `lib_py/agents/registry.py` 등록.
|
||
- SQLite 스토리지(`opencode.db`) 내 `directory` 및 `time_created`(밀리초) 실 스키마 기반의 방어적 세션 검증(`verify_artifact`) 및 멀티 후보자 발견(`discover`) 지원.
|
||
- `_resolve_db()`를 통한 `opencode db path` CLI 동적 해석 및 샌드박스 환경(`HOME`/`XDG_DATA_HOME`) 격리 지원.
|
||
* **F-2: CLI 라이프사이클 및 스크립트 전면 연동 (29개 터치포인트)**:
|
||
- `create_session.sh`, `resume_session.sh`, `stop_session.sh`, `status.sh`, `reconcile.sh`, `run_loop.sh`, `orc_onboard.sh` 내 `--agent opencode` 완전 지원.
|
||
- 세션 생성 및 재개 시 `OPENCODE_PERMISSION='{"*":"allow"}'` 자동 내보내기 및 사용자 사전 설정 보존(Empty-Guard).
|
||
- 백그라운드 조정자(`reconcile.sh`) drift-C 블록에서 어댑터 공유 헬퍼를 통한 세션 ID 사후 자율 감지 및 YAML 등록.
|
||
* **F-3: 포괄적 테스트 스위트 확장**:
|
||
- `tests/test_a4_adapter_contract.py`: 어댑터 계약, 실 스키마 밀리초 비교, CLI DB 경로 동적 해석 검증.
|
||
- `tests/test_tier1_unit.py`, `tests/test_tier2_component.py`, `tests/test_c1_tui_readiness.py`: TUI 및 라이프사이클 통합 검증 (총 447개 테스트 100% 통과).
|
||
|
||
---
|
||
|
||
### 🚀 `v4.0.0` — Complete Cline Agent Deprecation & Hermes Modernization (2026-08-28)
|
||
|
||
> **주요 마일스톤 (MAJOR Release)**: `cline` 에이전트 백엔드의 완전한 제거 및 폐지(Breaking Change per SemVer 2.0.0 §8), Hermes 에이전트 풀 모더니제이션 및 Ollama 로컬 연동 정식 지원, 4대 핵심 에이전트(`claude`, `agy`, `hermes`, `grok`) 표준화 및 439개 전체 테스트 100% PASS 달성.
|
||
|
||
#### ⚠️ 동작 변경 및 마이그레이션 안내 (Breaking Changes)
|
||
* **B-1: `--agent cline` 지원 완전 제거 및 파서 거부**:
|
||
- `adapters/cline.py` 삭제 및 `registry.py` 등록 해제에 따라 모든 스킬 엔트리포인트(`create_session.sh`, `resume_session.sh`, `stop_session.sh`, `reconcile.sh`, `orc_onboard.sh`, `multi-agent-mux-delegate-job` 등)에서 `--agent cline` 입력 시 `exit 2`로 즉시 거부됩니다.
|
||
- 지원되는 에이전트 패밀리는 `claude`, `agy`, `hermes`, `grok` 4종으로 엄격히 제한됩니다.
|
||
* **B-2: 영향도 확인 (Detection)**:
|
||
- 저장된 스크립트나 커스텀 세션에서 Cline 관련 설정이 남아있는지 확인:
|
||
```bash
|
||
grep -rn "agent: cline\|cline_conversation_id_own\|--agent cline" .mam/ *.sh 2>/dev/null
|
||
```
|
||
* **B-3: 업그레이드 전 세션 정리 (Pre-upgrade Cleanup — 권장)**:
|
||
- v3.1.0 상태에서 `deploy/update.sh` 실행 전, 기존의 활성/보관 중인 Cline 세션을 원자적으로 정리:
|
||
```bash
|
||
multi-agent-mux-stop --session <session-name> --agent cline --purge-conversation --yes
|
||
```
|
||
* **B-4: 업그레이드 후 고아 세션 레코드 정리 (Post-upgrade YAML Pruning)**:
|
||
- `deploy/update.sh`는 `.mam/agent-sessions.yaml`을 덮어쓰지 않고 보존하므로, 사전에 정리되지 않은 v3.x `cline` 세션 행은 아래 원자적 갱신 명령을 통해 안전하게 정리할 수 있습니다:
|
||
```bash
|
||
source .agents/skills/lib.sh
|
||
atomic_dump_yaml .mam/agent-sessions.yaml <<'MUT'
|
||
d['herdr_sessions'] = [
|
||
s for s in d.get('herdr_sessions', [])
|
||
if s.get('agent') != 'cline' and not str(s.get('name', '')).endswith('-cline')
|
||
]
|
||
MUT
|
||
```
|
||
* **B-5: Hermes 에이전트 모더니제이션**:
|
||
- 헤드리스 플래그(`--yolo --accept-hooks`), TUI 입력 영역 구분자(`─{10,}` 및 `❯`), 준비 토큰 강화, `reconcile.sh` 타임스탬프 기반 다중 후보자 동등성(`discover()`) 지원이 정식 통합되었습니다.
|
||
|
||
---
|
||
|
||
### 🚀 `v3.1.0` — 2-Tier TUI Readiness Model, Adapter Modal Contract & Fail-Closed Pane Resolution (2026-08-28)
|
||
|
||
> **주요 마일스톤**: 다이얼로그 기아(starvation) 구조적 해소를 위한 2계층 TUI 준비 판정 모델(`S ∨ (W ∧ C)`), 모달/힌트 2계층 토큰 분리, 어댑터별 `modal_tokens` 계약(`T-2d`) 정립, 동종 페인 다중 매치 fail-closed 방어, 준비 실패 세션 진단 보존(`create_session.sh`), `lib.sh` 런타임 버전 상수(`MAM_VERSION="3.1.0"`) 신설 및 436개 전체 테스트 100% PASS 달성.
|
||
|
||
#### ⚠️ 동작 변경 및 마이그레이션 안내
|
||
* **B-1: TUI 미준비 세션 보존 (`create_session.sh`)**:
|
||
- TUI 준비 감지 타임아웃 발생 시, PID가 생존해 있다면 세션을 강제 종료(`kill-session`)하지 않고 `status: running`, `last_visible_status: tui-ready-timeout`으로 등록한 뒤 `exit 0`(성공)으로 반환합니다.
|
||
- 구 `v3.0.1`의 무조건 롤백 및 종료 동작을 원할 경우 `MAM_KILL_ON_READY_TIMEOUT=1`을 설정하십시오.
|
||
* **B-2: 프로세스 사망 시 종료 코드 변경**:
|
||
- `create_session.sh` 실행 중 PID가 사망한 경우 명시적 구분 코드 `42`(`MAM_EXIT_READY_TIMEOUT`)로 종료합니다.
|
||
* **B-3: 동종 페인 다중 매치 시 Fail-Closed 거부 (`_resolve_herdr_pane_id`)**:
|
||
- 동일한 에이전트 종류의 페인이 여러 개 발견되고 정확한 세션명 일치가 실패할 경우, 임의의 첫 번째 페인을 선택하지 않고 즉시 `exit 1`로 실패하여 교차 세션 오염을 원천 차단합니다.
|
||
* **B-4 & B-5: 환경변수 전파 (`WORKSPACE_ROOT`, `MAM_VERSION`)**:
|
||
- `WORKSPACE_ROOT` 및 `MAM_VERSION`이 자식 프로세스로 자동 `export`됩니다. `MAM_VERSION`은 런타임 위조 방지를 위해 환경변수 오버라이드가 차단됩니다.
|
||
|
||
#### 🔧 기존 워크스페이스 사용자 안내
|
||
기존 배포된 워크스페이스 사용자는 `.mam.env`에 아래 3종의 신규 환경변수를 추가하여 동작을 튜닝할 수 있습니다:
|
||
|
||
| 변수명 | 기본값 | 설명 |
|
||
| :--- | :---: | :--- |
|
||
| `MAM_KILL_ON_READY_TIMEOUT` | `0` | `1`로 설정 시 TUI 준비 타임아웃 발생 시 세션을 보존하지 않고 강제 종료(v3.0.1 동작) |
|
||
| `MAM_DIALOG_TAIL_LINES` | `8` | 블로킹 모달 시그니처를 검사할 페인 하단 비공백 라인 수 |
|
||
| `MAM_READY_EMPTY_GIVEUP` | `5` | `wait_for_tui_ready`가 페인을 관측 불가(Headless)로 판정하기까지의 연속 빈 캡처 횟수 |
|
||
|
||
#### 1. 2계층 TUI 준비 판정 모델 (`S ∨ (W ∧ C)`)
|
||
* **단독 충분 강한 신호 (`S`)**: `strong_ready_tokens` 또는 8자 이상의 유의미한 `input_placeholder`(`Ask anything...`) 일치 시 준비 완료 선언.
|
||
* **보강 결합 약한 신호 (`W ∧ C`)**: `weak_ready_tokens`(`history`, `Chat` 등)는 `input_prompt`(`❯`, `>`) 또는 `input_rule_pattern`(`─{10,}`)과 동시 일치할 때만 준비 완료로 판정하여 오탐 차단.
|
||
* **하강 호환성 (Fallback)**: `input_*` 속성이 미정의된 에이전트(Hermes 등)는 강한 토큰 단독 검사로 안전하게 하강.
|
||
|
||
#### 2. 모달/힌트 토큰 분리 및 어댑터 `modal_tokens` 계약 (`T-2a` / `T-2d`)
|
||
* **토큰 집합 분리**: `_MAM_MODAL_TOKENS`(고신뢰 블로킹 모달)와 `_MAM_HINT_TOKENS`(자연어 TUI 안내 팁)를 분리. `_pane_dialog_open`이 힌트 텍스트로 인해 영구 차단(Starvation)되는 결함 해소. `_MAM_DIALOG_TOKENS`는 하위호환용 합집합으로 존치.
|
||
* **`BaseAgentAdapter.modal_tokens`**: 에이전트별 특화 모달(Claude의 `Try the new fullscreen renderer?`, Cline의 `Select API Provider` 등)을 팩트 브리지(`MAM_MODAL_TOKENS`)로 통합.
|
||
|
||
#### 3. Fail-Closed 페인 해석 및 워크스페이스 스코프 강화 (`R-1` / `R-2`)
|
||
* `_resolve_herdr_pane_id`에서 다중 매치 시 `sys.exit(1)` 처리 및 `export WORKSPACE_ROOT` 보강으로 격리 안정성 확보.
|
||
|
||
#### 4. 준비 실패 세션 진단 보존 및 `MAM_VERSION` 스탬프
|
||
* `create_session.sh` 실패/타임아웃 시 `.mam/diagnostics/<session>-<timestamp>.txt`로 페인 덤프 및 `MAM Version` 스탬프 자동 기록.
|
||
|
||
#### 5. 런타임 버전 상수 `MAM_VERSION` 신설 및 3자 락스텝 검증
|
||
* `lib.sh` 내 `MAM_VERSION="3.1.0"` 선언 및 `test_version_consistency.py`를 통한 `lib.sh` ↔ `VERSIONS.md` ↔ 8개 `SKILL.md` 3자 일치 자동 검증.
|
||
|
||
#### 6. 신규 환경변수 3종 문서화
|
||
* `.mam.env.example`에 `MAM_KILL_ON_READY_TIMEOUT`, `MAM_DIALOG_TAIL_LINES`, `MAM_READY_EMPTY_GIVEUP` 3종 등록.
|
||
|
||
#### 7. 테스트 스위트 확장: 412 → 436 (+24, 100% PASS)
|
||
* `test_c1_tui_readiness.py` (21 tests), `test_adapter_modal_tokens_contract`, `test_version_consistency.py` 신설 등 총 436개 테스트 무결점 통과.
|
||
|
||
#### 8. 교차 피어 리뷰 이력
|
||
* F-1~F-8 및 R-1, R-2 총 10건의 피어 리뷰 이슈 도출 및 해소:
|
||
- F-1 (`Yes, try it` 팁 오탐 제거), F-2 (업셀 모달 선행 검사), F-3 (변수 스코프 격리), F-4 (리뷰어 실측 검증 후 자진 철회), F-5/F-6/F-7 (어댑터 팩트 연동 및 세션 패턴 보강), F-8 (비-클로드 어댑터 modal_tokens 계약 정립), R-1 (Fail-closed 페인 리졸버), R-2 (`WORKSPACE_ROOT` export 전파).
|
||
- 계획 단계 챌린지(`b9c29c3b`)를 통한 C-1 (`MAM_VERSION` 3자 락스텝) 및 C-2 (마이그레이션 안내) 수용.
|
||
|
||
### 🚀 `v3.0.1` — Herdr Shim Routing Contract Refactor, Multi-Workspace Isolation & 412-Test Milestone (2026-08-27)
|
||
|
||
> **주요 마일스톤**: Herdr 심 라우팅 5대 결함(ISSUE-1~5) 및 F-1~F-4 보완 완결, `HERDR_WORKSPACE_ID` 환경 변수 스코핑 및 파일 기반 격리 영속화 엔진 탑재, `pane send-text` 기반 단일 안전 삽입 계약 정립, 부분 문자열 매칭(`in tn`) 완전 제거, 412개 전체 테스트 100% PASS 달성.
|
||
|
||
#### 1. Herdr 심 라우팅 및 다중 워크스페이스 격리 강화 (`.agents/skills/lib.sh`)
|
||
* **`HERDR_WORKSPACE_ID` 명시적 스코프 & 영속화 (`ISSUE-3`, `F-2b`)**:
|
||
- `_herdr_ws_id_file()`, `_herdr_persist_ws_id()`, `_herdr_ws_scope()` 헬퍼 도입.
|
||
- `new-session` 시 생성된 워크스페이스 ID를 `$WORKSPACE_ROOT/.mam/herdr_workspace_id`에 영속화.
|
||
- `_herdr_agent_get_scoped`는 호출자의 환경변수(`HERDR_WORKSPACE_ID`)만을 우선 평가하여 단축 경로에서의 교차 워크스페이스 라우팅 오염 차단.
|
||
* **엄격 일치(Exact-Match) 단일 페인 리졸버 정립 (`ISSUE-2`, `ISSUE-5`, `F-3`)**:
|
||
- 기존의 에이전트 CLI 명칭 부분 문자열 매칭(`in tn`, `agent in tn`) 완전 제거.
|
||
- 단일 중앙 헬퍼 `_resolve_herdr_pane_id`로 `has-session`, `kill-session`, `capture-pane`, `send-keys`, `paste-buffer`의 페인 해석 통합.
|
||
- 영숫자 워크스페이스 식별자 패턴(`^w[A-Za-z0-9]+:p[A-Za-z0-9]+$`) 지원 (`H-20`).
|
||
|
||
#### 2. 원자적 텍스트 삽입 및 안전 전송 계약 교정 (`ISSUE-1`, `F-4`)
|
||
* **`paste-buffer`의 `pane send-text` 전용화**:
|
||
- Herdr CLI에 미존재하던 `agent send` 제거 및 `pane send-text`로 교체.
|
||
- 삽입과 제출(Enter) 책임을 분리하여 의도치 않은 자동 제출 및 이중 제출(Double Submit) 방지.
|
||
- 페인 미해석 또는 삽입 실패 시 `send_keys_safe`로 에러 코드(`rc 3`) 명시적 전파.
|
||
* **`capture-pane` 폴백 체인 복원 (`F-4`)**:
|
||
- `pane read` 실패 시 `agent read`로의 폴백 체인을 안정적으로 복원하여 TUI 캡처 신뢰성 보장.
|
||
|
||
#### 3. TUI 준비 상태 감지 및 다이얼로그 토큰 정밀화 (`N-1`, `F-1`)
|
||
* **`_MAM_DIALOG_TOKENS` 일반 팁 토큰 분리**:
|
||
- 일반 대화형/TUI 팁에 등장하는 `Yes, try it`을 블로킹 다이얼로그 토큰 목록에서 제외하여 세션 시작 시의 오탐 방지.
|
||
- 풀스크린 업셀 모달에 대해서는 전용 Escape 거절 브랜치로 격리 처리.
|
||
|
||
#### 4. 테스트 스위트 및 Mock Herdr 계약 정기 동기화 (`tests/`)
|
||
* **Mock Herdr (`tests/conftest.py`)**: 실제 Herdr 0.8.2 CLI 계약과 완벽 동기화 (`pane send-text`, `pane read`, `pane rename` 반영, 미지원 `agent send` 실패 처리).
|
||
* **신규 계약 & 회귀 테스트 19건 추가**:
|
||
- `test_herdr_shim_contract.py`: H-15 ~ H-23 (단일 리졸버 불변식, 워크스페이스 격리, 단축경로 스코핑).
|
||
- `test_b19_headless_reconcile_fixes.py`: D-4 ~ D-7 (set -e 안전성, send_keys_safe 실패 전파, 다이얼로그 토큰 제외 회귀 방지).
|
||
* **전체 테스트 결과**: 412 passed in 681.20s (100% PASS).
|
||
|
||
---
|
||
|
||
### 🚀 `v3.0.0` — 5th Agent (Grok) Ecosystem Expansion, Mux Loop CLI Redesign & Deterministic 2xK Layout Engine 2.0 (2026-08-26)
|
||
|
||
> **주요 마일스톤**: 5번째 공식 에이전트 `grok`(Grok Build) 전면 통합, `/multi-agent-mux-loop`의 `--target-agent` 폐지 및 `--creator`/`--planner` 역할 분리(Breaking Change), 2×2 대칭 그리드를 보장하는 결정론적 레이아웃 엔진 2.0 탑재, 393개 전체 테스트 100% PASS 달성.
|
||
|
||
#### ⚠️ Breaking Changes & Migration Guide
|
||
* **`multi-agent-mux-loop` CLI 플래그 개편**:
|
||
- 기존의 단일 대상 지정 플래그 `--target-agent <session>`가 **공식 제거(Drop)** 되었습니다.
|
||
- 기존 명령은 이제 `ERROR: --target-agent was removed. Use --creator <session> instead.`와 함께 종료(`exit 1`)됩니다.
|
||
- **마이그레이션 방법**:
|
||
- 기존: `bash run_loop.sh --target-agent <dev-session> --task "..."`
|
||
- 변경: `bash run_loop.sh --creator <dev-session> --task "..."`
|
||
- 플래너 지정 시: `bash run_loop.sh --plan --planner <plan-session> --creator <dev-session> --task "..."`
|
||
|
||
#### 1. 5번째 공식 AI 에이전트 Grok Build (`grok`) 전면 통합
|
||
* **어댑터 및 수명 주기 관리 (`lib_py/agents/`, `lib.sh`)**:
|
||
- `GrokAgentAdapter` 구현 및 레지스트리 공식 등록 (`claude, agy, hermes, cline, grok`).
|
||
- `create_session.sh`, `resume_session.sh`, `stop_session.sh`, `resolve_session_id.sh`의 에이전트 화이트리스트 및 디스패치 지원 완비.
|
||
- 온디스크 세션 UUID 해석 및 TUI 렌더링 준비 감지 토큰 반영.
|
||
|
||
#### 2. 결정론적 2×K 그리드 레이아웃 엔진 2.0 (`lib_py/layout.py`, `lib.sh`)
|
||
* **$N=1\to 2$ `right` 분할 우선 정책 (R-1 결함 해소)**:
|
||
- 1개 페인에서 2번째 에이전트 추가 시 `down` 대신 `right`로 분할(`single_pane_split_right`)하여 좌/우 2개의 전고(Full-height) 열 확보.
|
||
- 이후 $N=3$(좌측 down), $N=4$(우측 down)로 이어져 완벽한 2×2 균등 대칭 격자 기하학(`widths=[138,139], heights=[39]`) 완성.
|
||
* **GUI ↔ 헤드리스 단일 결정표 통합 (`_decide_from_cols`)**:
|
||
- 기존의 불완전한 `n % 2` 홀짝 패리티 로직을 폐기하고, 열 점유 상태(`cols=(a, b)`) 기반의 통합 3단계 결정 매트릭스로 일원화.
|
||
* **안전 가드 및 4중 용량 배선**:
|
||
- `_full_height_pane` 가드로 반쪽 열 분할 원천 차단.
|
||
- `layout.py` 기본값(`default=2`), `lib.sh:435` CLI 인자(`--max-cols`, `--max-rows`), `.mam.env.example` 동기화 완료.
|
||
|
||
#### 3. 사전 검증 및 인프라 동기화
|
||
* `create_session.sh`의 `--workspace` 필수 인자 사전 검증(preflight) 강화.
|
||
* 사설 NATS 브로커(`nats-docker`) 2.14-alpine 최신 태그 동기화.
|
||
|
||
---
|
||
|
||
### 🚀 `v2.2.1` — Single-Workspace 2xK Multi-Pane Tiling Optimization & Premature Overflow Fix (2026-08-24)
|
||
|
||
> **주요 마일스톤**: `MAM_MIN_PANE_COLS` 기본값 60→40 완화, 표준 80~100컬럼 터미널 뷰포트에서 조기 워크스페이스 오버플로(가상 데스크톱 분리) 방지 및 단일 워크스페이스 2x2 통합 타일링 완성, 신규 80/79 경계 및 90/100 col 타일링 테스트 6종 추가, 다중 에이전트 피어 리뷰 100% PASS 달성.
|
||
|
||
#### 1. 2xK 레이아웃 엔진 최소 폭 완화 (`lib_py/layout.py`, `lib.sh`)
|
||
- `compute_2xk_layout` 기본 `min_cols` 및 CLI `--min-cols`, `lib.sh:432`의 `${MAM_MIN_PANE_COLS:-40}`, `.mam.env.example` 문서를 `40`으로 4중 일치화.
|
||
- 90~100컬럼 너비 터미널에서 3번째, 4번째 에이전트 생성 시 불필요하게 가상 데스크톱(Workspace)이 분리되던 현상 완전 해소.
|
||
|
||
#### 2. 경계값 및 타일링 자동화 테스트 확충 (`tests/test_layout.py`, `tests/test_tier1_unit.py`)
|
||
- `80` 컬럼(분할 성공) vs `79` 컬럼(오버플로) 하한 경계값 검증.
|
||
- `90` 및 `100` 컬럼 단일 워크스페이스 1→2→3→4 단계 2x2 타일링 및 5번째 에이전트 오버플로 전 과정 수명 주기 검증.
|
||
|
||
---
|
||
|
||
### 🚀 `v2.2.0` — Herdr Workspace Label Standardization, Runtime Sync & Legacy Fallback Decoupling (2026-08-24)
|
||
|
||
> **주요 마일스톤**: `--herdr-workspace` 옵션 전 스킬 도입 및 YAML 독립 직렬화, Herdr 런타임 워크스페이스 레이블 실시간 동기화, 레거시 소켓 폴백 체인 분리(Breaking Change 방어), 최신 Claude Code TUI 감지 토큰 반영, 27개 신규 테스트 추가 및 만장일치 PASS 달성.
|
||
|
||
#### 1. `--herdr-workspace` 옵션 도입 및 Herdr 런타임 레이블 동기화
|
||
- **CLI 옵션 및 YAML 직렬화 표준화**:
|
||
- `create_session.sh`, `resume_session.sh`, `update_yaml_resumed.sh`, `stop_session.sh`에 `--herdr-workspace <name>` 파서 및 환경변수(`HERDR_WORKSPACE`) 지원 추가.
|
||
- `agent-sessions.yaml`에 `herdr_session`(소켓명)과 `herdr_workspace`(워크스페이스 라벨)를 각각 독립 필드로 영구 직렬화.
|
||
- **Herdr 런타임 워크스페이스 레이블 실시간 연동 (`lib.sh`, `resume_session.sh`)**:
|
||
- `herdr workspace create` 호출 시 `--label "$MAM_WS_LABEL"` 전달 및 기존 워크스페이스 사용 시 `herdr workspace rename` 자동 호출.
|
||
- `resume_session.sh` 실행 시 저장된 `herdr_workspace`를 읽어 Herdr 런타임 레이블 복원 보장.
|
||
|
||
#### 2. 레거시 소켓 폴백 체인 분리 및 Breaking Change 원천 차단
|
||
- **소켓 vs 워크스페이스 함수 완전 분리 (`lib.sh`)**:
|
||
- `resolve_herdr_session()`: 데몬/소켓 세션명만 반환 (row `herdr_session` -> row `herdr_server` -> env -> slug).
|
||
- `resolve_herdr_workspace()`: 워크스페이스 라벨만 반환 (row `herdr_workspace` -> `pane.cwd` slug -> caller `ws` arg).
|
||
- 기존 코드베이스 6개 지점(`lib.sh:1027`, `reconcile.sh:135, 399, 495`, `status.sh:145, 270`)에서 소켓 검색 시 `herdr_workspace`를 오인 참조하던 구문을 완전히 제거.
|
||
- **외부 세션 입양(Drift-B) 보강 (`reconcile.sh`)**:
|
||
- 외부 세션 입양 시 `herdr_workspace`와 `herdr_server`를 자동 채번 및 직렬화.
|
||
|
||
#### 3. 최신 에이전트 TUI 준비 감지 보강 (`claude.py`, `lib.sh`)
|
||
- 최신 Claude Code(`v2.1.241`)의 시작 배너(`Claude Code`, `Opus 5 with high effort` 등)를 `ready_tokens`에 추가하여 세션 생성 타임아웃 방지.
|
||
|
||
---
|
||
|
||
### 🚀 `v2.1.0` — 2xK Grid Layout Engine, Explicit Agent Standardization & Herdr Session Hardening (2026-08-24)
|
||
|
||
> **주요 마일스톤**: 2xK 우측 성장 그리드 레이아웃 엔진(`lib_py.layout`) 구축(B-20), 전 스크립트 `--agent` / `--herdr-session` 표준화 및 전파 가드, 전체 346개 테스트 스위트 100% PASS 달성.
|
||
|
||
#### 1. 2xK 우측 성장 그리드 레이아웃 엔진 구축 (B-20 / I-2, I-3, C-1, J-1)
|
||
- **순수 파이썬 레이아웃 엔진 신설 (`lib_py/layout.py`)**:
|
||
- `tput` 기반 터미널 크기 동적 감지 및 2xK(2행 고정, 우측 열 추가) 그리드 기하학 계산 엔진 구현.
|
||
- 패널 번호 순서(0:좌상, 1:좌하, 2:중상, 3:중하...)에 따른 우측 확장 타일링 분할 명령(`split-pane -h/-v`, `select-pane`) 계산.
|
||
- 헤드리스/CI 최소 차원(최소 너비 60, 최소 높이 20) 가드 및 `default=60` falsy-zero trap 해결 (`_env_int`).
|
||
- **33개 신규 레이아웃 단위/회귀 테스트 구축 (`tests/test_layout.py`)**:
|
||
- 1~8개 패널 수식 검증, 비정상 인자/환경변수 방어, 무한 루프 방지 가드 검증.
|
||
|
||
#### 2. 에이전트 인자 표준화 및 레지스트리 자동 추론
|
||
- `stop_session.sh`, `create_session.sh`, `resume_session.sh`, `update_yaml_resumed.sh`, `resolve_session_id.sh` 전반에 걸쳐 `--agent <claude|agy|hermes|cline>` 명시적 표준화.
|
||
- 미지정 시 YAML 레지스트리(`agent-sessions.yaml`) 기반 에이전트 타입 자동 추론(`resolve_agent_type_from_registry`) 연동.
|
||
|
||
#### 3. `--herdr-session` 격리 세션 옵션 표준화 및 전파 가드
|
||
- `create_session.sh`, `resume_session.sh`, `stop_session.sh`, `update_yaml_resumed.sh` 전반에 `--herdr-session <NAME>` 표준 옵션화 (레거시 `--herdr-server` 완전 호환).
|
||
- `create_session.sh`에서 명시적 세션명이 워크스페이스 슬러그에 의해 덮어씌워지지 않도록 가드 보강.
|
||
- `resume_session.sh`의 post-spawn 재개 시 신규 Herdr 세션명이 YAML 레지스트리에 정확히 전파되도록 갱신 로직 및 신규 Tier 2 테스트 5건 추가.
|
||
|
||
#### 4. 테스트 스위트 확장 및 피어 리뷰 전원 만장일치 PASS
|
||
- 전체 테스트 스위트 수 **276건 → 346건 (100% PASS)** 확장.
|
||
- Multi-Agent Loop를 통한 Reviewer(`claude`, `cline`) 전원 `[VERDICT: PASS]` 검증 완료.
|
||
|
||
---
|
||
|
||
### 🚀 `v2.0.0` — Unified Agent Adapter Architecture & Herdr Standardization (2026-08-17)
|
||
|
||
> **주요 마일스톤**: 에이전트 지식 계층 단일 소스화(A-4), 레거시 격리 완전 폐기(Option B), 셸 브리지 하드닝 및 스킬 메타데이터 규격화 완료.
|
||
|
||
#### 1. 에이전트 지식 계층 마이그레이션 (A-4 Phase 2 / P3-1)
|
||
- **`BaseAgentAdapter` 추상 클래스 및 4대 어댑터 구축**:
|
||
- [`.agents/skills/lib_py/agents/base.py`](.agents/skills/lib_py/agents/base.py): `DiscoveryContext` 및 추상 인터페이스 정의 (`ready_tokens`, `exit_key`, `delegate_agent_key`, `identity_cache_fields`, `artifact_path`, `verify_artifact`, `purge_artifacts`, `spawn_spec`, `resume_spec`, `auth_ok`, `discover`).
|
||
- [`.agents/skills/lib_py/agents/adapters/`](.agents/skills/lib_py/agents/adapters/): `ClaudeAgentAdapter`, `AgyAgentAdapter`, `HermesAgentAdapter`, `ClineAgentAdapter` 4개 구체 클래스 구현.
|
||
- **`facts` 브리지 셸 인터페이스 하드닝**:
|
||
- `lib_py.agents` CLI 모듈을 통해 8개 `MAM_*` 변수를 `shlex.quote` 안전 인용 처리하여 방출.
|
||
- `wait_for_tui_ready` 빈 토큰 시 전량 매칭 오탐 방지 및 미지 에이전트 fail-closed 가드 내장.
|
||
- **셸 스크립트 전반 어댑터 이관**:
|
||
- `create_session.sh`, `resume_session.sh`, `stop_session.sh`, `reconcile.sh`에 산재되어 있던 40여 개 하드코딩 분기를 어댑터 호출로 일원화.
|
||
|
||
#### 2. 레거시 `isolation.root` 및 C-3b 소비자 완전 폐기 (Option B)
|
||
- Universal Global Config 전환 이후 남아있던 4개 레거시 격리 소비자 코드(`lib.sh::mam_session_iso_root`, `workspace_uuid.py::iso_root_of`, `verify_session.py`, `stop_session.sh`) 및 `atomic_yaml.py`의 레거시 유효성 검사 절 100% 삭제.
|
||
- 저장소 내 격리 잔재 참조 0건 달성.
|
||
|
||
#### 3. 셸 브리지 보안 및 예외 처리 강화 (R1, R2, N1 교정)
|
||
- **R1 (위임 에이전트 키 폴백 보강)**: 브리지 미작동 시 `delegate_agent`가 `antigravity-cli`로 일괄 퇴화하지 않고 `claude-code`, `hermes-agent`, `cline-agent`로 명시적 `case` 폴백하도록 개선.
|
||
- **R2 (`argv` 서브커맨드 전환)**: `python -c` 셸 변수 문자열 보간을 `spawn-spec`, `resume-spec`, `exit-key` 서브커맨드로 전면 전환하여 공백/작은따옴표 경로 에러 및 코드 주입 위협 원천 차단.
|
||
- **N1 (클린 환경 격리 가드)**: `test_a4_adapter_contract.py` 내 CLI 테스트가 앰비언트 `PYTHONPATH` 없이도 독립 통과하도록 환경 격리 보강.
|
||
|
||
#### 4. 스킬 메타데이터 규격화 및 피어 리뷰 100% PASS
|
||
- 8개 `SKILL.md` frontmatter `version: 2.0.0` 통일 및 배포 무결성 검증.
|
||
- Reviewer `cline` (Job `e7b9812b`) 및 Planner/Senior Reviewer `claude` (Job `31730364`) 전원 `[VERDICT: PASS]` 획득.
|
||
|
||
#### 5. 레거시 주석 및 사용법 정합성 최신화 (C-6)
|
||
- `stop_session.sh` 상단 주석 및 `usage()` 내 폐기된 플래그(`--mode soft|hard`, `--capture-id`, `--graceful`) 안내 문구를 완전 제거하고 현행 4대 에이전트(`claude`, `agy`, `hermes`, `cline`) 및 플래그 체계로 동기화.
|
||
- 회귀 방지 컴포넌트 테스트(`test_comp_stop_usage_matches_parser`) 신설.
|
||
- 회귀 및 계약 테스트: **263/263 PASS (100%)** 달성.
|
||
|
||
#### 6. macOS NFS 감지 `df -P` 폴백 검증 및 종결 (B-5)
|
||
- `_check_is_nfs`(`lib.sh`)의 macOS/BSD 환경 내 GNU 전용 `df --output` 구문 오류 시 POSIX `df -P` 폴백 동작을 실측 및 단위 테스트(`test_stop_check_is_nfs_local`)로 검증 완료하여 B-5 이슈를 정식 종결.
|
||
|
||
#### 7. `agent_identities` tier-3 신원 캐시 완전 제거 및 UUID 해결 경로 PyYAML 탈의존 (B-10 / Option A)
|
||
- 쓰기 경로가 존재하지 않아 구조적으로 히트 불가였던 tier-3 폴백과 부속 소비자(`workspace_uuid.py`, `reconcile.sh` drift D, `stop_session.sh` 캐시 소거)를 전면 삭제.
|
||
- UUID 해결 경로를 **tier-1(per-row own id) → tier-2(어댑터 `discover()`)** 2단계로 단순화.
|
||
- `verify_session.py::mam_orchestrator_uuids` 의 즉시 `yaml` import 를 YAML 폴백 분기로 이동, UUID 해결 경로가 PyYAML 없이 완주함을 실행 가드로 고정(`atomic_yaml.py` 의 시스템 PyYAML 요구는 설계상 유지).
|
||
- 회귀 가드 3종 신설 — 읽기 경로 부활 차단, `import yaml` AST 검사(지연 import 포함), 실행 경로 검증.
|
||
- 회귀 및 계약 테스트: **266/266 PASS (100%)** 달성.
|
||
|
||
#### 8. 셀프 호스팅 루프 런타임 프리즈 스냅샷 (B-13 / Stage 2)
|
||
- 루프 기동 시 `.agents/skills/` 를 `$TMPDIR` 에 1회 동결하고 스냅샷에서 재실행하여, 턴 도중 프레임워크 스킬 편집이 진행 중인 루프를 깨뜨리지 못하도록 차단.
|
||
- 코드 루트(스냅샷)와 상태 루트(`MAM_REAL_ROOT`)를 분리해 레지스트리·루프 락·diff 수집은 실제 저장소를 계속 사용.
|
||
- 재실행 시 원본 argv 를 배열로 보존해 인자 유실을 방지(파서가 `$@` 를 소비하므로 필수).
|
||
- 스냅샷 생성 실패 시 `log_*` 정의 이전 구간임을 고려해 `echo` 로 경고하고 미동결 진행.
|
||
- `MAM_LOOP_NO_FREEZE=1` 로 비활성화 가능. 스냅샷 생성 실패는 경고 후 기존 동작으로 폴백.
|
||
- 회귀 가드 5종 신설 — argv 보존, 파손 래퍼 면역, 스킬 트리 무오염(B-6 경계), 락 해제 및 스냅샷 정리(B-12 경계), 비활성화 스위치.
|
||
- 회귀 및 계약 테스트: **271/271 PASS (100%)** 달성.
|
||
|
||
#### 9. 감사 로그 루트 지연 평가 (B-9 / P4-1)
|
||
- `mqtt_common.LOGS_DIR` 의 import 시점 cwd 고정을 제거하고 호출 시점에 해석하는 `get_logs_dir()` 를 도입. `chdir` 이후에도 감사 로그가 현재 워크스페이스에 정확히 기록됨.
|
||
- PEP 562 모듈 `__getattr__` 로 `LOGS_DIR` 속성 접근 하위 호환 유지(동적 평가).
|
||
- PEP 562 `__dir__` 병행 정의로 `dir()`·탭 완성 가시성 유지.
|
||
- `DELEGATE_JOB_LOGS_DIR` 환경변수가 실행 중 변경까지 반영.
|
||
- 회귀 가드 5종 신설 — cwd 추종, 실제 파일 생성, 환경변수 동적 반영, 전역 재도입 차단, dir() 탐색성.
|
||
- 회귀 및 계약 테스트: **276/276 PASS (100%)** 달성.
|
||
|
||
---
|
||
|
||
### 🛠️ `v1.4.0` — Stability, Cleanup & Safe Job Delegation (2026-08-16)
|
||
|
||
> **주요 마일스톤**: 격리 잔재 정리, 서브셸 루프 락 조기 해제 버그 픽스, 신규 파일 캡처 및 경량화.
|
||
|
||
- **P2-2 (C-3a / C-4 레거시 격리 스텁 및 미사용 심볼 제거)**:
|
||
- `lib.sh` 내 빈 스텁 4종(`provision_isolation`, `isolation_lever`, `isolation_env_prefix`, `isolation_cmd_args`) 및 `_REAL_HERDR_PATH` 완전 삭제.
|
||
- `registry.py::TERMINAL_STATUSES` 및 `create_session.sh::ISOLATE` 제거.
|
||
- **P2-1 (B-6 / B-12 `delegate_job_safe` 안정화)**:
|
||
- `.agents/skills/...` 내 불필요한 `.tmp` 복사본 생성 제거 및 인플레이스 직접 실행(`bash "$orig_script"`) 전환.
|
||
- 서브셸 내 `trap`으로 인한 루프 락 마커(`.mam/loop-guard-active`) 조기 삭제 결함(D1) 원천 차단.
|
||
- **P0-2 (O-2 중복 루프 기동 방지 원자적 락)**:
|
||
- `loop_lock.sh` 신설: `set -C` 기반 원자적 락 획득 및 PID + lstart 소유권 검증으로 동시 실행 방지.
|
||
- **P0-1 (B-7 외곽 diff 수집 및 미추적 파일 캡처)**:
|
||
- `diff_collect.sh` 도입: CWD 독립 `$REPO_ROOT` 기준 diff 수집 및 `git ls-files -o` 미추적 파일 병합.
|
||
- **B-4 (시프트 `ls` 동적 포시스 타임스탬프 복원)**:
|
||
- `lib.sh` 554행의 `created=999999` 하드코딩을 실시간 타임스탬프(`int(time.time())`)로 복원.
|
||
- **테스트 슈트 경량화**:
|
||
- 노후화된 중복 레거시 테스트 7개 파일(1,559줄) 삭제 (`cf51b2c`).
|
||
|
||
---
|
||
|
||
### 🛡️ `v1.3.0` — Orchestrator Onboarding & Scoped Guarding (2026-08-15)
|
||
|
||
> **주요 마일스톤**: 오케스트레이터 신원 격리 및 다중 에이전트 협업 가드레일 확립.
|
||
|
||
- **`multi-agent-mux-orc-onboard` 스킬 신설**:
|
||
- 오케스트레이터(`agy`)의 대화 UUID를 `.mam/agent-sessions.yaml` 내 `orchestrator_uuids` 리스트로 등록.
|
||
- `find_workspace_uuid` 및 `verify_session_uuid`에서 오케스트레이터 UUID를 스킵하여 서브에이전트 세션 오염 방지.
|
||
- **O-3 (조건부 오케스트레이션 위임 가드 — Scoped Guard)**:
|
||
- 오케스트레이터가 `/multi-agent-mux-loop` 활성화 상태에서 직접 코드를 수정하지 않고 스크립트로 위임하도록 통제.
|
||
- `AGENTS.md` §5 및 `MULTI_AGENT_RULES.md` 내 가드레일 명시.
|
||
- **배포 및 패키징 파이프라인 현대화**:
|
||
- `deploy/lib_ownership.sh` 신설 및 `hooks.json` 3-way 병합(`MERGE_REGISTRY`) 지원.
|
||
- Gitea CI/CD 파이프라인 (`deploy/gitea-ci.yml`) 연동.
|
||
|
||
---
|
||
|
||
### 🔌 `v1.2.0` — Universal Herdr Server Isolation & Cline Integration (2026-08-14)
|
||
|
||
> **주요 마일스톤**: Herdr 단일 서버 격리 및 다중 AI 에이전트 확장.
|
||
|
||
- **Universal Herdr Session Isolation**:
|
||
- `HERDR_SESSION_NAME` 기반으로 격리 서버를 통일하여 프로세스 충돌 방지.
|
||
- **Cline 에이전트 통합**:
|
||
- `cline` CLI 기반 대화 세션 생성, 정지, 복원 및 TUI 레디 토큰 핸들링 지원.
|
||
- **SQLite WAL 트랜잭션 동시성**:
|
||
- 세션 레지스트리 동시 쓰기 시 발생하는 락 충돌을 방지하기 위해 SQLite WAL 모드 전면 적용.
|
||
|
||
---
|
||
|
||
### 🧱 `v1.0.0` ~ `v1.1.0` — Initial Multi-Agent Mux Framework (2026-08-10 ~ 2026-08-13)
|
||
|
||
> **주요 마일스톤**: 터미널 다중 에이전트 오케스트레이션 기초 설계 및 비동기 루프 완성.
|
||
|
||
- **핵심 수명주기 스킬군 구축**: `create`, `stop`, `resume`, `status`, `monitor` 스킬 기본 구현.
|
||
- **MQTT 기반 비동기 잡 위임**: `multi-agent-mux-delegate-job`을 통한 에이전트 간 이벤트 통신 및 결과 구독.
|
||
- **자율 협업 루프**: `multi-agent-mux-loop` 컨트롤러를 통한 Planner-Creator-Reviewer 역할 분담 체계 정립.
|
||
|
||
---
|
||
|
||
## 🧪 품질 보증 및 검증 기준 (Verification Standards)
|
||
|
||
모든 릴리스는 다음 4단계 엄격한 검증을 통과해야 배포됩니다:
|
||
1. **정적 문법 검사**: `bash -n` (모든 셸 스크립트) 및 AST 미사용 코드 분석.
|
||
2. **단위 및 컴포넌트 테스트 (Tier 1~2)**: 인플레이스 및 컴포넌트 간 상호작용 검증.
|
||
3. **통합 및 계약 테스트 (Tier 3~4 / Contract)**: clean environment (`env -u PYTHONPATH`) 하에서의 어댑터 계약 및 CLI 브리지 검증.
|
||
4. **멀티에이전트 교차 피어 리뷰**: Planner(`claude`) 및 Reviewer(`cline`) 간 교차 검증 및 `[VERDICT: PASS]` 100% 합의.
|