docs(issue-21): item 4 helpers, the submodule removal rule and the resume cwd rule in the design doc, compatibility matrix and skill docs

- design doc §3: the hand-assembled worktree/.venv/create recipe is replaced by
  worktree_prepare.sh --creator-agent (its BASE= line is the base to record);
  cleanup is worktree_remove.sh --stop; resume needs the original path.
- §4: what the helpers do - no .mam.env copy, the shared info/exclude, opt-in
  submodules (any init leaves modules/, which blocks a plain remove), removal
  of submodule worktrees with one --force after the checks, and never by hand.
- §5: item 4 is loop A on this branch; phase-1 items are done in v4.6.0.
- §7: create (control-root reconcile/watchdog, loud binding failure), resume
  (original cwd only, binding persisted when already running), stop (the
  removal helper's checks), monitor (register via create/prepare only).
- create / stop / resume SKILL.md: usage and exit codes of worktree_prepare.sh
  and worktree_remove.sh, the original-cwd rule and its exit code (1 -> 2).
  Frontmatter versions are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017R3Eg7MEpSWnm1oB9ZPP6G
This commit is contained in:
GodopuandClaude Opus 5 committed 2026-09-14 15:26:37 +00:00
1 parent 657b4b70ed
commit 7988db9adf
4 files changed
+74 -23

No files matched your search

@@ -233,6 +233,23 @@ bash .agents/skills/multi-agent-mux-create/scripts/create_session.sh \
The script handles the YAML append, pane capture, and the `last_visible_status` placeholder.
## Issue worktrees: `worktree_prepare.sh` (Issue #21 item 4)
Run from the control root's skills tree (lib.sh's `WORKSPACE_ROOT` = the main checkout that owns `.mam`):
```bash
bash .agents/skills/multi-agent-mux-create/scripts/worktree_prepare.sh \
--path ../multi-agent-mux-wt/issue-42 --branch fix/issue-42 [--base <ref>] [--submodules] \
[--creator-agent claude [--session <name>] [--role creator] [--herdr-session <s>] [--herdr-workspace <w>] [--onboard]] [--dry-run]
```
- Creates the worktree next to the control root (a path **inside** it is refused), idempotently; links `.venv` to the control root's; adds `/.venv` and `.pytest_cache/` to the shared `info/exclude` (a linked worktree's exclude file is `$GIT_COMMON_DIR/info/exclude`, so it applies to the main checkout too).
- **Submodules are opt-in** (`--submodules`). Initialisation writes `submodule.<name>.url` to the shared `.git/config`, and any attempt - even a failed one - leaves a `modules/` directory that makes a plain `git worktree remove` refuse. A failed `--submodules` removes those traces and exits 1; re-run without `--submodules` to finish.
- `--creator-agent` creates the creator through this skill with the control root's binding and `MAM_NO_SUBSCRIBER_DAEMON=1`; the row's `pane.cwd` is the worktree.
- Prints `BASE=<sha>`: record it **before** delegating the implementation, then review with `run_loop.sh --worktree <WT> --base "$BASE" --review-only`.
- Exit codes: 0 ok, 1 git / submodule / create failure, 2 usage or path rule.
- create itself: a control-plane binding that cannot be computed is refused (exit 2) for a `--workspace` other than the control root and warned about for the control root; the immediate reconcile and the watchdog run the control root's code.
## Pitfalls
- **Don't use `nohup`/`disown`/`setsid` for the agent itself** — those background the agent outside herdr. The whole point of this skill is *the herdr session is the supervisor*. `nohup` is OK only for *launching the wrapper* (which itself creates the herdr session via `herdr new-session -d`).
@@ -123,6 +123,15 @@ bash .agents/skills/multi-agent-mux-resume/scripts/update_yaml_resumed.sh \
herdr agent attach "$SESSION_NAME"
```
## Original cwd only (Issue #21 phase 2)
A session is resumed only in the directory it ran in (its row's `pane.cwd`): the conversation is keyed by that directory, and a class A agent (agy/hermes/opencode/codex) would otherwise start a new conversation in the wrong tree.
- `--workspace` differing from `pane.cwd` → **exit 2** with the path to use (claude/grok used to fail with exit 1 "No saved session").
- `pane.cwd` no longer exists (worktree removed) → **exit 2**; recreate the worktree at that path to resume.
- Rows without `pane.cwd` are not checked. The check runs before session-id resolution and also under `--dry-run`.
- The control-plane binding is computed before the herdr-alive check, so an already-running session's row gets it too; a binding that cannot be computed is refused (exit 2) for a worktree and warned about for the control root.
## Pitfalls
- **`claude -r` requires the SAME project directory** — if the workspace path differs from when the session was created, claude will create a new project dir key (`-home-...-different-name`) and put the resume in a different location. Always `-c` (cd to workspace) before running.
@@ -109,6 +109,24 @@ The script:
6. Updates the YAML entry and SQLite database atomically
7. If `delegate_job_id` is set, publishes a `completed` event to the multi-agent-mux-delegate-job registry
## Removing an issue worktree: `worktree_remove.sh` (Issue #21 item 4)
```bash
bash .agents/skills/multi-agent-mux-stop/scripts/worktree_remove.sh --path <WT> [--stop] [--dry-run]
```
Every check runs first and changes nothing; a refusal exits 3:
1. a linked worktree of the control root, not locked (`git worktree unlock` first - git refuses even `--force` on a locked one);
2. running sessions under it only with `--stop` (stopped normally, conversations kept);
3. no open job of those sessions - never cancelled automatically, they may belong to a running loop (the delegate job of a session `--stop` stops is cancelled by stop itself);
4. a clean tree, submodules included (`status --porcelain --ignore-submodules=none`);
5. inside submodules: no modification and no commit that is on no remote (`HEAD` included - a submodule sits on a detached HEAD).
Then it stops the sessions, re-checks the jobs and removes the worktree (the branch is kept): plain, or - when the worktree holds submodule state, which git refuses to remove otherwise - a **single `--force` justified by checks 4 and 5**. Do not run `git worktree remove --force` by hand on such a worktree: it deletes the worktree's own submodule repositories, unpushed commits included.
Exit codes: 0 removed, 1 stop/git failure, 2 not a worktree of the control root, 3 refused (nothing changed). Sessions created in a removed worktree cannot be resumed until a worktree exists at that path again.
## Pitfalls
- **Don't delete on-disk artifacts by default** — the agent's `*.jsonl` / `conversations/*.db` is the data that `multi-agent-mux-resume` needs. `--purge-conversation` is for when the user is genuinely done with the conversation and wants zero recovery chance.
+30 -23
View File
@@ -37,27 +37,25 @@ MAM 은 **워크스페이스 = `.agents/skills` 가 들어 있는 체크아웃
- **오케스트레이터 커밋 = 워크트리.** 본 체크아웃은 리뷰어의 작업 공간이므로 오케스트레이터는 그곳에서 브랜치를 바꾸지 않는다.
- **제어 평면 = 본 체크아웃의 `.mam` 하나.** create/resume 이 바인딩(`WORKSPACE_ROOT`·`AGENT_SESSIONS_YAML`·`MAM_JOBS_DIR`·`DELEGATE_JOB_LOGS_DIR`)을 행(`control_plane`)에 영속하고, `--workspace` 기본값과 다르면 pane 에 명시 전파한다(항목 3). 호출자 env 만으로는 pane 상속이 보장되지 않는다(lib.sh:667-680 — new-session 은 선행 `K=V` 토큰만 `--env` 로 바꾼다). 행의 `pane.cwd` 는 워크트리다.
## 3. C 안의 동작 방식 (부분 지원 + 1단계 수정 반영)
## 3. C 안의 동작 방식 (1단계 + 2단계 루프 A 반영)
```bash
MAIN=/path/to/multi-agent-mux # 본 체크아웃: 리뷰어 풀 + .mam (제어 루트)
WT=/path/to/multi-agent-mux-wt/issue-20 # 이슈 워크트리 (소스 루트)
git -C "$MAIN" worktree add -b fix/issue-20 "$WT" main
ln -s "$MAIN/.venv" "$WT/.venv"; git -C "$WT" submodule update --init # 워크트리 준비(.venv, nats-docker) — 헬퍼는 항목 4
# creator 를 워크트리 cwd 로, 그러나 본 체크아웃의 .mam 에 등록.
# 바인딩은 행의 control_plane 에 남고, --workspace 기본값과 다르므로 pane 에 --env 로 전파된다.
WORKSPACE_ROOT="$MAIN" AGENT_SESSIONS_YAML="$MAIN/.mam/agent-sessions.yaml" \
MAM_JOBS_DIR="$MAIN/.mam/jobs" DELEGATE_JOB_LOGS_DIR="$MAIN/.mam/delegate_job_logs" \
bash "$MAIN/.agents/skills/multi-agent-mux-create/scripts/create_session.sh" \
--workspace "$WT" --agent claude --role Creator --session creator-claude-issue20 \
--herdr-session multi-agent-mux --herdr-workspace mam-agent
# 워크트리 준비 + creator 생성(항목 4, worktree_prepare.sh): git worktree add, 제어 루트 .venv 링크,
# 공통 info/exclude 에 /.venv·.pytest_cache/, creator 는 제어 루트 바인딩과 MAM_NO_SUBSCRIBER_DAEMON=1 로 생성.
# 행의 pane.cwd 는 워크트리, control_plane 은 본 체크아웃. 서브모듈은 --submodules 를 줄 때만 초기화한다.
cd "$MAIN" && bash .agents/skills/multi-agent-mux-create/scripts/worktree_prepare.sh \
--path "$WT" --branch fix/issue-20 --base main \
--creator-agent claude --session creator-claude-issue20 \
--herdr-session multi-agent-mux --herdr-workspace mam-agent
# 출력 끝의 BASE=<sha> 가 워크트리 HEAD 다. (b) 흐름이면 구현 위임 전에 기록해 둔다.
# (a) 루프가 구현까지 한다: base 는 Phase 2 시작 시점의 워크트리 HEAD
cd "$MAIN" && bash .agents/skills/multi-agent-mux-loop/scripts/run_loop.sh \
--worktree "$WT" --creator creator-claude-issue20 --all-reviewer --task "Issue #20 ..."
# (b) 구현은 루프 밖(1단계 기본): base 를 구현 위임 **전에** 기록한다
# (b) 구현은 루프 밖(1단계 기본): base 를 구현 위임 **전에** 기록한다(prepare 가 출력한 BASE 와 같다)
BASE=$(git -C "$WT" rev-parse HEAD)
bash "$MAIN/.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job" submit --type direct \
--agent claude --agent-session herdr:creator-claude-issue20 --workdir "$MAIN" --prompt "Issue #20 ..."
@@ -65,10 +63,15 @@ bash "$MAIN/.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate
cd "$MAIN" && bash .agents/skills/multi-agent-mux-loop/scripts/run_loop.sh \
--worktree "$WT" --base "$BASE" --review-only --creator creator-claude-issue20 --all-reviewer --task "Issue #20 review"
# 정지·재개: --workspace 는 원래 워크트리 경로. 재개는 행에 영속된 바인딩을 다시 전파한다
# 정지·재개: --workspace 는 원래 워크트리 경로여야 한다(다른 경로·삭제된 경로는 exit 2).
# 재개는 행에 영속된 바인딩을 다시 전파한다
bash "$MAIN/.agents/skills/multi-agent-mux-stop/scripts/stop_session.sh" --session creator-claude-issue20
bash "$MAIN/.agents/skills/multi-agent-mux-resume/scripts/resume_session.sh" \
--workspace "$WT" --agent claude --session creator-claude-issue20
# 정리(worktree_remove.sh): 잠금·세션·잡·청결·서브모듈 판정을 모두 통과해야 정지·제거한다.
# 하나라도 걸리면 exit 3 이고 아무것도 바꾸지 않는다. 브랜치는 남긴다.
bash "$MAIN/.agents/skills/multi-agent-mux-stop/scripts/worktree_remove.sh" --path "$WT" --stop
```
- `--task` 에 base 를 적지 말 것. 러너가 넣는 base 와 둘이 된다. 리뷰어가 받는 base 는 러너의 `[워크트리 컨텍스트]` 블록 하나뿐이어야 한다.
@@ -83,18 +86,22 @@ bash "$MAIN/.agents/skills/multi-agent-mux-resume/scripts/resume_session.sh" \
- **리뷰 단계는 풀 크기만큼만 병렬**이다. 이슈 3개를 동시에 돌려도 리뷰어 하나는 잡을 순서대로 처리한다(래퍼는 busy 세션에 rc 5 → 러너가 `MAM_SESSION_BUSY_RETRY_S` 뒤 1회 재시도 → 그래도 busy 면 실패). 다중 루프 동시 기동에는 **리뷰어 큐잉 또는 리뷰어 인스턴스 증설**이 전제다.
- 같은 `.mam` 의 두 번째 루프는 경고가 아니라 **거부**다: loop lock 이 exit 1 로 막는다(`run_loop.sh` 의 `mam_acquire_loop_lock` 분기). 체크포인트·loop guard 도 파일 하나다(`runner.py` 의 `checkpoint_file`, `MAM_LOOP_MARKER`). YAML/SQLite 는 flock·`BEGIN IMMEDIATE` 로 트랜잭션 안전(atomic_yaml.py)하므로, 문제는 DB 손상이 아니라 **공유 논리 상태와 리뷰어 소유권**이다.
- **1단계 운영 형태 = 구현 병렬, 리뷰 루프 하나**: creator 구현은 루프 밖(delegate-job direct)에서 워크트리마다 병렬로 한다. 리뷰는 `run_loop.sh --worktree <WT> --base <구현 전 sha> --review-only` 로 한 번에 하나씩 한다. base 를 구현 위임 **전에** 기록하지 않으면 리뷰를 시작할 수 없다(가드가 빈 변경분을 거부한다). busy 처리는 큐가 아니다(1회 재시도). 다중 루프는 항목 8(리뷰어 스케줄러) 이후다.
- 준비 헬퍼는 항목 4 다. `.mam.env` 는 복사 대신 중앙 `MAM_ENV_FILE` 정책으로 하고, `.venv` 심링크는 gitignore 되지 않으므로 `.git/info/exclude` 로 처리한다.
- 준비·해체 헬퍼는 항목 4 로 구현했다(`worktree_prepare.sh`, `worktree_remove.sh`).
- `.mam.env` 는 복사하지 않는다. creator 에는 제어 루트의 것을 `MAM_ENV_FILE` 로 준다.
- `.venv` 는 제어 루트 것의 심링크다. 무시 규칙(`/.venv`, `.pytest_cache/`)은 linked worktree 가 공유하는 `$GIT_COMMON_DIR/info/exclude` 에 들어간다. 그래서 본 체크아웃에도 적용되며, 둘 다 본 체크아웃에서도 무시해야 하는 산출물이다.
- 서브모듈은 `--submodules` 로만 초기화한다. 초기화는 성공·실패와 무관하게 워크트리 git 디렉터리에 `modules/` 를 남기고, 그러면 git 이 force 없는 `worktree remove` 를 거부한다. prepare 는 실패한 초기화의 흔적(`modules/`, 새 `submodule.*` 공유 설정)을 되돌리고 exit 1 한다.
- 서브모듈을 초기화한 워크트리는 remove 가 수정과 원격에 없는 커밋(`HEAD` 포함 — 서브모듈은 detached HEAD 다)을 확인한 뒤 `--force` 를 한 번만 써서 제거한다. **사람이 `git worktree remove --force` 를 직접 쓰지 말 것.** 워크트리 전용 서브모듈 저장소와 미푸시 커밋이 함께 사라진다.
## 5. 필요한 변경 (Issue #21 작업 항목)
| # | 항목 | 상태 |
|---|---|---|
| 1 | 래퍼 주입 문구·시작 명령(`--registry-dir`, 감사 로그 루트)·Output Report Path 절대경로화, `MAM_JOBS_DIR` 존중 | 이 브랜치 |
| 2 | 루프 러너 `--worktree` / `--base` / `--review-only`, 빈 변경분 fail-closed 가드, 러너의 `MAM_JOBS_DIR` 존중 | 이 브랜치 |
| 3 | 제어 평면 바인딩 영속(`control_plane`)·명시 전파(create/resume), stop·status·reconcile 의 `MAM_JOBS_DIR`·행 바인딩 존중 | 이 브랜치 |
| 4 | 워크트리 준비·해체 헬퍼, resume 을 원래 cwd 로만 허용, create 의 reconcile/watchdog 기동 위치, `MAM_NO_SUBSCRIBER_DAEMON` 기본화, reconcile 의 루트 밖 cwd 재발견 거부(#17-4 셸 축소와 함께) | 대기 |
| 1 | 래퍼 주입 문구·시작 명령(`--registry-dir`, 감사 로그 루트)·Output Report Path 절대경로화, `MAM_JOBS_DIR` 존중 | 완료(v4.6.0) |
| 2 | 루프 러너 `--worktree` / `--base` / `--review-only`, 빈 변경분 fail-closed 가드, 러너의 `MAM_JOBS_DIR` 존중 | 완료(v4.6.0) |
| 3 | 제어 평면 바인딩 영속(`control_plane`)·명시 전파(create/resume), stop·status·reconcile 의 `MAM_JOBS_DIR`·행 바인딩 존중 | 완료(v4.6.0). 루프 A 에서 바인딩 계산 실패 처리·조기 종료 경로 영속 보강 |
| 4 | 워크트리 준비·해체 헬퍼, resume 을 원래 cwd 로만 허용, create 의 reconcile/watchdog 기동 위치, `MAM_NO_SUBSCRIBER_DAEMON`(prepare 가 설정). reconcile 의 루트 밖 cwd 재발견 거부는 제한으로 남김 | 루프 A (이 브랜치) |
| 5 | 리뷰 identity: (loop_id, base_sha, head_sha) 판정 기록·무효화, 리뷰어 검증 격리(detached 워크트리) | 대기 |
| 6 | 이 설계 문서 정정 | 이 브랜치 |
| 6 | 이 설계 문서 정정 | 완료(v4.6.0), 루프 A 에서 갱신 |
| 7 | 수용 사례 고정(실제 herdr 의 pane env 전파 포함) | 대기 |
| 8 | 다중 루프 네임스페이스·리뷰어 스케줄러 | 보류 |
@@ -116,11 +123,11 @@ bash "$MAIN/.agents/skills/multi-agent-mux-resume/scripts/resume_session.sh" \
| 스킬 | 제어 평면 바인딩 | 워크트리 cwd 행 | 1단계 이후 남은 제한 (항목) |
|---|---|---|---|
| create | env 에서 결정 → 행 `control_plane` 에 항상 영속. `--workspace` 기본값과 다르면 새 pane 의 생성 호출(`pane split`/`workspace create`)에 `--env` 로 전파한다. 전파가 필요하면 idle pane 을 재사용하지 않는다(`agent start -- <args>` 는 `--` 뒤를 에이전트 인자로 넘기므로 `env K=V` 를 쓸 수 없다). 전파가 필요한데 경로에 공백이 있으면 exit 2. `~/.local/bin` wrapper 경로는 기록만 하고 경고 | pane.cwd = `--workspace` | create 가 `$WORKSPACE` 사본으로 reconcile/watchdog 기동, `MAM_NO_SUBSCRIBER_DAEMON` 미기본 (4) |
| resume | 행 바인딩 우선(호출자 env 와 다르면 stderr 경고) 재전파. 바인딩 없는 기존 행은 호출자 바인딩으로 채움 | `--workspace` 는 원래 워크트리 경로여야 함 | 워크트리 삭제 시 재개 불가, 종료 헬퍼 (4) |
| stop | `MAM_JOBS_DIR` > 행의 `mam_jobs_dir` > `$WORKSPACE_ROOT/.mam/jobs` 순으로 잡 조회·발행. 감사 로그도 `DELEGATE_JOB_LOGS_DIR` > 행의 `delegate_job_logs_dir` 순 | 행 cwd 로 uuid 캡처·purge | 해체 순서 헬퍼 (4) |
| create | env 에서 결정 → 행 `control_plane` 에 항상 영속. `--workspace` 기본값과 다르면 새 pane 의 생성 호출(`pane split`/`workspace create`)에 `--env` 로 전파한다. 전파가 필요하면 idle pane 을 재사용하지 않는다(`agent start -- <args>` 는 `--` 뒤를 에이전트 인자로 넘기므로 `env K=V` 를 쓸 수 없다). 전파가 필요한데 경로에 공백이 있으면 exit 2. `~/.local/bin` wrapper 경로는 기록만 하고 경고 | pane.cwd = `--workspace`. 바인딩 계산 실패 시 워크트리면 exit 2, 제어 루트면 경고 | — (루프 A: 즉시 reconcile·watchdog 은 제어 루트 코드와 `WORKSPACE_ROOT` 로 기동, prepare 가 `MAM_NO_SUBSCRIBER_DAEMON=1`) |
| resume | 행 바인딩 우선(호출자 env 와 다르면 stderr 경고) 재전파. 바인딩 없는 기존 행은 호출자 바인딩으로 채움. 이미 살아 있는 세션에도 바인딩을 영속한다. 계산 실패 시 워크트리면 exit 2 | 원래 cwd 로만: 경로 불일치·부재 시 exit 2 | 워크트리 삭제 시 재개 불가(대화 보존, 같은 경로에 다시 만들면 재개) |
| stop | `MAM_JOBS_DIR` > 행의 `mam_jobs_dir` > `$WORKSPACE_ROOT/.mam/jobs` 순으로 잡 조회·발행. 감사 로그도 `DELEGATE_JOB_LOGS_DIR` > 행의 `delegate_job_logs_dir` 순 | 행 cwd 로 uuid 캡처·purge. 워크트리 해체는 `worktree_remove.sh` 가 판정(잠금·세션·잡·청결·서브모듈) 뒤에만 정지·제거, 서브모듈 워크트리는 검증 후 `--force` 1회 | — |
| status | `MAM_JOBS_DIR`·행 `mam_jobs_dir`·감사 로그(`DELEGATE_JOB_LOGS_DIR`·행 `delegate_job_logs_dir`)·기존 후보 순 조회 | 행 cwd 로 대화 키 | — |
| monitor/reconcile | `MAM_JOBS_DIR` 우선(정적 가드로만 검증) | 기존 행은 행 cwd 로 검증 | 루트 밖 cwd 의 미등록 세션은 자동 등록 안 됨 → create 로만 등록 (4) |
| monitor/reconcile | `MAM_JOBS_DIR` 우선(정적 가드로만 검증) | 기존 행은 행 cwd 로 검증 | 루트 밖 cwd 의 미등록 세션은 자동 등록 안 됨 → create/prepare 로만 등록 (남은 제한) |
| delegate-job | `--registry-dir` > `MAM_JOBS_DIR` > `.mam/jobs`(`--workdir` 기준), 모두 절대화. 발행 명령이 감사 로그 루트(`DELEGATE_JOB_LOGS_DIR`)를 고정 | 문구·명령·리포트 경로 절대 | 작은따옴표가 든 경로는 지원 범위 밖 |
| loop | `MAM_JOBS_DIR` 존중, 래퍼에 `--registry-dir` 명시. `--worktree` 로 소스 루트 분리 | diff·base·head 만 소스 루트. **구현이 루프 밖이면 `--base <구현 전 sha> --review-only` 필수**. `--worktree`/`--base` 가 있으면 빈 변경분 거부 | 루프 하나(8), 리뷰 identity·검증 격리(5), 레거시 셸 루프(`MAM_LOOP_PY=0`)는 새 옵션 거부 |