Files
multi-agent-mux/VERSIONS.md
T
GodopuandClaude Opus 5.5 50c8da8269 release(v4.9.1): tests no longer reach a real herdr server - archive the review reports and bump the lockstep version
Review loop (--review-only, --all-reviewer): round 1 (@325b056) 4/4 PASS,
round 2 (@db7f4ca, HERDR_SESSION) 4/4 PASS. Full suite at 325b056 on
macOS / Python 3.9 / bash 3.2 / herdr 0.9.3 from an agent pane:
1845 passed / 0 failed / 56 skipped (18 test_deploy_freshness skips: no
nats-docker submodule in the fresh worktree); the panes of the real
servers (default, multi-agent-mux) and ~/.claude/projects were unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-10-13 10:33:04 +09:00

946 lines
174 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 📜 Multi-Agent Mux 버전 이력 (`VERSIONS.md`)
이 문서는 `multi-agent-mux` 프레임워크의 버전별 주요 기능 추가, 아키텍처 개선, 버그 수정 및 품질 검증 이력을 기록합니다.
---
## 📌 현재 버전 개요 (Current Release)
- **프레임워크 버전**: `v4.9.1`
- **최신 릴리스 일시**: 2026-10-13 (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.9.1`로 동기화되어 배포됩니다.
| 스킬명 | 버전 | 역할 및 주요 책임 | 상태 |
| :--- | :---: | :--- | :---: |
| **`multi-agent-mux-create`** | `4.9.1` | 에이전트 세션 신규 생성 및 Herdr 컨테이너 격리 스폰 | ✅ 배포 |
| **`multi-agent-mux-stop`** | `4.9.1` | 대화 UUID 원자적 캡처 및 세션 안전 종료 (Graceful Stop) | ✅ 배포 |
| **`multi-agent-mux-resume`** | `4.9.1` | 온디스크 대화 컨텍스트 기반 Tier-1 초고속 세션 복원 | ✅ 배포 |
| **`multi-agent-mux-status`** | `4.9.1` | 실시간 Herdr 세션 및 레지스트리 드리프트 스냅샷 조회 | ✅ 배포 |
| **`multi-agent-mux-monitor`** | `4.9.1` | YAML ↔ 런타임 상태 간 자율 조정자 (Reconciler Loop) | ✅ 배포 |
| **`multi-agent-mux-delegate-job`** | `4.9.1` | MQTT 이벤트 채널 기반 비동기 단위 작업 위임 | ✅ 배포 |
| **`multi-agent-mux-loop`** | `4.9.1` | Planner-Creator-Reviewer 3자 자율 계획·실행·피어리뷰 루프 | ✅ 배포 |
| **`multi-agent-mux-orc-onboard`** | `4.9.1` | 오케스트레이터 UUID 격리 등록 및 서브 세션 오염 방지 | ✅ 배포 |
---
## 📋 버전별 상세 변경 내역 (Changelog)
### 🚀 `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% 합의.