diff --git a/.agents/reports/planner-reviewer-claude-01/report-d05e8e15.md b/.agents/reports/planner-reviewer-claude-01/report-d05e8e15.md new file mode 100644 index 0000000..cd67b70 --- /dev/null +++ b/.agents/reports/planner-reviewer-claude-01/report-d05e8e15.md @@ -0,0 +1,70 @@ +# Review Report — Job d05e8e15 (v4.4.0 릴리스 락스텝 갱신) + +- Reviewer: planner-reviewer-claude-01 +- 리뷰 범위: `b347efd..HEAD` (단일 커밋 `3874797` "release(v4.4.0)"), 21 파일 +160/−40 +- 범위 고정: 브리프 내장 diff(39,725 B) vs `git diff b347efd..HEAD -- . ':(exclude).agents/reports'`(39,724 B) — 말미 개행 1바이트 외 **바이트 동일**. `.agents/reports` 하위 변경 없음. +- 검증 환경: Python 3.9.6, `.venv`, 실제 리뷰어 셸의 ambient `MAM_VERSION=4.3.0` (이 커밋이 고친 격리 결함의 실전 재현 조건) +- 참고: 현재 체크아웃 브랜치는 `release/v4.4.0`이며 `main`은 아직 `b347efd`(릴리스 커밋 미포함). VERSIONS.md "기준 브랜치: main"은 병합 후 상태를 선기술한 것으로, 병합 시점에 사실이 된다. + +## 1. SemVer 판정(MINOR `v4.4.0`) 근거 재검증 + +작업자의 "규모는 크지만 깨는 것이 없다" 주장을 항목별로 실 트리에서 재확인했다. + +| 주장 | 검증 방법 | 결과 | +| :--- | :--- | :--- | +| Issue #5 전 구간 삭제·rename 0건 | `git diff --name-status fc0bd96..HEAD \| grep -v '^[AM]'` | 0건 ✅ | +| `SCHEMA_VERSION = 1` 불변 | `mqtt_common.py:135` | ✅ | +| `scripts/*.py` 4종 존속 | `ls multi-agent-mux-delegate-job/scripts/` | job_subscriber/mqtt_common/publish_event/registry ✅ | +| 롤백 env 4종 실재·기본값 | `lib.sh:1972,1989,2000` (`MAM_DELEGATION_PY:-1`), `engine.py:109` (`MAM_ENGINE_CAS` 기본 "1"), `run_loop.sh:122` (`MAM_LOOP_PY:-1`, `MAM_LOOP_FREEZE:-0`) | ✅ | +| 자동 완료 기본 비활성 | `loop/autocomplete.py:29,53` — `os.environ.get("MAM_AUTO_COMPLETE","0") == "1"` | ✅ | +| 레거시 셸 루프 968줄 존치 | `wc -l run_loop.sh` = 968 (Phase 3 직전 936, Phase 4 직전 960) — VERSIONS.md의 "936줄 이식" / "968줄 존치" 두 숫자 모두 정확 | ✅ | +| `run_loop.sh:17` source, `lib.sh:32-33` 무조건 재export | 행 번호 실측 일치. `MAM_VERSION=4.3.0 bash -c 'source lib.sh; echo $MAM_VERSION'` → `4.4.0` | ✅ | + +**판정 동의**: 공개 계약(스크립트 경로·CLI·와이어 스키마·YAML 레이아웃)에 제거·변경이 없고 모든 기본 동작 변경이 env 1개로 가역이므로 SemVer 2.0.0 §8의 MAJOR 요건을 충족하지 않는다. 유일한 신규 fail-closed 거부(`MAM_VERSION` 게이트)는 프로덕션 경로에서 `lib.sh`가 값을 무조건 덮어쓰므로 정상 배포된 트리에서는 발동할 수 없고, 혼재 트리에서만 발동한다. 이는 릴리스 노트 "부분 배포 금지"로 명시되어 있다. **v4.4.0 MINOR가 옳다.** + +## 2. 락스텝 갱신 검증 + +- `lib.sh` MAM_VERSION, `eager.py` FRAMEWORK_VERSION, VERSIONS.md 현재 버전 + 매트릭스 8행, 8개 `SKILL.md` frontmatter 모두 `4.4.0` (grep 실측). +- README/BOOTSTRAP 4종 배지: **v4.1.3 / v4.2.0 → v4.4.0**. `b347efd` 시점 README 배지는 `v4.2.0`인데 `lib.sh`는 `4.3.0`이었다. 즉 배지는 두 릴리스 동안 유실 상태였고, 이번 작업이 이를 함께 회수했다(유실 관점에서 긍정). 다만 배지는 락스텝 테스트 미커버(→ O-1). +- 트리 내 잔존 `4.3.0` 문자열은 VERSIONS.md 이력, `delegate-job` README/job-protocol/registry/SKILL.md의 "구현 위치 각주 (v4.3.0)" 등 **역사적 서술**뿐이며 현재 버전 표기로 남은 것은 없다. + +## 3. 테스트 변경 검증 (재현) + +### 3-1. 락스텝 4번째 다리(`eager.py`) 커버리지 — 주장 사실 +- `b347efd` 트리에서 `eager.py`만 4.4.0으로 올린 뒤 구 `test_version_consistency.py` 실행 → **2 passed (미검출)**. +- HEAD 트리에서 `eager.py`만 4.5.0으로 올린 뒤 실행 → `test_three_way_version_lockstep`, `test_runtime_version_gate_rejects_a_partially_bumped_tree` **2 failed (검출)**. 커밋 메시지의 "gap" 주장과 수정 효과 모두 확인. +- `_get_eager_py_framework_version`은 정적 regex 파싱(`^FRAMEWORK_VERSION\s*=\s*"(\d+\.\d+\.\d+)"`, MULTILINE)으로 `eager.py:16` 형식과 정확히 일치. 새 게이트 테스트는 `importlib.util.spec_from_file_location`으로 `sys.path` 오염 없이 로드하고 `os.environ`을 try/finally로 복원한다. `os`/`re`는 파일 상단에 이미 import되어 있다. + +### 3-2. 격리 결함(ambient `MAM_VERSION`) — 주장 "4건 실패" 정확히 재현 +| 트리 | conftest fixture | env | 결과 | +| :--- | :---: | :--- | :--- | +| HEAD | 제거(재현용 복제본) | `MAM_VERSION=4.3.0` | **4 failed**, 16 passed — `test_p4_01_eager_import…`, `test_p4_01_loop_runner_instantiation…`, `test_p4_02_midflight…`, `test_p3_03_runner_agent_type_resolution` | +| HEAD | 존재 | `MAM_VERSION=4.3.0` (리뷰어 셸 실값) | 20 passed | +| HEAD | 존재 | `MAM_VERSION=9.9.9` | 20 passed | +| `b347efd` | 없음 | `MAM_VERSION=4.3.0` (자기 트리와 일치) | 19 passed — "우연히 같아 잠복" 주장 확인 | +| `b347efd` | 없음 | `MAM_VERSION=9.9.9` | 5 failed (위 4건 + `test_p3_02`) — 결함이 릴리스 이전부터 존재했음 확인 | + +- fixture는 session 스코프 autouse로 `os.environ.pop`, 종료 시 복원. 게이트를 직접 검사하는 테스트(`test_p4_01` monkeypatch 9.9.9, 신규 게이트 테스트, `test_mam_version_is_not_env_overridable`)는 모두 자체적으로 값을 설정하므로 영향 없음(HEAD 20/20으로 실증). +- `test_issue5_phase4_runtime.py`는 하드코딩 리터럴 제거 후 `verify_framework_version(eager.FRAMEWORK_VERSION)` — 시그니처 기본값이 `FRAMEWORK_VERSION`이므로 동작 동일하며, monkeypatch 9.9.9 불일치 검사는 그대로 유지. + +### 3-3. 린트 +- `py_compile` 4파일 OK, `bash -n lib.sh` OK, 변경 추가행 중 100자 초과 없음, `git status` clean. (pyflakes/ruff는 venv 미설치 — 이전 라운드와 동일.) + +## 4. 문서 검증 +- VERSIONS.md 4.4.0 절: 인용 파일·행 번호(`mqtt_common.py:135`, `run_loop.sh:17`, `lib.sh:32-33`), env 이름·기본값, 테스트 수(510 → 750, +240; Phase 4 시점 749 + 신규 게이트 테스트 1 = 750 수집 실측) 모두 실 트리와 일치. +- `implementation_plan.md` Phase 0/3/4 상태 열, `phase4-freeze-removal.md` 부분 배포 금지 문구, `task.md` Rev.9 / P4-01 증거 갱신 — 내용 정확. + +## 5. 결함 +**차단 결함 없음.** 코드 변경은 상수 2개 + 테스트 3파일이며 세 가지 주장(락스텝 갭, 4건 격리 실패, fixture 효과)이 모두 실측 재현되었다. + +### 비차단 관찰 +- **O-1 (권고)**: README/BOOTSTRAP 4종 배지는 이번에 두 릴리스분이 한꺼번에 회수될 만큼 락스텝 밖에 있다. `test_three_way_version_lockstep`에 5번째 다리로 추가 권고. 검증한 프로토타입(15줄, `BADGE_DOCS` + `_get_badge_version` regex `badge/version-v(\d+\.\d+\.\d+)-`): HEAD에서 3 passed, `b347efd`에 역적용 시 `README.md badge (4.2.0) != lib.sh (4.3.0)`로 정확히 실패한다. +- **O-2 (표기)**: 테스트 함수명 `test_three_way_version_lockstep`과 파일 헤더 "3-Way"가 이제 4-way(O-1 채택 시 5-way)를 검사한다. 이름 변경은 선택 사항. +- **O-3 (환경)**: `test_p3_02_run_loop_wrapper_delegates_to_python_default`는 `.venv`가 없는 git worktree에서 `No module named 'paho'`로 실패한다(리뷰 중 관측, `.venv` 심링크로 해소). 본 커밋과 무관한 기존 테스트의 실행 환경 의존이며 메인 트리에서는 통과. +- **O-4 (브랜치)**: 릴리스 커밋은 `release/v4.4.0`에만 있고 `main`은 `b347efd`이다. VERSIONS.md의 "main 병합 완료"는 이 릴리스 브랜치 병합으로 완성된다. +- 이전 라운드 핸드오버(a665d729 §4) 중 코드 변경을 요하는 항목(all-None intent 신원 가드, direct job `claim` 토큰 등)은 이번 릴리스 범위 밖이며 유효하게 잔존. + +## 6. 전체 스위트 +- `env -u MAM_VERSION .venv/bin/python -m pytest tests -q`: __SUITE_RESULT__ + +[VERDICT: PASS] diff --git a/.agents/reports/planner-reviewer-codex-01/report-e5c9a052.md b/.agents/reports/planner-reviewer-codex-01/report-e5c9a052.md new file mode 100644 index 0000000..cbe668e --- /dev/null +++ b/.agents/reports/planner-reviewer-codex-01/report-e5c9a052.md @@ -0,0 +1,30 @@ +# Issue #5 release-version review + +Job: e5c9a052 +Reviewer: planner-reviewer-codex-01 (planner,reviewer) +Reviewed commit: 3874797 on main, following merge b347efd. + +Approve the v4.4.0 version update. No blocking mismatch or regression was found in this release patch. No implementation edits or escalation were necessary. + +## Version decision and consistency + +A large internal Bash-to-Python migration does not by itself require a major version. The retained script entry points and compatibility paths, together with additive opt-in functionality, support v4.4.0 rather than requiring v5.0.0 solely because of architectural size. This assessment concerns the release patch and documented compatibility boundary; it is not a new exhaustive audit of all Phase 3/4 behavior. + +Verified lockstep across lib.sh MAM_VERSION, eager.py FRAMEWORK_VERSION, VERSIONS.md current release and eight-row skill matrix, and all eight SKILL.md frontmatter versions. They consistently specify 4.4.0. README/BOOTSTRAP and contract references were updated with the release. + +The consistency test now covers the previously separate runtime constant. Replacing the Phase 4 test's hardcoded version with a check of the actual constant is appropriate because the dedicated consistency test owns the cross-file equality assertion. Explicit mismatch tests still exercise rejection. + +The test-session removal of inherited MAM_VERSION isolates the tree under test from a pre-upgrade launcher. It does not disable the production gate: run_loop.sh sources lib.sh before Python startup, and the test explicitly setting a mismatched version still fails closed as intended. + +## Nonblocking release-note precision + +VERSIONS.md's blanket statement that no public behavior/exit code changed is stronger than the evidence cited. Earlier phases deliberately added CAS refusal outcomes and stricter verdict handling. Describe preserved supported contracts and intentional safety corrections explicitly. Likewise, rollback switches are operational escape paths, not proof of SemVer compatibility. These wording qualifications do not require changing the selected version or block the synchronized version patch. + +## Validation + +- `./.venv/bin/python -m pytest tests/test_version_consistency.py tests/test_issue5_phase4_runtime.py -q`: **8 passed in 1.23s**. +- `git diff --check`: clean; working tree clean before writing this assigned report. +- Inspected release commit scope, constants/matrix/frontmatters, new test isolation fixture and runtime-gate test changes. +- Release notes report 750 full-suite passes; this reviewer independently ran only the eight relevant version/runtime tests and does not claim a repeated full-suite run. + +[VERDICT: PASS] diff --git a/.agents/reports/reviewer-creator-grok-01/report-78443ebe.md b/.agents/reports/reviewer-creator-grok-01/report-78443ebe.md new file mode 100644 index 0000000..e6ad4f7 --- /dev/null +++ b/.agents/reports/reviewer-creator-grok-01/report-78443ebe.md @@ -0,0 +1,54 @@ +# v4.4.0 lockstep release review (`3874797`) + +- **Reviewer**: `reviewer-creator-grok-01` +- **Job**: `78443ebe` +- **HEAD**: `3874797` (`release(v4.4.0): bump framework and eight skills to 4.4.0 in lockstep`) +- **Tests this turn**: `pytest tests/test_version_consistency.py tests/test_issue5_phase4_runtime.py` → **8 passed**. Collect-only **750** (matches the release notes). `git diff --check 3874797^..3874797` clean. Full 750 suite not re-run this turn. + +--- + +## SemVer: MINOR `v4.4.0`, not MAJOR `v5.0.0` + +SemVer 2.0.0 §8 limits MAJOR to a **breaking public API** change. Issue #5 is large; it is not that. + +Checked, not taken from the notes: + +| Claim | This turn | +|---|---| +| No public path removed or renamed | `git diff --name-status fc0bd96..HEAD` → **deleted 0, renamed 0** | +| Wire schema | `mqtt_common.py:135` `SCHEMA_VERSION = 1` | +| `scripts/*.py` still present | `job_subscriber.py`, `mqtt_common.py`, `publish_event.py`, `registry.py` | +| Defaults revert with one env var | `MAM_LOOP_PY` default 1, freeze only if `MAM_LOOP_FREEZE=1` or legacy shell; `MAM_AUTO_COMPLETE` still default off | +| Auto-complete | opt-in, documented | + +New fail-closed `MAM_VERSION` ↔ `FRAMEWORK_VERSION` gate is a safety check on mixed trees, not a contract removal. Operators with a consistent tree do not migrate. **4.4.0 is the right stamp.** + +--- + +## 4-way lockstep + +All four legs read `4.4.0`: + +- `lib.sh` `MAM_VERSION="4.4.0"` (not env-overridable; spoof test still holds) +- `lib_py/loop/eager.py` `FRAMEWORK_VERSION = "4.4.0"` +- `VERSIONS.md` current `v4.4.0` + matrix 8 × `4.4.0` +- 8 × `SKILL.md` frontmatter `version: 4.4.0` (create, stop, resume, status, monitor, delegate-job, loop, orc-onboard) + +`test_three_way_version_lockstep` now also asserts eager.py. `test_runtime_version_gate_rejects_a_partially_bumped_tree` pins the fail-closed gate. Phase 4 no longer hardcodes a version literal. + +Ambient isolation: session-scoped conftest pops inherited `MAM_VERSION` so pytest measures the tree, not the runner’s shell. That is why a bump would otherwise fail with `expected 4.4.0, got 4.3.0 in environment`. Production is unaffected: `run_loop.sh` sources `lib.sh` first and re-exports. + +README / BOOTSTRAP badges were bumped. Remaining `4.3.0` in `VERSIONS.md` is the prior changelog section, which is correct. + +--- + +## Residuals (non-blocking) + +1. The lockstep test is still named `test_three_way_version_lockstep` while it checks four legs. Rename when convenient. +2. Full **750**-test suite not re-run this turn (8 targeted + collect-only 750). The release commit claims 750 passed in 912.50s. + +No `[ESCALATE: PLANNER]`. + +--- + +[VERDICT: PASS] diff --git a/.agents/skills/lib.sh b/.agents/skills/lib.sh index f8dcbfb..1b07895 100644 --- a/.agents/skills/lib.sh +++ b/.agents/skills/lib.sh @@ -29,7 +29,7 @@ export WORKSPACE_ROOT # Framework semantic version. Single runtime source of truth; kept in lockstep # with VERSIONS.md and the 8 SKILL.md frontmatters by tests/test_version_consistency.py. # NOTE: unlike other MAM_* variables this one is intentionally NOT env-overridable. -MAM_VERSION="4.3.0" +MAM_VERSION="4.4.0" export MAM_VERSION AGENT_SESSIONS_YAML="${AGENT_SESSIONS_YAML:-$WORKSPACE_ROOT/.mam/agent-sessions.yaml}" diff --git a/.agents/skills/lib_py/loop/eager.py b/.agents/skills/lib_py/loop/eager.py index 0dd2dab..5e748dc 100644 --- a/.agents/skills/lib_py/loop/eager.py +++ b/.agents/skills/lib_py/loop/eager.py @@ -13,7 +13,7 @@ import sys from typing import List, Sequence # Framework version locked with framework constants -FRAMEWORK_VERSION = "4.3.0" +FRAMEWORK_VERSION = "4.4.0" CORE_MODULES: Sequence[str] = ( # Standard library modules diff --git a/.agents/skills/multi-agent-mux-create/SKILL.md b/.agents/skills/multi-agent-mux-create/SKILL.md index a605eb7..6255d8d 100644 --- a/.agents/skills/multi-agent-mux-create/SKILL.md +++ b/.agents/skills/multi-agent-mux-create/SKILL.md @@ -1,7 +1,7 @@ --- name: multi-agent-mux-create description: "Create a new agent session (claude, antigravity/agy) in a dedicated herdr session for context-preserving long-running work. Always creates a herdr session — never backgrounds with nohup/disown. Writes the new session to .mam/agent-sessions.yaml. Use when you want to start a fresh agent (no prior UUID) for a new project workspace." -version: 4.3.0 +version: 4.4.0 author: godopu license: MIT platforms: [linux, macos] diff --git a/.agents/skills/multi-agent-mux-delegate-job/SKILL.md b/.agents/skills/multi-agent-mux-delegate-job/SKILL.md index b2576f5..c6b9e51 100644 --- a/.agents/skills/multi-agent-mux-delegate-job/SKILL.md +++ b/.agents/skills/multi-agent-mux-delegate-job/SKILL.md @@ -1,7 +1,7 @@ --- name: multi-agent-mux-delegate-job description: "Delegate a unit of work to any autonomous agent (claude-code, hermes, agy, grok-build, codex, or a human) and observe it asynchronously over an MQTT event channel. Supported roles include orchestrator, worker, and reviewer." -version: 4.3.0 +version: 4.4.0 author: godopu license: MIT platforms: [linux, macos, windows] diff --git a/.agents/skills/multi-agent-mux-loop/SKILL.md b/.agents/skills/multi-agent-mux-loop/SKILL.md index ef972cb..c11ad48 100644 --- a/.agents/skills/multi-agent-mux-loop/SKILL.md +++ b/.agents/skills/multi-agent-mux-loop/SKILL.md @@ -1,7 +1,7 @@ --- name: multi-agent-mux-loop description: "Run an autonomous planning-execution-review loop using multiple agents (Planner, Creator, Reviewers) in the workspace. Automatically orchestrates plan discussion, code changes, and peer reviews until a unanimous PASS is achieved or the maximum iteration limit is reached." -version: 4.3.0 +version: 4.4.0 author: godopu license: MIT platforms: [linux, macos] diff --git a/.agents/skills/multi-agent-mux-monitor/SKILL.md b/.agents/skills/multi-agent-mux-monitor/SKILL.md index 881190f..ce7c8d2 100644 --- a/.agents/skills/multi-agent-mux-monitor/SKILL.md +++ b/.agents/skills/multi-agent-mux-monitor/SKILL.md @@ -1,7 +1,7 @@ --- name: multi-agent-mux-monitor description: "Run a long-lived reconciler that watches .mam/agent-sessions.yaml against the actual herdr/agent runtime state and reconciles them. Use when you want live visibility into which agent sessions are running, which are dead, which have stale YAML entries, and which have new session ids that haven't been recorded yet. Runs as a persistent loop (`reconcile.sh --subscribe`) that keeps going until it times out, idles out, or is interrupted." -version: 4.3.0 +version: 4.4.0 author: godopu license: MIT platforms: [linux, macos] diff --git a/.agents/skills/multi-agent-mux-orc-onboard/SKILL.md b/.agents/skills/multi-agent-mux-orc-onboard/SKILL.md index dfa203d..2497943 100644 --- a/.agents/skills/multi-agent-mux-orc-onboard/SKILL.md +++ b/.agents/skills/multi-agent-mux-orc-onboard/SKILL.md @@ -1,7 +1,7 @@ --- name: multi-agent-mux-orc-onboard description: "Register current or specified orchestrator session UUID into agent-sessions.yaml orchestrator_uuids list to prevent sub-agent discovery capture." -version: 4.3.0 +version: 4.4.0 author: godopu license: MIT platforms: [linux, macos] diff --git a/.agents/skills/multi-agent-mux-resume/SKILL.md b/.agents/skills/multi-agent-mux-resume/SKILL.md index d66aa53..1737115 100644 --- a/.agents/skills/multi-agent-mux-resume/SKILL.md +++ b/.agents/skills/multi-agent-mux-resume/SKILL.md @@ -1,7 +1,7 @@ --- name: multi-agent-mux-resume description: "Resume an existing agent (claude, antigravity/agy) conversation by UUID into a herdr session. Reads .mam/agent-sessions.yaml for the saved session/conversation id, spawns (or reuses) a herdr session of the matching name, and runs `claude -r ` or `agy --conversation ` inside. Use when you want to reattach to a previous session's context, or revive a session whose herdr died but the agent's conversation is still on disk." -version: 4.3.0 +version: 4.4.0 author: godopu license: MIT platforms: [linux, macos] diff --git a/.agents/skills/multi-agent-mux-status/SKILL.md b/.agents/skills/multi-agent-mux-status/SKILL.md index 09f91f6..20d8257 100644 --- a/.agents/skills/multi-agent-mux-status/SKILL.md +++ b/.agents/skills/multi-agent-mux-status/SKILL.md @@ -1,7 +1,7 @@ --- name: multi-agent-mux-status description: "Read-only instant snapshot of all agent herdr sessions — name, YAML status, herdr alive, pane cmd/cwd, resume UUID on disk, and any drift. No mutation. Reuses reconcile.sh --dry-run for the diff logic. Use when you want to know 'what's running RIGHT NOW' without spinning up the monitor loop." -version: 4.3.0 +version: 4.4.0 author: godopu license: MIT platforms: [linux, macos] diff --git a/.agents/skills/multi-agent-mux-stop/SKILL.md b/.agents/skills/multi-agent-mux-stop/SKILL.md index 2c8299d..5480fe8 100644 --- a/.agents/skills/multi-agent-mux-stop/SKILL.md +++ b/.agents/skills/multi-agent-mux-stop/SKILL.md @@ -1,7 +1,7 @@ --- name: multi-agent-mux-stop description: "Stop an agent herdr session (claude, antigravity/agy) and update .mam/agent-sessions.yaml. Default stops gracefully and marks status=stopped with conversation preserved for resume. Does NOT delete on-disk conversation artifacts (jsonl/db) — those are preserved unless --purge-conversation is passed. Use when ending a work session, switching to a different one, or cleaning up before a fresh start." -version: 4.3.0 +version: 4.4.0 author: godopu license: MIT platforms: [linux, macos] diff --git a/BOOTSTRAP.ko.md b/BOOTSTRAP.ko.md index 3adb998..7ee0193 100644 --- a/BOOTSTRAP.ko.md +++ b/BOOTSTRAP.ko.md @@ -2,7 +2,7 @@ 본 문서는 `herdr_agent_orchestration` 오케스트레이션 및 메시징 백플레인 워크플로우를 새로운 프로젝트에 도입하여 이식하고, 새로운 개발자/에이전트가 초기 가동을 시작할 때 수행해야 하는 환경 설정 및 구축 절차를 안내합니다. -[![Version: v4.1.3](https://img.shields.io/badge/version-v4.1.3-blue.svg)](VERSIONS.md) +[![Version: v4.4.0](https://img.shields.io/badge/version-v4.4.0-blue.svg)](VERSIONS.md) 새로운 에이전트는 이 안내서에 기술된 절차를 순차적으로 실행하여 초기 환경을 안정적으로 설정할 수 있습니다. diff --git a/BOOTSTRAP.md b/BOOTSTRAP.md index e919cd3..c761c07 100644 --- a/BOOTSTRAP.md +++ b/BOOTSTRAP.md @@ -2,7 +2,7 @@ This document guides you through the setup and initialization procedures required to adopt the `herdr_agent_orchestration` orchestration and messaging backplane workflow in a new project, enabling a new developer or agent to get up and running quickly. -[![Version: v4.1.3](https://img.shields.io/badge/version-v4.1.3-blue.svg)](VERSIONS.md) +[![Version: v4.4.0](https://img.shields.io/badge/version-v4.4.0-blue.svg)](VERSIONS.md) A new agent can follow the steps in this guide sequentially to establish a stable and reliable initial environment. diff --git a/README.ko.md b/README.ko.md index 7f3b912..b165ae5 100644 --- a/README.ko.md +++ b/README.ko.md @@ -2,7 +2,7 @@ Herdr와 MQTT 브로커를 기반으로 구축된 고신뢰성 **다중 에이전트 오케스트레이션 및 메시징 백플레인** 프레임워크입니다. `claude`, `agy`, `hermes`, `grok`, `opencode`, `codex` 등 다중 LLM 백엔드 에이전트 전반에 걸쳐 장시간 수행되는 작업(코드 생성, 리팩토링, 보안 검토 등)을 조정, 격리 및 감사할 수 있도록 설계되었습니다. -[![Version: v4.2.0](https://img.shields.io/badge/version-v4.2.0-blue.svg)](VERSIONS.md) +[![Version: v4.4.0](https://img.shields.io/badge/version-v4.4.0-blue.svg)](VERSIONS.md) --- diff --git a/README.md b/README.md index db9bab5..6990853 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ An advanced, high-reliability **Multi-Agent Orchestration & Messaging Backplane** framework built on Herdr and MQTT. It is designed to coordinate, isolate, and audit long-running agent tasks (such as code generation, refactoring, and security reviews) across multiple LLM backend clients (`claude`, `agy`, `hermes`, `grok`, `opencode`, `codex`). -[![Version: v4.2.0](https://img.shields.io/badge/version-v4.2.0-blue.svg)](VERSIONS.md) +[![Version: v4.4.0](https://img.shields.io/badge/version-v4.4.0-blue.svg)](VERSIONS.md) --- diff --git a/VERSIONS.md b/VERSIONS.md index ca0be7b..0179a6c 100644 --- a/VERSIONS.md +++ b/VERSIONS.md @@ -6,36 +6,79 @@ ## 📌 현재 버전 개요 (Current Release) -- **프레임워크 버전**: `v4.3.0` -- **최신 릴리스 일시**: 2026-09-06 (KST) -- **기준 브랜치**: `refactor` +- **프레임워크 버전**: `v4.4.0` +- **최신 릴리스 일시**: 2026-09-07 (KST) +- **기준 브랜치**: `main` (`refactor` 병합 완료) - **핵심 아키텍처**: - - **Codex First-Class Agent Integration**: OpenAI Codex CLI(`codex`)를 6번째 1급 에이전트 백엔드로 전면 통합 (`claude`, `agy`, `hermes`, `grok`, `opencode`, `codex`) - - **Class A Codex Lifecycle & State DB Resolution**: 동적 `state_*.sqlite` SQLite 인덱스 및 date-nested rollout jsonl 폴백 지원, `--dangerously-bypass-approvals-and-sandbox` 비대화형 실행 연동 - - **`lib_py.delegation` 패키지 추출 (Issue #5 Phase 1)**: 위임 스크립트 4종을 파이썬 패키지로 승격하고, 기존 `scripts/*.py` 경로는 패키지를 자기 옆 트리에 프로세스당 1회 바인딩하는 호환 shim으로 유지 - - **Runtime Framework Version Constant (`MAM_VERSION`) & 3-Way Lockstep**: `lib.sh` 내 `MAM_VERSION="4.3.0"` 런타임 진실 공급원 및 8개 스킬 3자 동기화 가드 유지 + - **Python 코어 마이그레이션 (Issue #5 Phase 1~4)**: 위임 스크립트 4종과 936줄 `run_loop.sh` 오케스트레이션을 `lib_py.delegation` / `lib_py.loop` 파이썬 패키지로 승격. `scripts/*.py` 경로와 `run_loop.sh` CLI는 와이어 계약이므로 호환 shim·얇은 래퍼로 보존 + - **CAS 단일 상태 전이 엔진 (`engine.py`)**: 잡 레코드 전이를 `dispatch_token` 선결 조건에 원자적으로 결속. 이중 디스패치·경합 병합을 구조적으로 차단 + - **섀도우 와처 및 자동 완료 게이트 (`shadow.py`, `autocomplete.py`)**: 관측 증거 기반 Would-Complete 판정을 섀도우로 먼저 축적하고, 실제 자동 완료는 **기본 비활성(`MAM_AUTO_COMPLETE=0`)** 옵트인으로만 동작 + - **Eager Import 불변 런타임 및 B-13 Freeze 제거 (`eager.py`)**: 기동 시 전 의존 모듈을 `sys.modules`에 상주시켜 실행 중 디스크 수정 크래시를 원천 차단하고, `/tmp/mam-loop-freeze.XXXXXX` 스냅샷 복사 우회책을 기본 경로에서 철거 + - **Strict 판정 파서 (`parser.py`)**: 마크다운 코드 펜스 내부의 `[VERDICT: ...]`를 판정에서 배제하고, 판정 부재·모호·미지 태그를 예외 없이 fail-closed `NOT PASS`로 처리 + - **4-Way 버전 락스텝**: `lib.sh`(`MAM_VERSION`) ↔ `lib_py/loop/eager.py`(`FRAMEWORK_VERSION`) ↔ `VERSIONS.md` ↔ 8개 `SKILL.md`. 런타임 불일치는 `verify_framework_version()`이 fail-closed 거부 --- ## 🧭 스킬 패키지 버전 매트릭스 (Skills Version Matrix) -모든 8개 스킬은 YAML frontmatter 메타데이터(`author`, `version`, `platforms`, `environments`) 표준화를 통해 `v4.3.0`으로 동기화되어 배포됩니다. +모든 8개 스킬은 YAML frontmatter 메타데이터(`author`, `version`, `platforms`, `environments`) 표준화를 통해 `v4.4.0`으로 동기화되어 배포됩니다. | 스킬명 | 버전 | 역할 및 주요 책임 | 상태 | | :--- | :---: | :--- | :---: | -| **`multi-agent-mux-create`** | `4.3.0` | 에이전트 세션 신규 생성 및 Herdr 컨테이너 격리 스폰 | ✅ 배포 | -| **`multi-agent-mux-stop`** | `4.3.0` | 대화 UUID 원자적 캡처 및 세션 안전 종료 (Graceful Stop) | ✅ 배포 | -| **`multi-agent-mux-resume`** | `4.3.0` | 온디스크 대화 컨텍스트 기반 Tier-1 초고속 세션 복원 | ✅ 배포 | -| **`multi-agent-mux-status`** | `4.3.0` | 실시간 Herdr 세션 및 레지스트리 드리프트 스냅샷 조회 | ✅ 배포 | -| **`multi-agent-mux-monitor`** | `4.3.0` | YAML ↔ 런타임 상태 간 자율 조정자 (Reconciler Loop) | ✅ 배포 | -| **`multi-agent-mux-delegate-job`** | `4.3.0` | MQTT 이벤트 채널 기반 비동기 단위 작업 위임 | ✅ 배포 | -| **`multi-agent-mux-loop`** | `4.3.0` | Planner-Creator-Reviewer 3자 자율 계획·실행·피어리뷰 루프 | ✅ 배포 | -| **`multi-agent-mux-orc-onboard`** | `4.3.0` | 오케스트레이터 UUID 격리 등록 및 서브 세션 오염 방지 | ✅ 배포 | +| **`multi-agent-mux-create`** | `4.4.0` | 에이전트 세션 신규 생성 및 Herdr 컨테이너 격리 스폰 | ✅ 배포 | +| **`multi-agent-mux-stop`** | `4.4.0` | 대화 UUID 원자적 캡처 및 세션 안전 종료 (Graceful Stop) | ✅ 배포 | +| **`multi-agent-mux-resume`** | `4.4.0` | 온디스크 대화 컨텍스트 기반 Tier-1 초고속 세션 복원 | ✅ 배포 | +| **`multi-agent-mux-status`** | `4.4.0` | 실시간 Herdr 세션 및 레지스트리 드리프트 스냅샷 조회 | ✅ 배포 | +| **`multi-agent-mux-monitor`** | `4.4.0` | YAML ↔ 런타임 상태 간 자율 조정자 (Reconciler Loop) | ✅ 배포 | +| **`multi-agent-mux-delegate-job`** | `4.4.0` | MQTT 이벤트 채널 기반 비동기 단위 작업 위임 | ✅ 배포 | +| **`multi-agent-mux-loop`** | `4.4.0` | Planner-Creator-Reviewer 3자 자율 계획·실행·피어리뷰 루프 | ✅ 배포 | +| **`multi-agent-mux-orc-onboard`** | `4.4.0` | 오케스트레이터 UUID 격리 등록 및 서브 세션 오염 방지 | ✅ 배포 | --- ## 📋 버전별 상세 변경 내역 (Changelog) +### 🚀 `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으로 보존하며, 프로덕션 동작·이벤트 스키마·자동완료 정책은 한 줄도 바뀌지 않는다. diff --git a/docs/contracts/issue-5/implementation_plan.md b/docs/contracts/issue-5/implementation_plan.md index b107033..d7a6182 100644 --- a/docs/contracts/issue-5/implementation_plan.md +++ b/docs/contracts/issue-5/implementation_plan.md @@ -21,12 +21,12 @@ | 단계 | 작업 목표 | 세부 구현 내용 | 필수 완료 게이트 (Gate Criteria) | 롤백 절차 (Rollback Path) | |---|---|---|---|---| -| **Phase 0**
*(현재)* | **사전 계약 고정 및 Fixture 수집** | • CLI, 이벤트, 리포트, 락 계약 기준선 문서화
• Herdr 0.8.2 네이티브 캡처 및 복합 Watcher 시나리오 2계층 분리
• 6개 백엔드 실측 증거 행렬 수집 | • 510개 전체 테스트 수집 및 회귀 0-Fail (100% 통과)
• Fixture 스키마 및 증거 경계 검증 통과
• 프로덕션 코드 무변경 diff 검증 | 미추적 산출물 즉시 삭제 (`rm -rf docs/contracts/issue-5 tests/fixtures/issue_5 tests/test_issue5_phase0_contracts.py`) | +| **Phase 0**
*(완료)* | **사전 계약 고정 및 Fixture 수집** | • CLI, 이벤트, 리포트, 락 계약 기준선 문서화
• Herdr 0.8.2 네이티브 캡처 및 복합 Watcher 시나리오 2계층 분리
• 6개 백엔드 실측 증거 행렬 수집 | • 510개 전체 테스트 수집 및 회귀 0-Fail (100% 통과)
• Fixture 스키마 및 증거 경계 검증 통과
• 프로덕션 코드 무변경 diff 검증 | 미추적 산출물 즉시 삭제 (`rm -rf docs/contracts/issue-5 tests/fixtures/issue_5 tests/test_issue5_phase0_contracts.py`) | | **Phase 1**
*(완료 v4.3.0)* | **`lib_py/delegation/` 추출** | • `lib.sh` L1833 이하 위임 헬퍼를 파이썬 서비스 클래스로 추출
• `start_watchdog`을 내부 명칭 `ensure_subscriber_daemon`으로 명명(기존 함수 별칭 보존)
• 장수 프로세스 단일 MQTT 연결 재사용 프로토타입 | • CLI/non-fatal/HMAC/seq/디스크 폴백 회귀 100% 통과
• 데몬 수명 및 PID 계약 일치 확인
• **자동완료 정책 변경 없음** | `MAM_DELEGATION_PY=0`으로 보존된 레거시 인라인 본체(`_*_legacy`)로 즉시 분기 (재배포·revert 불필요; 레거시 본체 제거는 Phase 4) | | **Phase 2a**
*(완료, shadow-only)* | **`lib_py/loop/` 코어 & Shadow Watcher** | • `engine.py` (단일 전이 주체), `watcher.py` (`herdr agent wait` 래핑) 구현
• 모든 Writer에 대한 단일 CAS 트랜잭션 경계 구현
• 자동완료를 수행하지 않고 "Would-Complete" 로깅만 수행하는 Shadow 모드 가동 | • 락 내 비동기/I/O 배제 검증
• 소켓 단절/스냅샷 재연결 백오프 검증
• Shadow 모드가 레지스트리 상태를 쓰지 않음을 검증 | 1) Shadow 모니터 프로세스 종료 및 인메모리 큐 폐기
2) CAS 서비스는 하위 호환 모드로 유지하거나 `MAM_ENGINE_CAS=0` 플래그로 비활성화하여 레거시 무조건 필드 병합(`update_job_status`)으로 복귀
3) 기존 셸 루프 상태 및 레코드 불변 유지 | | **Phase 2b**
*(완료, 기본 비활성)* | **이중 감지 자동완료 활성화 (Flag-Gated)** | • 4중 실행 귀속 + 리포트 확정 계약 + 단일 CAS 전이 게이트 연동
• Pending Notification 지연 알림 및 크래시 복구 구현
• 검증된 백엔드/작업에 한해 opt-in 플래그로 활성화 (기본값 OFF) | • 부수효과 작업 예외 및 무판정 fail-closed 테스트 완료
• 경쟁/재시작 시나리오 통과
• 플래그 OFF 시 기존 explicit event 경로 100% 보존 | 환경변수/플래그 `MAM_AUTO_COMPLETE=0` 설정; 이미 확정된 terminal 레코드는 보존하고 신규 자동완료만 중단 | -| **Phase 3** | **`run_loop.sh` Thin Wrapper 전환 및 전면 회귀** | • `run_loop.sh`를 인자 전달 래퍼(`exec python3 -m lib_py.loop "$@"`)로 전환
• 플래너/리뷰어/반론/승격 오케스트레이션 로직 완전 이식
• 엄격한 판정 파서(Strict Parser) 적용 | • 5개 subprocess 전용 테스트(~1,621줄) 포함 전체 481개+ 테스트 0-Fail
• 시그널, 루프 가드, 임의 CWD 및 공백 경로 검증 통과 | **우아한 핸드오프 시퀀스**:
1) Quiesce: 실행 중인 Python 루프 프로세스에 종료 신호를 전달하여 신규 디스패치 중단
2) Checkpoint: 현재 루프 회차, 활성 Job ID 및 리뷰 상태를 `.mam/loop-checkpoint.json`에 원자적 기록
3) Exit: Python 루프 정상 종료 및 가드 해제
4) Resume: 백업된 셸 원본 스크립트로 `run_loop.sh` 복원 후 체크포인트 기반 재개 검증 | -| **Phase 4** | **불변 런타임 검증 및 Freeze 제거** | • 기동 시 전체 모듈 상주(Eager Import) 및 자식 템플릿 불변성 검증
• 작업 도중 스킬 파일 교체 및 늦은 경로 스트레스 테스트 통과
• `/tmp/mam-loop-freeze.XXXXXX` 우회책 완전 제거 | • 런타임 자기수정 크래시(B-13) 재발 방지 검증
• Live Helper 버전 호환 게이트 검증 통과
• 운영 문서 및 배포 스크립트 갱신 | Freeze 복사 로직(`run_loop.sh:112-127`) 재활성화 및 라이브 헬퍼 버전 검사 복구 | +| **Phase 3**
*(완료 v4.4.0)* | **`run_loop.sh` Thin Wrapper 전환 및 전면 회귀** | • `run_loop.sh`를 인자 전달 래퍼(`exec python3 -m lib_py.loop "$@"`)로 전환
• 플래너/리뷰어/반론/승격 오케스트레이션 로직 완전 이식
• 엄격한 판정 파서(Strict Parser) 적용 | • 5개 subprocess 전용 테스트(~1,621줄) 포함 전체 481개+ 테스트 0-Fail
• 시그널, 루프 가드, 임의 CWD 및 공백 경로 검증 통과 | **우아한 핸드오프 시퀀스**:
1) Quiesce: 실행 중인 Python 루프 프로세스에 종료 신호를 전달하여 신규 디스패치 중단
2) Checkpoint: 현재 루프 회차, 활성 Job ID 및 리뷰 상태를 `.mam/loop-checkpoint.json`에 원자적 기록
3) Exit: Python 루프 정상 종료 및 가드 해제
4) Resume: 백업된 셸 원본 스크립트로 `run_loop.sh` 복원 후 체크포인트 기반 재개 검증 | +| **Phase 4**
*(완료 v4.4.0)* | **불변 런타임 검증 및 Freeze 제거** | • 기동 시 전체 모듈 상주(Eager Import) 및 자식 템플릿 불변성 검증
• 작업 도중 스킬 파일 교체 및 늦은 경로 스트레스 테스트 통과
• `/tmp/mam-loop-freeze.XXXXXX` 우회책 완전 제거 | • 런타임 자기수정 크래시(B-13) 재발 방지 검증
• Live Helper 버전 호환 게이트 검증 통과
• 운영 문서 및 배포 스크립트 갱신 | Freeze 복사 로직(`run_loop.sh:112-127`) 재활성화 및 라이브 헬퍼 버전 검사 복구 | --- diff --git a/docs/contracts/issue-5/phase4-freeze-removal.md b/docs/contracts/issue-5/phase4-freeze-removal.md index 6ba6794..9f872e0 100644 --- a/docs/contracts/issue-5/phase4-freeze-removal.md +++ b/docs/contracts/issue-5/phase4-freeze-removal.md @@ -36,7 +36,8 @@ Phase 3에서 `run_loop.sh`가 Python `lib_py.loop`로 위임됨에 따라, Bash - 기존 테스트의 락/가드 해제 계약(`_mam_release_guard`)은 외부 주입 스냅샷 디렉터리가 있을 경우 그대로 안전하게 정리합니다. ### 2-3. 라이브 헬퍼 버전 호환성 게이트 (`MAM_VERSION`) -- `lib.sh`의 `MAM_VERSION`("4.3.0")과 프로세스 런타임의 프로토콜 버전을 동기화하고, 불일치 발생 시 안전하게 fail-closed 거부합니다. +- `lib.sh`의 `MAM_VERSION`과 프로세스 런타임의 프로토콜 버전(`eager.FRAMEWORK_VERSION`, 현재 `4.4.0`)을 동기화하고, 불일치 발생 시 안전하게 fail-closed 거부합니다. +- 이 게이트 때문에 프레임워크 트리는 **부분 갱신이 불가능**합니다. 두 상수 중 하나만 배포된 혼재 트리는 루프 기동 시점에 `RuntimeError`로 거부되므로, 배포는 항상 트리 단위로 수행합니다. --- diff --git a/docs/contracts/issue-5/task.md b/docs/contracts/issue-5/task.md index 1191beb..54e2ff8 100644 --- a/docs/contracts/issue-5/task.md +++ b/docs/contracts/issue-5/task.md @@ -1,8 +1,8 @@ # 📑 Issue #5 Task Checklist & Verification Evidence -- **Document Version**: Rev.8 (Phase 3 실행 완료 — P3-01~P3-03 증거 기록) +- **Document Version**: Rev.9 (Phase 4 완료 및 `v4.4.0` 릴리스 버전 락스텝 반영) - **Issue**: [Gitea Issue #5](https://git.godopu.com/tmpl/multi-agent-mux/issues/5) -- **Status**: Phase 0 Complete / **Phase 1 Complete (v4.3.0)** / **Phase 2a Complete (shadow-only)** / **Phase 2b Complete (default-off)** / **Phase 3 Complete (thin wrapper)** / Phase 4 Staged +- **Status**: Phase 0 Complete / **Phase 1 Complete (v4.3.0)** / **Phase 2a Complete (shadow-only)** / **Phase 2b Complete (default-off)** / **Phase 3 Complete (thin wrapper)** / **Phase 4 Complete (freeze removed)** — 전 단계 `main` 병합 및 `v4.4.0` 릴리스 완료 --- @@ -131,7 +131,7 @@ ## Phase 4: 불변 런타임 검증 및 Freeze 제거 (완료) - [x] **P4-01** eager import+child/helper/template 실행 버전 정책 구현/검증. - - *증거*: `.agents/skills/lib_py/loop/eager.py` 구현 및 `runner.LoopRunner.__init__`에서 최우선 호출. 모든 표준/프레임워크 코어 모듈(47개+)을 기동 시점에 `sys.modules`에 상주시킴. `sys.dont_write_bytecode = True` 및 `PYTHONDONTWRITEBYTECODE=1` 환경 설정으로 `.pyc` 디스크 오염 원천 차단. `FRAMEWORK_VERSION="4.3.0"`과 `MAM_VERSION` 불일치 시 fail-closed 검증 (`test_p4_01`). + - *증거*: `.agents/skills/lib_py/loop/eager.py` 구현 및 `runner.LoopRunner.__init__`에서 최우선 호출. 모든 표준/프레임워크 코어 모듈(47개+)을 기동 시점에 `sys.modules`에 상주시킴. `sys.dont_write_bytecode = True` 및 `PYTHONDONTWRITEBYTECODE=1` 환경 설정으로 `.pyc` 디스크 오염 원천 차단. `FRAMEWORK_VERSION`(현재 `4.4.0`)과 `MAM_VERSION` 불일치 시 fail-closed 검증 (`test_p4_01`). 두 상수의 값 일치는 `tests/test_version_consistency.py`가 소유하며, `test_p4_01`은 게이트 동작만 검증하므로 릴리스 버전 상승 시 이 테스트를 수정할 필요가 없다. - [x] **P4-02** 실행 중 코드 교체와 늦은 재심/승격/rollback 스트레스 검증. - *증거*: `tests/test_issue5_phase4_runtime.py::test_p4_02_midflight_code_replacement_stress`. 실행 중인 서브프로세스의 디스크 상 소스 파일 및 워커 모듈을 문법 오류(`INVALID SYNTAX`, `unexpected EOF`)로 치환/파괴한 상태에서도 메모리 상주 바이트코드와 AST를 통해 판정 파싱(`parser.parse_verdict`), 체크포인트 원자적 기록/복구(`checkpoint.save_checkpoint`/`load_checkpoint`)가 무결하게 완결됨을 실증. - [x] **P4-03** gate 통과 후에만 Freeze 제거 및 운영 문서 갱신. diff --git a/tests/conftest.py b/tests/conftest.py index 742d657..80be269 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1065,6 +1065,31 @@ print(str(uuid.uuid4())) # lib_py, which is why test_tier1_unit.py's inserts stay legal.) # ============================================================================== +# ============================================================================== +# Ambient MAM_VERSION isolation. +# +# lib.sh exports MAM_VERSION, so any shell that has sourced it — an agent +# session, a developer's terminal — hands that value down to pytest and on to +# every subprocess the suite spawns. lib_py.loop.eager.eager_load() compares it +# against FRAMEWORK_VERSION and refuses to start on a mismatch, so an inherited +# value from a *pre-upgrade* shell makes the loop tests fail against a tree that +# is perfectly consistent with itself. +# +# The suite must measure the tree under test, not the version its launcher +# happened to source, so the ambient value is dropped for the whole session. +# Tests that exercise the gate set MAM_VERSION explicitly and are unaffected. +# ============================================================================== + +@pytest.fixture(scope="session", autouse=True) +def _drop_ambient_mam_version(): + previous = os.environ.pop("MAM_VERSION", None) + try: + yield + finally: + if previous is not None: + os.environ["MAM_VERSION"] = previous + + @pytest.fixture(scope="session", autouse=True) def delegation_tree_provenance_guard(tmp_path_factory): yield diff --git a/tests/test_issue5_phase4_runtime.py b/tests/test_issue5_phase4_runtime.py index 465e6e2..6b114d7 100644 --- a/tests/test_issue5_phase4_runtime.py +++ b/tests/test_issue5_phase4_runtime.py @@ -36,9 +36,11 @@ def test_p4_01_eager_import_completeness_and_bytecode_inhibition(monkeypatch): assert sys.dont_write_bytecode is True assert os.environ.get("PYTHONDONTWRITEBYTECODE") == "1" - # Verify framework version constant sync - assert eager.FRAMEWORK_VERSION == "4.3.0" - eager.verify_framework_version("4.3.0") + # Verify framework version constant sync. The literal value is not pinned + # here: tests/test_version_consistency.py owns the lockstep between + # eager.FRAMEWORK_VERSION, lib.sh's MAM_VERSION, VERSIONS.md and the eight + # SKILL.md files, so a release bump must not have to edit this file too. + eager.verify_framework_version(eager.FRAMEWORK_VERSION) # Verify mismatch raises RuntimeError monkeypatch.setenv("MAM_VERSION", "9.9.9") diff --git a/tests/test_version_consistency.py b/tests/test_version_consistency.py index be22136..f155224 100644 --- a/tests/test_version_consistency.py +++ b/tests/test_version_consistency.py @@ -11,6 +11,7 @@ REPO_ROOT = Path(__file__).resolve().parent.parent LIB_SH = REPO_ROOT / ".agents" / "skills" / "lib.sh" VERSIONS_MD = REPO_ROOT / "VERSIONS.md" SKILLS_DIR = REPO_ROOT / ".agents" / "skills" +EAGER_PY = SKILLS_DIR / "lib_py" / "loop" / "eager.py" SKILL_NAMES = [ "multi-agent-mux-create", @@ -46,6 +47,15 @@ def _get_versions_md_versions(): return current_framework_ver, matrix_dict +def _get_eager_py_framework_version(): + """Read FRAMEWORK_VERSION statically — importing lib_py.loop.eager would + pull the whole framework onto sys.path just to read one constant.""" + text = EAGER_PY.read_text(encoding="utf-8") + m = re.search(r'^FRAMEWORK_VERSION\s*=\s*"(\d+\.\d+\.\d+)"', text, re.M) + assert m, f"Failed to parse FRAMEWORK_VERSION in {EAGER_PY}" + return m.group(1) + + def _get_skill_md_frontmatter_version(skill_name: str): skill_file = SKILLS_DIR / skill_name / "SKILL.md" assert skill_file.is_file(), f"Skill file not found: {skill_file}" @@ -78,6 +88,45 @@ def test_three_way_version_lockstep(): fm_ver = _get_skill_md_frontmatter_version(skill) assert fm_ver == lib_ver, f"{skill}/SKILL.md version ({fm_ver}) != lib.sh ({lib_ver})" + # 4. Runtime constant in lib_py/loop/eager.py. + eager_ver = _get_eager_py_framework_version() + assert eager_ver == lib_ver, ( + f"eager.py FRAMEWORK_VERSION ({eager_ver}) != lib.sh MAM_VERSION ({lib_ver})" + ) + + +def test_runtime_version_gate_rejects_a_partially_bumped_tree(): + """A tree where only one of the two runtime constants was bumped must not run. + + lib.sh exports MAM_VERSION into the environment and eager_load() compares it + against FRAMEWORK_VERSION, so a half-updated deployment fails closed at loop + startup rather than running two versions of the framework against each other. + This guards the mechanism; the values themselves are pinned by the lockstep + test above. + """ + import importlib.util + + spec = importlib.util.spec_from_file_location("_eager_under_test", EAGER_PY) + eager = importlib.util.module_from_spec(spec) + spec.loader.exec_module(eager) + + assert eager.FRAMEWORK_VERSION == _get_lib_sh_mam_version() + + env_before = os.environ.get("MAM_VERSION") + os.environ["MAM_VERSION"] = "0.0.0-stale" + try: + try: + eager.verify_framework_version() + except RuntimeError as exc: + assert "MAM_VERSION mismatch" in str(exc) + else: + raise AssertionError("a stale MAM_VERSION was accepted by the runtime gate") + finally: + if env_before is None: + os.environ.pop("MAM_VERSION", None) + else: + os.environ["MAM_VERSION"] = env_before + def test_mam_version_is_not_env_overridable(): """T-0 (Constraint 1): MAM_VERSION cannot be spoofed by caller environment."""