merge: fix/u1e-workspace-root (u1e no longer writes the checkout's .mam/shim/herdr; v4.9.3)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
This commit is contained in:
GodopuandClaude Opus 5.5 committed 2026-10-13 12:33:10 +09:00
commit e32c957464
26 files changed
+304 -26

No files matched your search

@@ -0,0 +1,14 @@
{
"loop_id": "loop-20261013T025803Z-c69408",
"base_sha": "42e2a7ea9bd3e2770ed8626d789193428ca3aa8b",
"head_sha": "3a30d7da894cdac26b7ec054b4e66fe9fa3dc8db",
"tree_sha": "bd43e4642f0109091f5f497b2703dcbaf8e2616b",
"review_commit": "3a30d7da894cdac26b7ec054b4e66fe9fa3dc8db",
"reviewer": "planner-reviewer-claude-01",
"job_id": "495a1373",
"verdict": "PASS",
"transport": "herdr",
"evidence": "review-tree",
"isolation": "worktree",
"verified_at": "2026-10-13T03:32:19Z"
}
@@ -0,0 +1,34 @@
# 테스트 격리 수정(u1e 의 WORKSPACE_ROOT) 리뷰 — 커밋 3a30d7d (job 495a1373)
- 리뷰어: planner-reviewer-claude-01 (Reviewer)
- 리뷰 identity: loop-20261013T025803Z-c69408 / base 42e2a7ea .. head 3a30d7da (커밋 1개, 테스트 2 파일) / tree bd43e464
- 검증 경로: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T025803Z-c69408/1` — HEAD·tree 일치, `git status --porcelain` 0 줄, **`.mam` 없음**(시작·종료 모두). 본 체크아웃 HEAD 42e2a7e(main) 변동 없음.
- 실행 안전: 모든 실행은 `env -i`(PATH/HOME/LANG/TMPDIR 만)로 브리프의 `-u` 목록을 포함한 라우팅 변수를 비웠고, basetemp 는 scratchpad 아래(이름에 "shim" 없음). 수정 전 재현은 검증 경로를 `git clone --no-hardlinks` 한 임시 복사본에서만 했고 거기 생긴 `.mam` 은 지웠다. 검증 경로에서 돌린 것은 W1/W2 뿐이며(W1 은 체크아웃을 tmp 로 복사해 그 안에서 노드를 돌린다), 끝난 뒤 검증 경로에 `.mam` 이 없음을 확인했다. 실제 `.mam`·`.mam.env`·모니터·실제 서버·에이전트 홈 미접촉.
## 1. 원인과 수정
- 원인(1차 README 리뷰에서 재현한 그대로): `test_u1e` 는 래퍼를 `subprocess.run(["bash", WRAPPER, "submit", …, "--dry-run"], env=dict(os.environ, HOME=…, MAM_ENV_FILE=…), cwd=tmp_path)` 로 부르는데, 래퍼가 source 하는 `lib.sh` 는 `WORKSPACE_ROOT` 가 없으면 체크아웃을 기본값으로 삼고(`lib.sh:26`) `_init_herdr_isolation` 이 `$WORKSPACE_ROOT/.mam/shim/herdr` 를 쓴다(`lib.sh:150-154`). conftest 의 `pytest_configure` 는 다른 체크아웃을 가리키는 `WORKSPACE_ROOT` 만 떨어뜨리므로 pane 밖에서는 변수가 없고, 체크아웃이 기본값이 된다.
- 수정(`tests/test_issue50_dispatch.py:167-170`): env 에 `WORKSPACE_ROOT=str(tmp_path)` 추가 → shim 은 `tmp_path/.mam/shim/herdr` 에 쓰인다. `tests/test_issue17_workspace_root_isolation.py:43`: 이 노드를 W1 `NODES` 에 추가. 두 파일 `py_compile` 통과. 같은 파일의 다른 테스트는 `decide.*` 를 in-process 로 부르므로(`_register`/`_edit`/`_event` 헬퍼) shim 을 쓰지 않는다 — 아래 §2 의 파일 전체 실행이 이를 확인.
## 2. 실행 결과
| 실험 | 결과 |
|---|---|
| **수정 전 복사본**(검증 경로 clone + `test_issue50_dispatch.py` 를 base 42e2a7e 로 되돌림): u1e 단독 | 1 passed, 복사본에 **`.mam/shim/herdr` 생성** — 원인 재현 |
| **수정 전 복사본**: W1(새 NODES 포함) | **FAIL**: `AssertionError: tests wrote into the checkout they ran in: ['.mam/shim', '.mam/shim/herdr']` — W1 이 이 노드로 회귀를 잡는다(복사본 안의 tmp 복사본에만 쓰였고 복사본 자체에는 `.mam` 없음) |
| **head, 검증 경로**: `test_issue17_workspace_root_isolation.py` (W1+W2) | **2 passed**, 검증 경로 `.mam` 없음 |
| **head 복사본**(`git archive`): `test_issue50_dispatch.py` 전체 | **49 passed**, 복사본 `.mam` 없음 |
W1 이 잡는 원리: W1 은 복사본에 `WORKSPACE_ROOT=<복사본>` 을 주고 노드를 돌린다. 수정 전 u1e 는 `os.environ` 을 그대로 물려주므로 래퍼가 그 복사본에 shim 을 쓰고, 수정 후는 `tmp_path` 로 덮어써 복사본에 아무것도 남지 않는다. pane 밖(변수 없음)에서도 lib.sh 의 체크아웃 기본값 때문에 같은 결과이므로 두 환경 모두를 대표한다.
## 3. 같은 결함이 남은 테스트가 있는가
- 정적: 래퍼·`lib.sh`·`run_loop.sh`·`create_session.sh`·`reconcile.sh`·`stop/resume_session.sh` 를 subprocess 로 부르는 테스트 파일 59 개를 추려 `mam_sandbox`/`own_workspace_root`/`WORKSPACE_ROOT` 참조 여부를 봤다. 참조가 하나도 없는 7 파일과, 참조가 적어 테스트별 누락이 있을 수 있는 18 파일(W1 NODES 에 없는 것 위주)을 골랐다.
- 경험적: 그 **25 파일을 head 의 `git archive` 복사본에서 파일마다 따로** 돌리고 매번 복사본의 `.mam` 을 봤다 — `test_b7_diff_untracked`, `test_deploy_freshness`, `test_deploy_layout`, `test_issue42_skill_blocks`, `test_issue45_herdr_config`, `test_issue5_phase0_contracts`, `test_local_mqtt`, `test_issue14_delivery_ack`, `test_issue18_liveness_contract`, `test_issue6_turn_handshake`, `test_issue26_envfile`, `test_issue26_ints`, `test_issue33_budget`, `test_layout`, `test_workspace_scope`, `test_a4_adapter_contract`, `test_issue45_restore`, `test_sanitize_and_mock_errors`, `test_sanity`, `test_issue12_registry_secrets`, `test_issue21_acceptance`, `test_issue15_registry_isolation`, `test_issue20_state_isolation`, `test_issue36_prompt_transport`, `test_issue34_job_tree`: 전부 통과(skip 은 tomllib/nats-docker 류), **`.mam` 생성 0 건**.
- 이전 리뷰들에서 같은 방식으로 돌린 파일들(leak·startup_path·shim_contract·issue17/9/45, scenario4, pane_lock, issue50_dispatch_herdr 등)도 검증 경로에 `.mam` 을 남기지 않았고, `.mam/shim` 이 생긴 경우는 모두 `test_issue50_dispatch.py` 가 포함된 실행뿐이었다(u1e). 즉 이 결함은 u1e 하나였고, 수정으로 닫혔다.
## 4. 잔류물
- clone 복사본·archive 복사본·basetemp·로그는 scratchpad 아래. 검증 경로는 깨끗(`.mam` 없음, `git status` 0 줄). #55 가드 오탐은 이번 실행에서 나오지 않았다.
## 결론
원인 진단이 맞고, `WORKSPACE_ROOT=tmp_path` 가 정확한 최소 수정이며, W1 은 수정 전 트리에서 이 노드로 실패하고 수정 후 통과한다. 다른 테스트에서 같은 누출은 25 파일의 파일별 실행으로 재현되지 않았다.
[VERDICT: PASS]
@@ -0,0 +1,14 @@
{
"loop_id": "loop-20261013T025803Z-c69408",
"base_sha": "42e2a7ea9bd3e2770ed8626d789193428ca3aa8b",
"head_sha": "3a30d7da894cdac26b7ec054b4e66fe9fa3dc8db",
"tree_sha": "bd43e4642f0109091f5f497b2703dcbaf8e2616b",
"review_commit": "3a30d7da894cdac26b7ec054b4e66fe9fa3dc8db",
"reviewer": "planner-reviewer-codex-01",
"job_id": "8db89564",
"verdict": "PASS",
"transport": "herdr",
"evidence": "review-tree",
"isolation": "worktree",
"verified_at": "2026-10-13T03:32:19Z"
}
@@ -0,0 +1,54 @@
# 테스트 격리 수정 리뷰 — 8db89564
판정: PASS. u1e의 체크아웃 쓰기 원인을 정확히 수정했고, 추가된 W1 노드가 그 수정의 제거를 실제로 검출합니다. 차단할 결함은 발견하지 못했습니다.
## Identity 및 변경 범위
- loop: `loop-20261013T025803Z-c69408`
- base: `42e2a7ea9bd3e2770ed8626d789193428ca3aa8b`
- head: `3a30d7da894cdac26b7ec054b4e66fe9fa3dc8db`
- tree: `bd43e4642f0109091f5f497b2703dcbaf8e2616b`
- 검증 경로: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T025803Z-c69408/1`
- 변경은 테스트 두 파일뿐입니다. 시작과 종료 시 HEAD/tree 일치 및 clean 상태를 확인했고, 종료 시 리뷰 체크아웃에 `.mam`이 없음을 확인했습니다. 리뷰 트리나 creator 작업 트리를 수정하지 않았습니다.
## 원인과 수정 검토
`tests/test_issue50_dispatch.py:169–170`은 래퍼 subprocess에 `WORKSPACE_ROOT=str(tmp_path)`를 명시하여 상속된 루트와 체크아웃 기본값을 모두 덮어씁니다.
원인은 `.agents/skills/lib.sh:26`이 변수 미설정 시 라이브러리 위치에서 체크아웃 루트를 계산하고, 3168행에서 `_init_herdr_isolation`을 무조건 호출하기 때문입니다. 이 함수의 150–157행은 `$WORKSPACE_ROOT/.mam/shim`을 만들고 shim 파일을 씁니다. 래퍼 `.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job:53`은 dry-run 처리를 하기 전에 이 라이브러리를 source합니다. 따라서 기존의 임시 HOME/cwd 설정만으로는 격리가 되지 않으며, 이번 명시적 루트 설정이 필요한 최소 수정입니다.
u1e의 원래 검증(미전달 dry-run 레코드는 admission을 막지 않고, running 또는 일반 pending 레코드는 막음)은 175–186행에 그대로 남아 있습니다. 새 환경 설정이 테스트를 우회하거나 assertion을 약화하지 않습니다.
`tests/test_issue17_workspace_root_isolation.py:43`의 노드 추가는 적절합니다. W1은 복사본의 루트를 의도적으로 상속시키고(62행), 내부 테스트가 모두 실행·통과했는지 검사한 뒤(67–69행), 복사본 `.mam`의 생성 자체를 확인합니다(70–72행). 이 때문에 u1e가 환경을 다시 덮어쓰지 않으면 회귀를 잡습니다.
## 실행 검증
아티팩트 루트: `/tmp/review-8db89564-gw9g__7l`
1. **수정된 리뷰 트리: 51 passed, 68.05초, 종료 코드 0.**
- 대상: `tests/test_issue50_dispatch.py` 전체와 `tests/test_issue17_workspace_root_isolation.py` 전체(W1/W2 포함).
- 로그: `/tmp/review-8db89564-gw9g__7l/positive.log`.
- W1 내부 노드 실행의 통과·누수 부재 검사도 통과했습니다.
2. **수정 제거 대조군: W1이 기대한 위치에서 실패, 30.06초, pytest 종료 코드 1.**
- 현재 HEAD의 추적 파일을 임시 체크아웃으로 복사했습니다. 새 W1 NODES는 유지하고, `tests/test_issue50_dispatch.py`만 base 버전으로 교체했습니다.
- W1의 내부 테스트 실행 및 통과 수 검사를 지난 뒤, 71행에서 `tests wrote into the checkout they ran in: ['.mam/shim', '.mam/shim/herdr']`로 실패했습니다.
- 이는 단순 실행 오류가 아니라 원래 누수에 대한 회귀 검출입니다.
- 로그: `/tmp/review-8db89564-gw9g__7l/negative.log`.
- 수정 전 코드와 누수가 생긴 중첩 복사본을 포함한 negative 임시 트리는 검증 뒤 삭제했습니다.
3. **`git diff --check 42e2a7e..HEAD`: 성공.**
두 실행은 요구된 `env -u HERDR_SOCKET_PATH -u HERDR_PANE_ID -u HERDR_TAB_ID -u HERDR_WORKSPACE_ID -u HERDR_SESSION_NAME -u HERDR_SESSION -u WORKSPACE_ROOT -u MAM_ENV_FILE`을 적용했습니다. 외부 가상환경 Python, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider` 및 리뷰 트리 밖 basetemp를 사용했습니다. 시작 환경의 HOME/XDG_CONFIG_HOME도 임시 경로로 설정하고 에이전트·제어 평면 관련 상속 변수를 제거했습니다. 실제 서버나 에이전트를 실행하지 않았으며, #55의 다른 에이전트 대화 파일 관련 오탐도 발생하지 않았습니다.
## 유사 경로 점검 및 한계
래퍼 실행 및 `source lib.sh` 검색 결과의 주요 테스트 헬퍼를 추가로 검토했습니다. 살펴본 경로에서는 동일한 누락을 발견하지 못했습니다.
- `test_issue18_liveness_contract.py:54–60`, `test_issue14_delivery_ack.py:32–36`, `test_issue33_budget.py:42–47`, `test_issue34_job_tree.py:50–64`, `test_issue36_prompt_transport.py:47–61`, `test_issue26_envfile.py:73–76`, `test_issue26_ints.py:127–129`: 명시적으로 임시 WORKSPACE_ROOT 설정.
- `test_issue17_item4_decisions.py:225–233`, 689–699: 래퍼 source/submit 환경에서 임시 루트를 고정.
- `test_issue6_fail_closed.py:21–23`, `test_version_consistency.py:11–13`, `test_b19_headless_reconcile_fixes.py:11–13`, `test_b8_send_keys_verification.py:5–7`: 모듈의 `own_workspace_root` 픽스처 사용.
- `test_issue21_worktree_prereqs.py`의 `_dry_submit` 호출부 92–149행은 루트를 전달하며, legacy loop 테스트 398–408행은 샌드박스 루트를 명시합니다.
- `test_issue20_state_isolation.py:192–202`, `test_workspace_scope.py:30–32`, `test_layout.py:318`: 호출 환경에서 루트 지정. `test_a4_adapter_contract.py`의 `test_wait_for_tui_ready_missing_tokens_diagnostic` 및 `test_issue5_phase1_delegation.py`의 `_run_helper`는 샌드박스 픽스처·복사본을 사용합니다.
전체 테스트 스위트를 실행하거나 모든 동적 호출 경로의 무누수를 증명한 것은 아닙니다. 이번 수정에 직접 관련된 51건 및 W1의 수정 제거 검증으로 동작과 회귀 방지 효과를 확인했습니다.
[VERDICT: PASS]
@@ -0,0 +1,14 @@
{
"loop_id": "loop-20261013T025803Z-c69408",
"base_sha": "42e2a7ea9bd3e2770ed8626d789193428ca3aa8b",
"head_sha": "3a30d7da894cdac26b7ec054b4e66fe9fa3dc8db",
"tree_sha": "bd43e4642f0109091f5f497b2703dcbaf8e2616b",
"review_commit": "3a30d7da894cdac26b7ec054b4e66fe9fa3dc8db",
"reviewer": "reviewer-agy-01",
"job_id": "4be97273",
"verdict": "PASS",
"transport": "herdr",
"evidence": "review-tree",
"isolation": "worktree",
"verified_at": "2026-10-13T03:32:19Z"
}
@@ -0,0 +1,86 @@
# Code Review Report: `fix/u1e-workspace-root` (Test Isolation Fix for `test_u1e`)
- **Job ID**: `4be97273`
- **Reviewer**: `agy` (Session: `herdr:reviewer-agy-01`, Role: `Reviewer`)
- **Date**: 2026-10-13
- **Target Branch**: `fix/u1e-workspace-root`
- **Base Commit**: `42e2a7ea9bd3e2770ed8626d789193428ca3aa8b`
- **Head Commit**: `3a30d7da894cdac26b7ec054b4e66fe9fa3dc8db`
- **Verification Path**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T025803Z-c69408/1`
- **Verification Environment**: macOS (Darwin 24.3.0, arm64), Python 3.9.6, pytest-8.4.2
---
## 1. Executive Summary
This review evaluated commit [`3a30d7da894cdac26b7ec054b4e66fe9fa3dc8db`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T025803Z-c69408/1), which fixes a test workspace isolation leak discovered during earlier review runs in [`tests/test_issue50_dispatch.py::test_u1e_a_dry_run_record_is_not_an_open_job`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T025803Z-c69408/1/tests/test_issue50_dispatch.py#L162-L174) and adds regression coverage to [`tests/test_issue17_workspace_root_isolation.py`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T025803Z-c69408/1/tests/test_issue17_workspace_root_isolation.py).
The root cause was confirmed, the fix was verified both positively and negatively (reproducing failure on base, success on head), and an exhaustive audit across `tests/` confirmed no other tests suffer from this defect.
**Verdict: PASS**.
---
## 2. Root Cause Analysis
In [`tests/test_issue50_dispatch.py`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T025803Z-c69408/1/tests/test_issue50_dispatch.py), `test_u1e_a_dry_run_record_is_not_an_open_job` invokes `subprocess.run` on `WRAPPER` (`multi-agent-mux-delegate-job`).
1. `WRAPPER` sources [`.agents/skills/lib.sh`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T025803Z-c69408/1/.agents/skills/lib.sh).
2. In `lib.sh:26`, if `WORKSPACE_ROOT` is unset in the execution environment, it defaults to:
```bash
WORKSPACE_ROOT="${WORKSPACE_ROOT:-$(cd "$SKILL_DIR/../.." && pwd)}"
```
which points to the repository checkout root.
3. In `lib.sh:149-155` and `lib.sh:3168`, `_init_herdr_isolation` automatically creates and populates `$WORKSPACE_ROOT/.mam/shim/herdr`.
4. Because `u1e` previously created a custom `env` containing `HOME` and `MAM_ENV_FILE` but omitted `WORKSPACE_ROOT`, executing `u1e` wrote directly into `<checkout>/.mam/shim/herdr`.
---
## 3. Verification of Fix & Regression Coverage
### 3.1 The Fix
In [`tests/test_issue50_dispatch.py:169-170`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T025803Z-c69408/1/tests/test_issue50_dispatch.py#L169-L170), `WORKSPACE_ROOT=str(tmp_path)` was added to `env`:
```python
env = dict(os.environ, HOME=str(tmp_path), MAM_ENV_FILE=str(tmp_path / ".mam.env.absent"),
WORKSPACE_ROOT=str(tmp_path))
```
This guarantees that any `.mam/shim` creation is strictly contained within `tmp_path`.
In [`tests/test_issue17_workspace_root_isolation.py:43`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T025803Z-c69408/1/tests/test_issue17_workspace_root_isolation.py#L43), `"tests/test_issue50_dispatch.py::test_u1e_a_dry_run_record_is_not_an_open_job"` was appended to `NODES`. Test W1 copies the checkout into a temporary sandbox and executes every node in `NODES`, asserting that no `.mam` directory is created in the copy.
### 3.2 Negative Reproduction (Pre-fix Validation)
In an isolated temporary clone checked out at base commit `42e2a7ea9bd3e2770ed8626d789193428ca3aa8b`, `u1e` was added to `NODES` before the fix. Running W1 failed immediately with:
```text
AssertionError: tests wrote into the checkout they ran in: ['.mam/shim', '.mam/shim/herdr']
assert ['.mam/shim',...m/shim/herdr'] == []
FAILED tests/test_issue17_workspace_root_isolation.py::test_w1_no_test_writes_into_the_checkout_it_runs_in
```
This confirms that W1 effectively catches the defect.
### 3.3 Positive Execution (Post-fix Validation)
Ran [`tests/test_issue17_workspace_root_isolation.py`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T025803Z-c69408/1/tests/test_issue17_workspace_root_isolation.py) in the verification directory with sanitized environment and isolated `--basetemp`:
```text
tests/test_issue17_workspace_root_isolation.py .. [100%]
============================== 2 passed in 30.47s ==============================
```
Verified that no `.mam` was created in the verification tree (`ls: .mam: No such file or directory`, git status clean).
---
## 4. Codebase Audit for Similar Invocations
An automated search across all test modules in [`tests/`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T025803Z-c69408/1/tests) identified all other locations where `WRAPPER` or `lib.sh` are invoked:
- [`test_issue14_delivery_ack.py`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T025803Z-c69408/1/tests/test_issue14_delivery_ack.py): `_base_env` explicitly sets `env["WORKSPACE_ROOT"] = str(cwd)`.
- [`test_issue17_item4_decisions.py`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T025803Z-c69408/1/tests/test_issue17_item4_decisions.py): `_env` explicitly sets `WORKSPACE_ROOT=str(tmp_path)`.
- [`test_issue18_liveness_contract.py`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T025803Z-c69408/1/tests/test_issue18_liveness_contract.py): `_env` explicitly sets `env["WORKSPACE_ROOT"] = str(cwd)`.
- [`test_issue26_envfile.py`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T025803Z-c69408/1/tests/test_issue26_envfile.py): `_env` explicitly sets `"WORKSPACE_ROOT": str(home)`.
- [`test_issue26_ints.py`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T025803Z-c69408/1/tests/test_issue26_ints.py): `_base_env` explicitly sets `"WORKSPACE_ROOT": str(home)`.
- [`test_issue33_budget.py`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T025803Z-c69408/1/tests/test_issue33_budget.py): `_env` explicitly sets `env["WORKSPACE_ROOT"] = str(home)`.
- [`test_issue50_dispatch_herdr.py`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T025803Z-c69408/1/tests/test_issue50_dispatch_herdr.py): `Box.env` explicitly sets `WORKSPACE_ROOT=str(self.ctl)`.
- [`test_issue6_turn_handshake.py`](file:///var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261013T025803Z-c69408/1/tests/test_issue6_turn_handshake.py): `_run_lib_sh` and `_run_wrapper` explicitly set `run_env["WORKSPACE_ROOT"] = str(cwd)`.
- Modules using `mam_sandbox` or `own_workspace_root` fixtures: correctly receive isolated `WORKSPACE_ROOT`.
No other un-isolated invocations exist in the test suite.
---
[VERDICT: PASS]
@@ -0,0 +1,14 @@
{
"loop_id": "loop-20261013T025803Z-c69408",
"base_sha": "42e2a7ea9bd3e2770ed8626d789193428ca3aa8b",
"head_sha": "3a30d7da894cdac26b7ec054b4e66fe9fa3dc8db",
"tree_sha": "bd43e4642f0109091f5f497b2703dcbaf8e2616b",
"review_commit": "3a30d7da894cdac26b7ec054b4e66fe9fa3dc8db",
"reviewer": "reviewer-grok-01",
"job_id": "db786989",
"verdict": "PASS",
"transport": "herdr",
"evidence": "review-tree",
"isolation": "worktree",
"verified_at": "2026-10-13T03:32:19Z"
}
@@ -0,0 +1,32 @@
# Review db786989 — u1e workspace root
Head `3a30d7da894cdac26b7ec054b4e66fe9fa3dc8db` (tree `bd43e4642f0109091f5f497b2703dcbaf8e2616b`) is the one commit `3a30d7d fix(tests): u1e gives the wrapper its own WORKSPACE_ROOT - it wrote the checkout's .mam/shim/herdr` on base `42e2a7ea9bd3e2770ed8626d789193428ca3aa8b`. Two test files, +5/−1. `git diff --check` is clean. Snapshot porcelain empty, and the snapshot has no `.mam` directory. Control-plane main is still `42e2a7ea9bd3e2770ed8626d789193428ca3aa8b`, porcelain empty.
## The cause and the pin
The wrapper sources `lib.sh` at startup (`.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job:53`). `lib.sh:26` sets `WORKSPACE_ROOT` to the checkout that contains the sourced file when the variable is unset, and the end of the file calls `_init_herdr_isolation` (`lib.sh:3168`). That function creates `$WORKSPACE_ROOT/.mam/shim` and writes `herdr` there (`lib.sh:149-156`). `pytest_configure` drops `WORKSPACE_ROOT` only when its real path is some other directory (`tests/conftest.py:153-155`), so an unset value stays unset and a value that names the checkout is kept.
`test_u1e_a_dry_run_record_is_not_an_open_job` now puts `WORKSPACE_ROOT` on the wrapper environment, set to that test's `tmp_path` (`tests/test_issue50_dispatch.py:167-173`). The shim is written under the test directory. The dry-run assertions in the same function are unchanged.
## W1 runs this node
`tests/test_issue17_workspace_root_isolation.py:43` adds the node to `NODES`. W1 copies the checkout, sets `WORKSPACE_ROOT` to that copy, runs the nodes, and fails if the copy grows a `.mam` directory (`:57-72`). On the fixed tree the node's own pin replaces the copy path, so the copy stays empty.
On an archive of base `42e2a7e` (outside the snapshot, deleted afterward) the old function does not mention `WORKSPACE_ROOT`. With the control-plane interpreter selected via `AGENT_PYTHON_BIN` (the same variable W1 exports at `:62`):
- `WORKSPACE_ROOT` unset: the node passed in 1.05s and the archive gained `.mam/shim/herdr`.
- `WORKSPACE_ROOT` set to the archive, which is what W1 would pass in: the node passed in 0.97s and the archive again had `.mam/shim/herdr`.
That directory is what W1's assertion at `:70-72` rejects. A passing node is still a W1 failure on the pre-fix tree.
On the snapshot, the fixed node passed in 1.05s. The shim was `test_u1e_a_dry_run_record_is_n0/.mam/shim/herdr` under a basetemp outside the snapshot. The snapshot had no `.mam`. W1 itself passed in 30.80s, which includes the inner count of at least `len(NODES)` and the empty-copy assertion. The snapshot still had no `.mam` after that run.
The wrapper's interpreter check runs after `source lib.sh` (`multi-agent-mux-delegate-job:69`). A first run that left `python3` without paho-mqtt exited 1 at that check on both trees, and the shim had already been written: on the archive in the unset and inherited-copy cases, and under the fixed test's basetemp. The snapshot stayed clean in that run too.
## Other wrapper callers
The same shape — a checkout wrapper or `lib.sh`, with the inherited root left unset — is already closed on the other callers checked here. Their child environments pin `WORKSPACE_ROOT` to the test directory (`tests/test_issue17_item4_decisions.py:229-231` and `:693-694`, `tests/test_issue26_ints.py:129`, `tests/test_issue36_prompt_transport.py:61`, `tests/test_issue30_stall_watch_stop.py:32`, `tests/test_issue6_fail_closed.py:38` and `:198`, `tests/test_issue50_dispatch_herdr.py:78`). The modules W1 already listed opt into `own_workspace_root` or that pin. `tests/test_issue30_stall_watch.py` builds its wrapper environment from `mam_sandbox` / `box`, which set `WORKSPACE_ROOT` before the copy (`tests/conftest.py:263`). No second unpinned checkout-wrapper call turned up.
No herdr server was started. `install.sh` was not run. User herdr config mtime stayed `1791858154`.
[VERDICT: PASS]
+1 -1
View File
@@ -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.9.2"
MAM_VERSION="4.9.3"
export MAM_VERSION
AGENT_SESSIONS_YAML="${AGENT_SESSIONS_YAML:-$WORKSPACE_ROOT/.mam/agent-sessions.yaml}"
+1 -1
View File
@@ -13,7 +13,7 @@ import sys
from typing import List, Sequence
# Framework version locked with framework constants
FRAMEWORK_VERSION = "4.9.2"
FRAMEWORK_VERSION = "4.9.3"
CORE_MODULES: Sequence[str] = (
# Standard library modules
@@ -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.9.2
version: 4.9.3
author: godopu
license: MIT
platforms: [linux, macos]
@@ -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.9.2
version: 4.9.3
author: godopu
license: MIT
platforms: [linux, macos, windows]
+1 -1
View File
@@ -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.9.2
version: 4.9.3
author: godopu
license: MIT
platforms: [linux, macos]
@@ -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.9.2
version: 4.9.3
author: godopu
license: MIT
platforms: [linux, macos]
@@ -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.9.2
version: 4.9.3
author: godopu
license: MIT
platforms: [linux, macos]
@@ -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 <id>` or `agy --conversation <id>` 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.9.2
version: 4.9.3
author: godopu
license: MIT
platforms: [linux, macos]
@@ -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.9.2
version: 4.9.3
author: godopu
license: MIT
platforms: [linux, macos]
+1 -1
View File
@@ -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.9.2
version: 4.9.3
author: godopu
license: MIT
platforms: [linux, macos]
+1 -1
View File
@@ -2,7 +2,7 @@
본 문서는 `herdr_agent_orchestration` 오케스트레이션 및 메시징 백플레인 워크플로우를 새로운 프로젝트에 도입하여 이식하고, 새로운 개발자/에이전트가 초기 가동을 시작할 때 수행해야 하는 환경 설정 및 구축 절차를 안내합니다.
[![Version: v4.9.2](https://img.shields.io/badge/version-v4.9.2-blue.svg)](VERSIONS.md)
[![Version: v4.9.3](https://img.shields.io/badge/version-v4.9.3-blue.svg)](VERSIONS.md)
새로운 에이전트는 이 안내서에 기술된 절차를 순차적으로 실행하여 초기 환경을 안정적으로 설정할 수 있습니다.
+1 -1
View File
@@ -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.9.2](https://img.shields.io/badge/version-v4.9.2-blue.svg)](VERSIONS.md)
[![Version: v4.9.3](https://img.shields.io/badge/version-v4.9.3-blue.svg)](VERSIONS.md)
A new agent can follow the steps in this guide sequentially to establish a stable and reliable initial environment.
+1 -1
View File
@@ -1,6 +1,6 @@
# Messaging System Technical Analysis & Architecture Report
Applies to: MAM v4.9.2
Applies to: MAM v4.9.3
This report provides a comprehensive, deep-dive analysis of the messaging system implemented in the `multi-agent-mux-delegate-job` skill. It covers the MQTT broker architecture, event protocols, job lifecycles, codebase internals, cross-system integration, and a list of known limitations along with production recommendations.
+1 -1
View File
@@ -2,7 +2,7 @@
[herdr](https://herdr.dev)와 MQTT 위에 만든 **다중 에이전트 오케스트레이션·메시징 백플레인**입니다. 장시간 실행되는 코딩 에이전트(`claude`, `agy`, `hermes`, `grok`, `opencode`, `codex`)를 격리된 herdr pane 에서 돌리고, 작업을 추적되는 잡으로 위임하며, 모든 리뷰어가 통과시킬 때까지 planner / creator / reviewer 루프를 돌립니다.
[![Version: v4.9.2](https://img.shields.io/badge/version-v4.9.2-blue.svg)](VERSIONS.md)
[![Version: v4.9.3](https://img.shields.io/badge/version-v4.9.3-blue.svg)](VERSIONS.md)
---
+1 -1
View File
@@ -2,7 +2,7 @@
A **Multi-Agent Orchestration & Messaging Backplane** built on [herdr](https://herdr.dev) and MQTT. It runs long-lived coding agents (`claude`, `agy`, `hermes`, `grok`, `opencode`, `codex`) in isolated herdr panes, delegates work to them as tracked jobs, and drives planner / creator / reviewer loops until every reviewer passes the change.
[![Version: v4.9.2](https://img.shields.io/badge/version-v4.9.2-blue.svg)](VERSIONS.md)
[![Version: v4.9.3](https://img.shields.io/badge/version-v4.9.3-blue.svg)](VERSIONS.md)
---
+22 -10
View File
@@ -6,7 +6,7 @@
## 📌 현재 버전 개요 (Current Release)
- **프레임워크 버전**: `v4.9.2`
- **프레임워크 버전**: `v4.9.3`
- **최신 릴리스 일시**: 2026-10-13 (KST)
- **기준 브랜치**: `main`
- **핵심 아키텍처**:
@@ -24,23 +24,35 @@
## 🧭 스킬 패키지 버전 매트릭스 (Skills Version Matrix)
모든 8개 스킬은 YAML frontmatter 메타데이터(`author`, `version`, `platforms`, `environments`) 표준화를 통해 `v4.9.2`로 동기화되어 배포됩니다.
모든 8개 스킬은 YAML frontmatter 메타데이터(`author`, `version`, `platforms`, `environments`) 표준화를 통해 `v4.9.3`로 동기화되어 배포됩니다.
| 스킬명 | 버전 | 역할 및 주요 책임 | 상태 |
| :--- | :---: | :--- | :---: |
| **`multi-agent-mux-create`** | `4.9.2` | 에이전트 세션 신규 생성 및 Herdr 컨테이너 격리 스폰 | ✅ 배포 |
| **`multi-agent-mux-stop`** | `4.9.2` | 대화 UUID 원자적 캡처 및 세션 안전 종료 (Graceful Stop) | ✅ 배포 |
| **`multi-agent-mux-resume`** | `4.9.2` | 온디스크 대화 컨텍스트 기반 Tier-1 초고속 세션 복원 | ✅ 배포 |
| **`multi-agent-mux-status`** | `4.9.2` | 실시간 Herdr 세션 및 레지스트리 드리프트 스냅샷 조회 | ✅ 배포 |
| **`multi-agent-mux-monitor`** | `4.9.2` | YAML ↔ 런타임 상태 간 자율 조정자 (Reconciler Loop) | ✅ 배포 |
| **`multi-agent-mux-delegate-job`** | `4.9.2` | MQTT 이벤트 채널 기반 비동기 단위 작업 위임 | ✅ 배포 |
| **`multi-agent-mux-loop`** | `4.9.2` | Planner-Creator-Reviewer 3자 자율 계획·실행·피어리뷰 루프 | ✅ 배포 |
| **`multi-agent-mux-orc-onboard`** | `4.9.2` | 오케스트레이터 UUID 격리 등록 및 서브 세션 오염 방지 | ✅ 배포 |
| **`multi-agent-mux-create`** | `4.9.3` | 에이전트 세션 신규 생성 및 Herdr 컨테이너 격리 스폰 | ✅ 배포 |
| **`multi-agent-mux-stop`** | `4.9.3` | 대화 UUID 원자적 캡처 및 세션 안전 종료 (Graceful Stop) | ✅ 배포 |
| **`multi-agent-mux-resume`** | `4.9.3` | 온디스크 대화 컨텍스트 기반 Tier-1 초고속 세션 복원 | ✅ 배포 |
| **`multi-agent-mux-status`** | `4.9.3` | 실시간 Herdr 세션 및 레지스트리 드리프트 스냅샷 조회 | ✅ 배포 |
| **`multi-agent-mux-monitor`** | `4.9.3` | YAML ↔ 런타임 상태 간 자율 조정자 (Reconciler Loop) | ✅ 배포 |
| **`multi-agent-mux-delegate-job`** | `4.9.3` | MQTT 이벤트 채널 기반 비동기 단위 작업 위임 | ✅ 배포 |
| **`multi-agent-mux-loop`** | `4.9.3` | Planner-Creator-Reviewer 3자 자율 계획·실행·피어리뷰 루프 | ✅ 배포 |
| **`multi-agent-mux-orc-onboard`** | `4.9.3` | 오케스트레이터 UUID 격리 등록 및 서브 세션 오염 방지 | ✅ 배포 |
---
## 📋 버전별 상세 변경 내역 (Changelog)
### 🚀 `v4.9.3` — u1e 테스트가 체크아웃의 `.mam/shim/herdr` 를 쓰지 않는다 (2026-10-13)
> **패치 (PATCH Release, 테스트 전용)**: v4.9.2 README 리뷰(claude 38a64423)가 재현한 결함. 제품 코드는 버전 줄 외에 바뀌지 않았다.
#### 🌟 주요 개선 사항
1. **원인**: `tests/test_issue50_dispatch.py::test_u1e_a_dry_run_record_is_not_an_open_job`(PR #56)이 delegate-job 래퍼를 `WORKSPACE_ROOT` 없이 실행했다. 래퍼가 source 하는 `lib.sh` 는 그 값을 체크아웃으로 기본값 처리하고 `<checkout>/.mam/shim/herdr` 를 다시 쓴다(Issue #17 후속과 같은 유형).
2. **수정**: 테스트가 래퍼에 자기 `tmp_path` 를 `WORKSPACE_ROOT` 로 준다. 이 노드를 W1(`tests/test_issue17_workspace_root_isolation.py`, 노드들을 체크아웃 복사본에서 돌리고 `.mam` 이 생기면 실패)의 목록에 넣었다.
#### ✅ 품질 검증
- **회귀**: 수정 전 트리에서 W1 이 `['.mam/shim', '.mam/shim/herdr']` 로 실패하고 수정 후 통과한다(리뷰어 4명이 각자 재현). 깨끗한 워크트리에서 #50·#55·누수 모듈 7개(112 tests)를 돌린 뒤 체크아웃에 `.mam` 이 없었다(수정 전에는 u1e 의 shim 하나).
- **리뷰 (`--review-only`, `--all-reviewer`)**: 1회차(@3a30d7d) **4/4 PASS**. claude 는 25 개 파일을 파일별로 돌려 다른 누출이 없음을 확인했다. 리포트는 `.agents/reports/<session>/`.
### 🚀 `v4.9.2` — README 를 현재 코드에 맞춰 다시 쓴다 (2026-10-13)
> **패치 (PATCH Release, 문서 전용)**: 코드 변경 없음. `README.md` 를 v4.9.1 의 코드와 대조해 다시 쓰고 `README.ko.md` 를 같은 내용으로 맞췄다.
@@ -40,6 +40,7 @@ NODES = [
"tests/test_issue17_item4_decisions.py::test_c_cmd_wait",
"tests/test_o2_race_free_lock.py::test_o2_12_run_loop_exits_on_lock_failure",
"tests/test_o1_rebuttal.py::test_v3_rebut_option_parsing_and_budget",
"tests/test_issue50_dispatch.py::test_u1e_a_dry_run_record_is_not_an_open_job",
]
+4 -1
View File
@@ -164,7 +164,10 @@ def test_u1e_a_dry_run_record_is_not_an_open_job(tmp_path):
dispatches; it stays pending (Issue #34 shows it as an open job), but the
admission must not wait for it."""
reg = tmp_path / "jobs"
env = dict(os.environ, HOME=str(tmp_path), MAM_ENV_FILE=str(tmp_path / ".mam.env.absent"))
# WORKSPACE_ROOT: the wrapper sources lib.sh, which otherwise writes the
# checkout's own .mam/shim/herdr (Issue #17 follow-up; W1 runs this node)
env = dict(os.environ, HOME=str(tmp_path), MAM_ENV_FILE=str(tmp_path / ".mam.env.absent"),
WORKSPACE_ROOT=str(tmp_path))
res = subprocess.run(["bash", str(WRAPPER), "submit", "--agent", "human", "--agent-session", "herdr:s",
"--dry-run", "--registry-dir", str(reg), "--prompt", "preview only"],
capture_output=True, text=True, env=env, cwd=str(tmp_path), timeout=120)