# C-1 구현 계획서 **Rev.2** — Kanban 잔재 문서 전면 제거

- **Job**: `17ffc912` / Role: **Planner** (Rev.1 = Job `75847d1d`)
- **대상**: IMPROVEMENTS.md **C-1** — "Kanban 문서 29회 언급 vs 실제 구현 0건"
- **기준 커밋**: `924e77e` (clean tree)
- **반영**: Creator `agy` Challenge Report (Job `a5f24e6f`) — 맹점 2건
- **검증 상태**: Rev.1 전량 + Rev.2 신규 게이트를 `git archive HEAD` 클론에 **실제 적용·측정 완료**. 수치는 전부 실측값이다.

---

## 0. Rev.1 → Rev.2 변경 요지

| 이의 | 판정 | 근거 | 계획 반영 |
|---|---|---|---|
| **맹점 1** — `status` 의 `prereq_skills` 에 `monitor` 가 있는 것은 논리 모순이므로 제거 | **기각 (REJECTED)** | `status.sh:12` 가 `multi-agent-mux-monitor/scripts/reconcile.sh` 를 **하드 참조**한다. 의존은 실재한다. 또한 `prereq_skills` 의 저장소 관례는 "선행 *실행*"이 아니라 "선행 *가용성*"이며, 이의제기의 전제는 파일 자신의 용례 2건에 의해 반증된다. | 변경 없음. 대신 **RK-G** 로 위험 기록 + 별도 개선 항목 제안(§9) |
| **맹점 2** — G-C 정규식이 `--goal-max-turns` 를 놓친다 | **인용 (ACCEPTED, 확대 적용)** | 사실이다. 다만 실측 결과 구멍은 **2개가 아니라 10개**다. 제안된 `--goal` 추가는 10개 중 2개만 막는다. | **G-C 전면 재설계** — 열거를 **파생(derivation)** 으로 교체. red/green **61건 → 0건** 실측 |
| (신규) | **추가** | 맹점 1이 제기한 "디스패처가 prereq 메타데이터로 오작동" 우려의 *검증 가능한* 형태 | **G-G** — `prereq_skills` 그래프 순환 부재 게이트 |

**총평**: 맹점 2의 진단은 정확했고 처방은 부족했다. 맹점 1은 진단 자체가 사실과 어긋난다. 상세는 §7.

---

## 1. 요약

C-1 은 "단어 29개를 지운다"가 아니라 **세 종류의 서로 다른 문제**다. 셋을 같은 방법으로 처리하면 실패한다.

| 분류 | 건수 | 성격 | 처리 |
|---|---|---|---|
| **N. 순수 노이즈** | 12 | 지워도 의미 손실 0 (수식어, 태그, 괄호) | 단어 삭제 |
| **D. 끊어진 스킬 참조** | 3 | 이 저장소에 **존재하지 않는 스킬**을 가리킴 (`kanban-worker`, `kanban-orchestrator`) | 실존 스킬로 교체 |
| **S. 구조적 의존** | 16 | 문서의 **실행 메커니즘 자체**가 Kanban (`hermes kanban create`, `kanban_comment/_block/_heartbeat`, `goal_mode`) | 섹션 재작성 |

**핵심 판단**: S 분류는 단어를 지우면 문서가 "무엇을 실행하라는 것인지" 알 수 없게 된다. 대체 메커니즘이 반드시 필요하고, **이미 구현되어 있다** — `reconcile.sh --subscribe` 가 MQTT push + 브로커 다운 시 폴링 폴백을 갖춘 자립형 상주 루프다. 즉 Kanban 은 처음부터 불필요한 껍데기였고, 제거는 문서를 **실제 구현에 일치시키는 작업**이다.

**부가 성과**: 문서 정합성 결함 2건 동시 해소(§6). 하나는 존재하지 않는 상태 파일 주장, 하나는 `--subscribe` 폴백 stdout 동작 오기.

---

## 2. 현황 측정 (근거)

```
$ grep -ril kanban . | grep -v '^./.git/'   →  파일 6개
   22  .agents/skills/multi-agent-mux-monitor/SKILL.md
    5  .agents/skills/multi-agent-mux-status/SKILL.md
    2  .agents/skills/multi-agent-mux-create/SKILL.md
    1  README.md
    1  README.ko.md
    2  IMPROVEMENTS.md          ← C-1 추적 항목 자신
```

- SKILL.md 합계 = **22 + 5 + 2 = 29**. C-1 의 "29회" 와 **정확히 일치** → 추적 항목의 전제는 유효하다.
- **코드 파일 참조 0건**: `grep -ril kanban` 에서 `.md` 를 빼면 아무것도 남지 않는다. `hermes kanban` 서브커맨드를 쓰는 코드도 0건.
- `reconcile.sh` 내 `kanban` 관련 코드 = **0건**. 문서가 서술한 `kanban_comment` 호출은 애초에 어디에도 없다.

→ **순수 문서 작업이며 런타임 동작 변경 위험은 0이다.**

> **주의 — `hermes` 자체는 실존 도구다.** `create_session.sh:102` 가 `hermes status` 를 호출하고, `create/SKILL.md:234` 는 `hermes profile alias` 사용 금지를 정당하게 경고한다. 제거 대상은 **`hermes kanban` 서브커맨드 표면**이지 `hermes` 전체가 아니다. Rev.2 검토 중 "스킬 문서에 `hermes ` CLI 호출 금지" 게이트를 후보로 올렸다가 이 실측으로 **폐기**했다 — 정당한 2건을 오탐했을 것이다.

---

## 3. 범위 확정 — 리뷰어의 "0건"은 어디까지인가

성공 조건은 "reviewers verifying 0 remaining references" 다. 리뷰어가 `grep -ri kanban .` 를 그냥 실행하면 **IMPROVEMENTS.md 가 걸린다**. 그런데 C-1 항목은 *제거 사실을 기록하는 추적 문서*이므로 이름을 언급하지 않고는 쓸 수 없다. 방치하면 리뷰가 무한히 실패한다.

**해소 규칙**:

- **G-A (제품 표면)** = `.agents/skills/` + `README.md` + `README.ko.md` → **반드시 0건**. 실질 합격 기준.
- **G-B (저장소 전체 − 추적/보고 문서)** = `IMPROVEMENTS.md` 와 `.mam/`(잡 브리프·리포트가 구 문안을 인용) 제외 → **0건**.
- IMPROVEMENTS.md 의 C-1 은 삭제가 아니라 **§5 완료된 과제로 이관**(파일 기존 관례). 잔존 2회는 "완료 기록"으로 의도된 것이며 G-A/G-B 어디에도 해당하지 않는다.

**리뷰어가 스스로 grep 범위를 즉흥 결정하지 않게 하는 것이 이 절의 목적이다.**

---

## 4. 변경 명세

### F1 — `.agents/skills/multi-agent-mux-monitor/SKILL.md` (22건, 전면 재작성)

이 파일만 **파일 전체 교체**를 지시한다. 22개 참조가 10개 섹션에 흩어져 있고 §Dispatching / §Worker body template 두 섹션은 통째로 Kanban 스펙이라, 부분 치환 지시로는 Creator 마다 결과가 갈린다. 최종 전문은 **부록 A**(검증 완료본, 218줄).

변경 인벤토리 — 22건 전량의 처분:

| 위치 | 원문 | 처분 | 분류 |
|---|---|---|---|
| L3 `description` | "long-lived **Kanban worker**", "dispatched as a **Kanban goal_mode** task (--goal)" | `reconcile.sh --subscribe` 상주 루프로 재서술 | S |
| L8 `environments` | `[kanban, terminal, herdr]` | `[terminal, herdr]` — 타 스킬 6종 전부와 동일해짐 | N |
| L11 `tags` | `..., monitor, kanban, observation, ...` | `kanban` 제거 | N |
| L12 `related_skills` | `..., kanban-orchestrator` | `..., multi-agent-mux-status` | **D** |
| L13 `prereq_skills` | `[kanban-worker, multi-agent-mux-create]` | `[multi-agent-mux-create]` | **D** |
| L16 H1 | "— Live Reconciliation **via Kanban Worker**" | "— Live Reconciliation" | N |
| L23 | "Dispatch a **Kanban worker** (in `goal_mode`) that:" | "Run a **long-lived reconciler** (`reconcile.sh --subscribe`) that:" | S |
| L25 | "Every ~30s polls" | 실제 동작으로 정정: MQTT 이벤트 반응 + 브로커 다운 시 `RECONCILE_POLL_INTERVAL`(기본 15s) 폴백 | S |
| L37 | 4. "Writes a Kanban `kanban_comment` on every drift event" | 4. `--emit-diff` 시 stdout JSON 방출 (+폴백 경로 단서, §6-b) | S |
| L39 | 6. "**Goal loop**: judge … worker blocks with `reason=stop-requested`" | 5. 실제 종료 조건: `--timeout` / `--idle-timeout` / 인터럽트 | S |
| L52 | "You don't have a Kanban dispatcher running" | "You only need a point-in-time answer — use `multi-agent-mux-status`" | S |
| L54-85 | §Dispatching the monitor — `hermes kanban create` 30줄 블록 | §**Running the monitor** — 실제 실행 2줄로 교체. body 내 herdr 네이티브 명령 경고문은 **보존** | S ×4 |
| L93 | "JSON diff … that the worker turns into a `kanban_comment`" | "for the caller to consume" | S |
| L110 | 플래그 문단 말미 "…turns the emitted JSON `drifts[]` into `kanban_comment` calls itself." | "…the emitted JSON `drifts[]` is the caller's to consume." — `--comment-card` 언급도 제거 | S |
| L167 | 함정 "**Don't run the monitor without `--goal`**" | 실존 함정으로 교체: `--once` 는 1회 후 종료 / `--idle-timeout` 기본 3600s 함정 | S |
| L169 | 함정 "**`kanban_comment` rate limits** … `.cache/…/<ws>.state` 를 추적한다" | 중복 병합 책임을 caller 로 명시. **거짓 주장 제거** → §6-a | S |
| L171 | "comment about it but don't delete" | "report it but don't delete" | N |
| L174-209 | §**Worker body template** — `kanban_comment`/`_heartbeat`/`_block`/`$HERMES_KANBAN_TASK` | §**Supervising-agent runbook** 으로 전환. **Hard rules 4개 전량 보존** | S ×4 |
| L119-163 | drift A~D 예시의 "comment:" | "report:" | N ×4 |
| L236-237 | §Related skills — `kanban-worker`, `kanban-orchestrator` | `multi-agent-mux-status`, `multi-agent-mux-delegate-job` | **D** |

**의도적으로 남기는 것**: `metadata.hermes` 블록 자체(Kanban 아님, 공통 스키마), `hermes` 태그 체계, §Security `--subscribe` 전문, §Status Enum, drift A~D 본문.

### F2 — `.agents/skills/multi-agent-mux-status/SKILL.md` (5건, 6줄 치환)

각 치환은 **원문에 정확히 1회만 등장**함을 스크립트로 강제 확인한다(0회/2회면 즉시 중단).

| 줄 | before → after |
|---|---|
| L3 | `…and any drift. No Kanban, no mutation. Reuses…` → `…and any drift. No mutation. Reuses…` |
| L3 | `without spinning up a Kanban monitor worker.` → `without spinning up the monitor loop.` |
| L24 | `**No mutation. No Kanban. No polling loop.**` → `**No mutation. No polling loop.**` |
| L108 | `dispatch \`multi-agent-mux-monitor\` (Kanban worker) or run` → `run \`multi-agent-mux-monitor\` or` |
| L118 | `- Quick sanity check before dispatching a long Kanban task` → `…before dispatching a long-running job` |
| L122 | `- Continuous live tracking → \`multi-agent-mux-monitor\` (Kanban worker)` → `…→ \`multi-agent-mux-monitor\`` |

> **frontmatter 는 손대지 않는다.** L12/L13 의 `related_skills` / `prereq_skills` 는 **Rev.1 과 동일하게 원형 유지**한다. 근거는 §7-A.

### F3 — `.agents/skills/multi-agent-mux-create/SKILL.md` (2건)

| 줄 | before → after |
|---|---|
| L51 | `If any check fails → \`kanban_block(reason="...")\` (worker path) or report to user (interactive path).` → `If any check fails → abort with a non-zero exit and report the reason (automated path) or report to user (interactive path).` |
| L65 | `(e.g., cmux, Kanban workers, manual herdr sessions)` → `(e.g., cmux, background workers, manual herdr sessions)` |

L51 은 단순 삭제하면 "실패 시 무엇을 하라"가 사라지므로 **동등한 비-Kanban 지시로 치환**해야 한다.

### F4 — `README.md` (1건) / `README.ko.md` (1건)

| 파일 | before → after |
|---|---|
| README.md L45 | `A long-running Kanban reconcile worker that dynamically monitors` → `A long-running reconcile worker that dynamically monitors` |
| README.ko.md L27 | `백그라운드에서 Kanban Reconcile 프로세스로 실행되어` → `백그라운드 Reconcile 프로세스로 실행되어` |

브리프 문구는 "SKILL.md files" 지만 §3 의 이유로 **README 2건 필수 포함**.

### F5 — `IMPROVEMENTS.md` (추적 항목 이관)

1. §4 에서 C-1 블록(L81-82) 삭제
2. §4 헤더 `(Legacy Remnants — 5건)` → `(Legacy Remnants — 4건)`
3. §5 헤더 `(Completed Tasks — 3건)` → `(Completed Tasks — 4건)`
4. §5 에 파일 기존 형식으로 추가:

```markdown
### **C-1: Kanban 문서 29회 언급 vs 실제 구현 0건** — ✅ 완료
- SKILL.md 3종(monitor 22 / status 5 / create 2)과 README 2종의 Kanban 서술을 전면 제거했습니다.
- `multi-agent-mux-monitor` 의 실행 메커니즘 서술을 실제 구현인 `reconcile.sh --subscribe`
  (MQTT push + 브로커 다운 시 폴링 폴백) 기준으로 재작성했습니다.
- 존재하지 않는 스킬 참조 2건(`kanban-worker`, `kanban-orchestrator`)을 실존 스킬로 교체했습니다.
- `hermes kanban create` CLI 플래그 잔재 10종(`--goal-max-turns`, `--assignee`, `--comment-card` 등)을
  파생형 검증 게이트(G-C)로 차단했습니다.
- 제품 표면(`.agents/skills/`, `README*.md`) Kanban 참조 **0건** 확인.
```

### F6 — 반영하지 **않는** 것

- `.mam/` 하위 잡 브리프·리포트: 과거 잡의 기록이며 수정 시 감사 추적 훼손. G-B 가 제외하는 이유.
- `status/SKILL.md` frontmatter `prereq_skills`: §7-A.
- `.cache` **코드**: C-2 소관(§6-a).

---

## 5. 적용 순서

F1 → F2 → F3 → F4 → **G 게이트 전량 실행** → F5(IMPROVEMENTS 이관) → 재실행.

F5 를 마지막에 두는 이유: 완료 기록은 **게이트 통과 후에** 쓰여야 한다. 먼저 쓰면 미완료를 완료로 기록한 순간이 커밋 이력에 남는다.

---

## 6. 검증 게이트

전부 클론에 적용해 **실측 완료**. 괄호 안이 측정값이다.

**G-A — 제품 표면 0건 (실질 합격 기준)** *(측정: 0 — CLEAN)*
```bash
grep -ric kanban .agents/skills README.md README.ko.md | grep -v ':0$' || echo "0 references — CLEAN"
```

**G-B — 저장소 전체 0건 (추적/보고 문서 제외)** *(측정: 0 — CLEAN)*
```bash
grep -ril kanban . | grep -v '^\./\.git/' | grep -v IMPROVEMENTS.md | grep -v '^\./\.mam/' \
  || echo "0 files — CLEAN"
```

### G-C — Kanban CLI/API 표면 잔재 0건 ⚠️ **Rev.2 에서 전면 재설계**

*(측정: HEAD **61건 FAIL** → 적용 후 **0건 PASS**)*

**Rev.1 의 결함**: 패턴을 **손으로 열거**했다(`KANBAN|kanban_|goal_mode|kanban-worker|kanban-orchestrator|HERMES_KANBAN`). 손 열거는 반드시 빠뜨린다. `agy` 가 `--goal-max-turns` 로 이를 정확히 지적했다.

**그러나 구멍은 2개가 아니라 10개였다.** F1 이 삭제하는 텍스트에서 토큰을 기계적으로 추출해 각 패턴의 포착 여부를 실측한 결과:

| 삭제되는 토큰 | Rev.1 패턴 | `agy` 제안 패턴 | Rev.2 파생 게이트 |
|---|---|---|---|
| `kanban_comment` / `kanban_block` / `kanban_heartbeat` | CATCH | CATCH | CATCH |
| `HERMES_KANBAN_TASK` / `goal_mode` | CATCH | CATCH | CATCH |
| `--goal` / `--goal-max-turns` | **MISS** | CATCH | CATCH |
| `--assignee` | **MISS** | **MISS** | CATCH |
| `--comment-card` | **MISS** | **MISS** | CATCH |
| `--max-runtime` | **MISS** | **MISS** | CATCH |
| `--max-retries` | **MISS** | **MISS** | CATCH |
| `--title` / `--branch` / `--body` / `--skill` | **MISS** | **MISS** | CATCH |

→ `agy` 의 처방은 **10개 중 2개**만 막는다. 진단은 옳았으나 처방이 부족하다.

**Rev.2 의 해법**: 열거를 **파생**으로 바꾼다. 토큰 목록을 기억이 아니라 *F1 이 실제로 삭제하는 텍스트*에서 뽑는다 — 구성상 완전하다.

```bash
python3 .mam/gates/gate_gc.py     # 전문은 부록 C. 실패 시 exit 1
```
```
HEAD:    G-C FAIL — 61 Kanban-surface hit(s)
적용 후:  G-C PASS — 0 Kanban CLI/API surface tokens
```

**오탐 위험 실측**: 후보 플래그 10종이 `monitor/SKILL.md` **밖** 제품 표면에 등장하는 횟수 = 전부 **0**. 오탐 없음.

**의도적 제외 1건**: `AGENT_SESSIONS_STATE_DIR` 도 F1 이 삭제하는 토큰이지만 게이트에 넣지 **않는다**. `reconcile.sh:23` 에 실존하는 환경변수이며, 삭제 사유는 Kanban 이 아니라 거짓 서술(§6-a)이다. 훗날 올바르게 재문서화될 수 있으므로 금지어로 만들면 안 된다.

**G-D — 전 SKILL.md frontmatter YAML 파싱 유지** *(측정: 7/7 OK)*
```bash
python3 - <<'PY'
import glob, yaml
for f in sorted(glob.glob('.agents/skills/*/SKILL.md')):
    t = open(f, encoding='utf-8').read()
    if not t.startswith('---'): continue
    d = yaml.safe_load(t.split('---',2)[1])
    print(f"  OK {d['name']:36s} env={d.get('environments')}")
PY
```

**G-E — 끊어진 스킬 참조 0건 (red→green)** *(HEAD 2건 → 적용 후 0건)*

단어를 지운 게 아니라 **실존하는 것으로 바꿨음**을 증명한다.
```bash
python3 - <<'PY'
import glob, os, yaml
local = {os.path.basename(os.path.dirname(f)) for f in glob.glob('.agents/skills/*/SKILL.md')}
EXTERNAL_OK = {'claude-code'}
bad = []
for f in sorted(glob.glob('.agents/skills/*/SKILL.md')):
    d = yaml.safe_load(open(f, encoding='utf-8').read().split('---',2)[1]) or {}
    h = (d.get('metadata') or {}).get('hermes') or {}
    for key in ('related_skills','prereq_skills'):
        for s in h.get(key) or []:
            if s not in local and s not in EXTERNAL_OK:
                bad.append((os.path.basename(os.path.dirname(f)), key, s))
print("DANGLING:", bad or "none — CLEAN")
PY
```
```
HEAD:    [('multi-agent-mux-monitor','related_skills','kanban-orchestrator'),
          ('multi-agent-mux-monitor','prereq_skills','kanban-worker')]
적용 후:  none — CLEAN
```

### G-G — `prereq_skills` 그래프 순환 부재 🆕 **Rev.2 신규**

*(측정: HEAD **DAG** → 적용 후 **DAG**)*

맹점 1이 제기한 "자율 디스패처가 prereq 메타데이터로 오작동한다"는 우려의 **검증 가능한 형태**다. 디스패처를 실제로 무한루프에 빠뜨리는 것은 *논쟁의 여지가 있는 의미론*이 아니라 *순환*이다. F1 이 `monitor.related_skills` 에 `status` 를 넣으므로(`status.related_skills` 에는 이미 `monitor` 존재) 상호 참조가 생기는데, **`related_skills` 의 상호 참조는 무해하고 `create`↔`resume` 에 이미 존재한다**. 위험한 것은 `prereq_skills` 순환뿐이며, 이 게이트가 그것만 정확히 겨냥한다.

```bash
python3 - <<'PY'
import glob, yaml
g = {}
for f in glob.glob('.agents/skills/*/SKILL.md'):
    d = yaml.safe_load(open(f, encoding='utf-8').read().split('---',2)[1]) or {}
    g[d['name']] = list(((d.get('metadata') or {}).get('hermes') or {}).get('prereq_skills') or [])
def cyc(n, seen):
    if n in seen: return seen[seen.index(n):] + [n]
    for m in g.get(n, []):
        if m in g:
            r = cyc(m, seen + [n])
            if r: return r
    return None
found = [c for n in g if (c := cyc(n, []))]
print("CYCLES:", found or "none — DAG")
PY
```
적용 후 실측 그래프:
```
create        -> ['claude-code']            monitor -> ['multi-agent-mux-create']
loop          -> ['multi-agent-mux-create'] resume  -> ['multi-agent-mux-create']
status        -> ['multi-agent-mux-create', 'multi-agent-mux-monitor']
stop          -> ['multi-agent-mux-create', 'multi-agent-mux-resume']
delegate-job  -> []
CYCLES: none — DAG
```

**G-F — 회귀 테스트: 불필요 (명시적 판단)**

`tests/` 전량에 SKILL.md·README 를 읽는 코드가 **0건**(`grep -rln "SKILL.md\|README" tests/` → 없음), CI 문서 린트 없음(`.github/workflows/` 부재), 변경 파일에 스크립트 **0개**. 8분짜리 전체 스위트는 이 변경에 대해 **아무것도 증명하지 못한다**. 형식적 완결성을 위해 돌리는 것을 막지 않되 **계획은 요구하지 않는다.** 실제 위험은 G-A~G-G 가 덮는다.

**측정된 변경 규모** (`diff -ru base clone`, 총 328줄):
```
  -74  +53   .agents/skills/multi-agent-mux-monitor/SKILL.md   (237줄 → 218줄)
  -5   +5    .agents/skills/multi-agent-mux-status/SKILL.md
  -2   +2    .agents/skills/multi-agent-mux-create/SKILL.md
  -1   +1    README.md
  -1   +1    README.ko.md
```

---

## 7. 이의제기 심층 판정

### 7-A. 맹점 1 — `status.prereq_skills` 에서 `monitor` 제거 → **기각**

**주장**: status 는 단발성 read-only 스냅샷이므로 monitor 를 선행 필수로 두는 것은 논리적 모순이다.

**반증 1 — 의존은 실재한다.**
```
.agents/skills/multi-agent-mux-status/scripts/status.sh:12
RECONCILE="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)/multi-agent-mux-monitor/scripts/reconcile.sh"
```
`status.sh` 는 monitor 스킬 디렉터리의 스크립트를 **경로로 하드 참조**한다. `status/SKILL.md:46` 도 *"Calls `reconcile.sh --once --emit-diff --dry-run`"* 이라 명시한다. monitor 스킬을 제거하면 **status 는 즉시 깨진다.** 이는 저장소 전체에서 `prereq_skills` 항목이 파일시스템 수준에서 문자 그대로 참인 **유일한 사례**다. 제거하면 메타데이터가 *덜* 정확해진다.

**반증 2 — "선행 실행" 해석은 파일 자신의 용례가 부정한다.** 실측된 6개 전량:
```
create  -> ['claude-code']                            ← 스킬로 "실행"하는 대상이 아님. 설치 여부다.
stop    -> ['multi-agent-mux-create','...-resume']    ← stop 전에 resume 을 실행할 필요는 없다.
```
두 건이 각각 독립적으로 "선행 *실행*" 해석을 반증한다. 저장소 관례는 **가용성(availability)** 이다. 그 해석에서 `status -> monitor` 는 모순이 아니라 **정확한 서술**이다.

**반증 3 — 소비자가 없다.** `prereq_skills` / `related_skills` / `environments` 를 읽는 코드는 저장소 전체에 **0건**(`.py`/`.sh` 전수 검색). 주장된 "디스패처 오작동"은 이 저장소 안에서는 재현 불가능한 가정이다. 외부 디스패처를 가정하더라도 반증 1·2 가 우선한다.

**그럼에도 남는 실재 문제**: `prereq_skills` 의 의미론이 **어디에도 정의되어 있지 않다**. `agy` 가 그럴듯하게 오독했다는 사실 자체가 모호성의 증거다. 다만 올바른 해소는 *참인 의존을 지우는 것*이 아니라 *의미론을 문서화하는 것*이며, 이는 C-1(Kanban 제거)과 무관한 별개 과제다 → §9 로 분리 제안.

### 7-B. 맹점 2 — G-C 정규식 보강 → **인용하되 확대**

지적은 정확하다. Rev.1 G-C 는 `--goal-max-turns` 를 놓쳤다. 다만 §6 G-C 표가 보이듯 **놓친 것은 10개**이고 제안된 `--goal` 추가는 그중 2개만 막는다. 나머지 8개(`--assignee`, `--comment-card`, `--max-runtime`, `--max-retries`, `--title`, `--branch`, `--body`, `--skill`)는 `agy` 의 패턴으로도 통과한다.

근본 원인은 특정 단어 누락이 아니라 **"기억으로 열거한 목록"이라는 방법론**이다. 그래서 Rev.2 는 단어를 더 넣는 대신 목록을 **삭제 대상 텍스트에서 파생**시킨다. 이 방법에서는 정의상 누락이 발생하지 않는다.

> 이 지적이 없었다면 Rev.1 은 8개의 추가 구멍을 가진 채 통과했을 것이다. **부분 편집(전체 교체가 아닌)으로 F1 을 수행한 Creator에게는 실제로 발생할 수 있는 실패**였다.

---

## 8. 함께 해소되는 문서 정합성 결함 2건

**(a) 존재하지 않는 상태 파일 — C-2 와 교차**

원문 L169 는 *"The script tracks a state file at `.cache/multi-agent-mux-monitor/<workspace>.state`"* 라 단언한다. 실측:
```
reconcile.sh:23   STATE_DIR="${AGENT_SESSIONS_STATE_DIR:-…/.cache/multi-agent-mux-monitor}"
reconcile.sh:313  mkdir -p "$STATE_DIR"
              → 읽기 0회, 쓰기 0회
```
`mkdir` 만 하고 아무것도 저장하지 않는다(IMPROVEMENTS **C-2** 가 코드 측면에서 동일 사실 추적 중). 대체 문장은 **중복 병합 책임을 caller 에게 명시**하고 상태 파일 주장은 싣지 않는다.
> 이 계획은 `.cache` **코드**를 건드리지 않는다. 그건 C-2 소관이다. 문서가 거짓을 말하지 않게만 한다.

**(b) `--subscribe` 폴백 경로의 stdout 동작**

Rev.1 초안에서 4번 항목을 "모든 drift 를 stdout JSON 으로 방출"이라 썼다가 스크립트 확인 중 정정했다:
```
reconcile.sh:303   bash "$_self" --once --emit-diff >/dev/null 2>&1 || true
```
브로커 다운 폴백 루프는 **stdout 을 버린다**. `--subscribe` 상태에서 drift JSON 을 소비할 수 없다. 최종 문안은 이 단서를 명시한다 — 원문에 없던 정보이며, 이를 믿고 파이프를 붙이려는 사용자를 구한다.

---

## 9. 후속 제안 (C-1 범위 밖, 별도 항목)

**제안: `prereq_skills` / `related_skills` 의미론 정의** — §7-A 가 드러낸 실재 모호성이다.

- 현상: 두 필드의 의미가 어디에도 정의되지 않아 "선행 실행"과 "선행 가용성"으로 갈리게 읽힌다. 실제로 Creator 가 후자를 전자로 오독해 참인 의존을 삭제하자고 제안했다.
- 소비 코드 0건이므로 **런타임 영향은 없으나**, 외부 디스패처 연동 시 실제 오작동 원인이 된다.
- 제안: `MULTI_AGENT_RULES.md` 또는 스킬 작성 가이드에 1문단으로 정의하고, G-E·G-G 를 상시 린트로 승격.
- **C-1 에 병합하지 않는 이유**: Kanban 잔재 제거와 무관하며, 병합하면 "0건 검증"이라는 단일 합격 기준이 흐려진다.

---

## 10. 리스크

| ID | 리스크 | 평가 / 완화 |
|---|---|---|
| **RK-A** | 런타임 회귀 | **없음.** 변경 5파일 전부 `.md`. 코드 참조 0건 실측. |
| **RK-B** | `environments: [kanban,…]` 제거가 외부 스킬 레지스트리 로딩을 깬다 | 저장소 내 `environments` 소비 코드 0건. 나머지 6개 스킬이 이미 `[terminal, herdr]` → **다수 관례에 합류**. |
| **RK-C** | §Dispatching 삭제로 "어떻게 띄우나"를 잃음 | 삭제가 아니라 **교체**. `hermes` 미설치 상태에서 원문 명령은 애초에 실행 불가였고, `--subscribe` 가 실제 동작하는 유일한 방법이다. |
| **RK-D** | 리뷰어가 IMPROVEMENTS.md 잔존 2건으로 FAIL 판정 | §3 에서 범위 사전 정의 + G-A/G-B 명령 제공. |
| **RK-E** | Worker body template 삭제로 Hard rules 상실 | §Supervising-agent runbook 으로 전환, **Hard rules 4개 전량 보존**. |
| **RK-F** | 향후 Kanban 문구 복귀 | G-C(파생형)를 리뷰 체크리스트 상시 항목으로. 자동 린트 승격은 §9. |
| **RK-G** 🆕 | 후속 리뷰어가 맹점 1을 재차 제기해 `status.prereq_skills` 를 임의 수정 | §7-A 를 계획에 명시적으로 남겨 판정 근거(`status.sh:12`)를 보존. **삭제 시 G-E 는 통과하지만 문서가 거짓이 된다** — 게이트로는 잡히지 않는 유형이므로 서술로 방어한다. |
| **RK-H** 🆕 | G-C 의 `--title`/`--body`/`--branch`/`--skill` 이 훗날 정당하게 등장해 오탐 | 현재 제품 표면 등장 0건 실측. 오탐 시 **해당 토큰만** 근거와 함께 제외하고, 파생 방식 자체는 유지할 것. |

---

## 11. 역할 경계

본 문서는 **설계 산출물**이며 저장소 코드·문서는 **한 줄도 수정하지 않았다**. 모든 프로토타이핑은 세션 스크래치패드 내 `git archive HEAD` 클론에서만 수행했고, 작업 트리는 `924e77e` 기준 clean 을 유지한다.

`MULTI_AGENT_RULES.md` §1 에 따라 **구현은 Creator, 커밋은 GM 소관**이다. **차단 항목 없음.**

---

## 부록 A — `multi-agent-mux-monitor/SKILL.md` 최종 전문 (F1 적용본, 218줄)

````markdown
---
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: 1.0.0
author: godopu
license: MIT
platforms: [linux, macos]
environments: [terminal, herdr]
metadata:
  hermes:
    tags: [agent, herdr, claude, antigravity, agy, monitor, observation, reconciliation]
    related_skills: [multi-agent-mux-create, multi-agent-mux-resume, multi-agent-mux-stop, multi-agent-mux-status]
    prereq_skills: [multi-agent-mux-create]
---

# Agent Sessions Monitor — Live Reconciliation

> **Companion skills**: `multi-agent-mux-create` / `multi-agent-mux-resume` / `multi-agent-mux-stop` (mutators); this skill is the **observer**.
> **Single source of truth**: `./.mam/agent-sessions.yaml`.

## What this skill does

Run a **long-lived reconciler** (`reconcile.sh --subscribe`) that:

1. Reacts to delegated-job events on the MQTT broker, and — whenever the broker is
   unreachable — falls back to polling every `RECONCILE_POLL_INTERVAL` (default 15s)
   the actual state of:
   - `herdr agent list` (which sessions are alive)
   - `herdr agent get <session>` (pane cmd, cwd)
   - `~/.claude/projects/<workspace-key>/*.jsonl` mtime + first-line sessionId
   - `~/.gemini/antigravity-cli/cache/last_conversations.json` (agy workspace → conversation mapping)
   - `~/.gemini/antigravity-cli/conversations/<uuid>.db` mtime (agy)
2. Compares the live state to `agent-sessions.yaml`
3. Detects 4 classes of drift:
   - **yaml-only terminated/archived/stopped**: herdr dead, YAML says `terminated`, `archived`, or `stopped` → OK, left untouched (deliberate end states)
   - **yaml-only running, herdr dead**: YAML says `running`, herdr is gone → mark `terminated` with timestamp
   - **herdr-only running, not in YAML**: herdr session exists with `<workspace>-creator-*` naming but YAML doesn't know about it → register as a new entry
   - **stale UUID**: YAML has a UUID, but the on-disk artifact is gone → report it
4. Emits a JSON drift record on stdout for every drift event when run with `--emit-diff`
   (note: the `--subscribe` broker-down fallback runs each pass for its YAML side-effects
   and discards the JSON — capture drift output with an explicit `--once --emit-diff`)
5. Keeps running until one of its exit conditions fires: `--timeout` (wall-clock),
   `--idle-timeout` (no message received), or an interrupt from the operator.

## When to use

- You have multiple workspaces with herdr agent sessions and want a single source of truth
- You suspect YAML drift after a host reboot / crash
- You want a notification when a session id was just created (so you can record it before next restart)
- You're running multi-day work and want to know "what's actually running right now"

## When NOT to use

- One-off interactive session — just check `herdr agent list` and read the YAML
- A single, short session — overhead > benefit
- You only need a point-in-time answer — use `multi-agent-mux-status` instead

## Running the monitor

```bash
# Persistent monitor: runs until interrupted; polls if the broker is unreachable.
bash .agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh --subscribe --idle-timeout 0

# Bounded run: exits after 5 min with no message, or 1 h wall-clock, whichever comes first.
bash .agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh --subscribe --idle-timeout 300 --timeout 3600
```

Run it under whatever supervisor you already use (a dedicated herdr pane, `nohup`,
or a background job). Nothing else needs to be running for the monitor to work —
it reconciles YAML ↔ herdr ↔ disk on its own.

The herdr commands the script issues (`herdr agent list`, `herdr agent get <session>`)
are real native herdr commands — do not substitute tmux-era names like `herdr ls` /
`herdr list-panes` outside a shell that has sourced `.agents/skills/lib.sh`.

## Helper script: `reconcile.sh`

This is the whole monitor — there is no separate driver. Each pass:

1. Diffs YAML ↔ herdr ↔ disk artifacts
2. Updates YAML if needed (only when changes are real, not on every poll — avoids spamming)
3. Emits a JSON diff to stdout for the caller to consume

```bash
# Reconcile + auto-update YAML (atomic, flock-guarded). Emits JSON drift to stdout.
bash .agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh --once --emit-diff

# Read-only: compute drift WITHOUT writing the YAML (use for "what's running?" checks).
bash .agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh --once --emit-diff --dry-run
```

Flags: `--once` (single pass), `--emit-diff` (print JSON), `--dry-run` (P1-E — no mutation), `--subscribe` (push-based MQTT subscription monitoring). `--subscribe` sub-flags: `--timeout N` (exit after N seconds of wall-clock; `0` = no limit, default), `--idle-timeout N` (exit after N seconds with no message; default `3600`, `0` = never idle-out). On a broker connection failure (connect error **or** non-zero CONNACK), `--subscribe` falls back to a polling loop that re-runs `--once --emit-diff` every `RECONCILE_POLL_INTERVAL` (default 15) seconds until `--timeout`. Terminal-event YAML updates are written through `lib.sh::atomic_dump_yaml` (flock + schema-validate + `.bak`). There are **no** `--workspace` / `--agent` flags; the emitted JSON `drifts[]` is the caller's to consume.

## Drift classes (what the script handles)

### Status Enum
The `status` field MUST be one of the following exact strings: `running`, `stopped`, `terminated`, `archived`.
The `last_visible_status` is a free-form human-readable status string (e.g. verification-cycle states: `unverified`, `pinned`, `resume_verified`, or a failure detail string) and is NOT constrained to this enum.
Any unstructured comments or reasons for the status change should be placed in `last_visible_note` or `termination_mode`.

### A. herdr dead, YAML says running → auto-terminate

```
YAML:  status=running, pane.pid=201132, cmd=claude
herdr:  no session
       → set status=terminated, terminated_at=<now>, termination_mode=auto-detected
       → report: "lab-landing-page-creator-claude: herdr gone (was pane 201132, cmd claude). Marked terminated."
```

**Skip-set**: the auto-terminate only fires for sessions whose status is `running`.
Rows already in a deliberate end state — `terminated`, `archived`, or **`stopped`**
(set by `multi-agent-mux-stop`) — are
left untouched. This is critical: a `stopped` row keeps its `resumable: true` and
captured `*_session_id_own`, so the monitor must **not** overwrite it with
`terminated ("auto-detected")` when its herdr is (expectedly) gone.

### B. herdr alive, not in YAML → auto-register

```
herdr:  session=lab-paper-pdf2md-creator-agy, pid=...,
       cmd=agy, cwd=$WORKSPACE_ROOT/paper-pdf2md
YAML:  no such session
       → register as new entry: status=running, last_visible_status=running, last_visible_note=auto-registered
       → report: "lab-paper-pdf2md-creator-agy: herdr found but not in YAML. Auto-registered."
```

### C. New session id materializes (claude first message sent)

```
YAML:  claude_session_id_own=null (placeholder)
disk:  ~/.claude/projects/.../b3a7...c2f.jsonl exists, mtime=now,
       first line sessionId=b3a7...c2f
       → update claude_session_id_own=b3a7...c2f
       → report: "lab-landing-page-creator-claude: session id materialized b3a7...c2f"
```

### D. Stale UUID (artifact gone)

```
YAML:  agent_identities.claude.session_id=87dc548e-...
disk:  ~/.claude/projects/.../87dc548e-...jsonl: missing
       → report it, but DO NOT delete from YAML
       (the user may have moved the file or the disk may be temporarily unavailable;
        only `--purge-conversation` should remove the id)
```

## Pitfalls

- **Don't expect `--once` to stay alive** — it does a single pass and exits. Use `--subscribe` for continuous monitoring.
- **`--idle-timeout` defaults to 3600s** — a monitor meant to run indefinitely needs `--idle-timeout 0` explicitly, or it will quietly exit after an hour of broker silence.
- **The poll interval is a default** — `RECONCILE_POLL_INTERVAL` (15s) is what the broker-down fallback uses. A workspace with 5+ agent sessions can bump it to reduce noise.
- **Coalesce repeated drifts** — the same drift re-appears on every pass until it is resolved. A caller that acts on `drifts[]` should compare against the previous pass and act only on *new* entries; the script does not deduplicate for you.
- **Don't fight the user's explicit action** — if `multi-agent-mux-stop` is mid-flight and the monitor sees the same session in two states within 5s, prefer the user's most recent action. The monitor should not auto-revert a fresh `terminated` to `running` because of a stale `herdr has-session` check.
- **The monitor should never modify the conversation artifacts** (jsonl, db) — only the YAML. If you see a stale UUID, report it but don't delete the file.
- **TUI capture-pane is expensive** — only capture when you need to update `last_visible_status`, not every poll.

## Supervising-agent runbook

If an agent drives the monitor rather than an operator watching it directly, this is
the behavior spec:

```markdown
# agent-sessions monitor

## Loop

1. Read agent-sessions.yaml
2. Bash: `bash .agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh --once --emit-diff`
3. Parse the JSON diff from stdout
4. If `drifts` is non-empty, report each *new* drift to the operator
5. Bash: `sleep 30`, then repeat

## Stop condition

Stop when the operator says to stop, or when the surrounding job's timeout fires.

## Drift responses

- A. herdr dead + YAML running: auto-terminate YAML, report
- B. herdr alive not in YAML: auto-register, report
- C. New session id from *.jsonl: update YAML, report
- D. Stale UUID: report only, no YAML change

## Hard rules

- Do NOT modify conversation artifacts (jsonl, db, brain/)
- Do NOT spawn/delete herdr sessions — that's the create/delete skills' job
- Do NOT call multi-agent-mux-create or multi-agent-mux-stop — only the user initiates those
- Do NOT call `git commit` / `git push`
```

## Security: --subscribe on Public Brokers

When using `--subscribe` with the default PoC public broker
(`broker.hivemq.com:1883`), be aware that:

1. **Wildcard subscription** means anyone can publish events to your job topics.
2. **Auto-kill on terminal events** means a spoofed `completed` or `error`
   event from a third party can terminate your agent session.
3. **Mitigation**: Use `--subscribe` only on private TLS-enabled brokers
   (production mode). For PoC, prefer polling-based monitor (`--once` or
   no `--subscribe`) which reads YAML/herdr state directly without MQTT.
4. **HMAC verification**: Events are now verified via `verify_hmac()` in
   `mqtt_common.py` (see FW-05). Ensure `auth_token` is set for each job
   to enable signature validation — unauthenticated events will be dropped.

## Verification (one-shot)

```bash
# Run reconcile once and inspect output
bash .agents/skills/multi-agent-mux-monitor/scripts/reconcile.sh --emit-diff --once \
  | python3 -m json.tool
```

## Related skills

- `multi-agent-mux-status` — read-only snapshot when you don't need a running loop
- `multi-agent-mux-delegate-job` — the MQTT job channel whose events `--subscribe` listens to
````

---

## 부록 B — F2~F4 자동 적용 스크립트

각 치환의 등장 횟수가 정확히 1인지 검사하고 아니면 즉시 중단한다. `replace_all` 사고를 구조적으로 막는다.

```python
import io, sys

def sub(path, pairs):
    s = io.open(path, encoding='utf-8').read()
    for old, new in pairs:
        n = s.count(old)
        if n != 1:
            print(f"!! {path}: expected 1 occurrence, got {n} for: {old[:70]!r}")
            sys.exit(1)
        s = s.replace(old, new)
    io.open(path, 'w', encoding='utf-8').write(s)
    print(f"ok {path}  ({len(pairs)} edits)")

sub('.agents/skills/multi-agent-mux-status/SKILL.md', [
 ("resume UUID on disk, and any drift. No Kanban, no mutation. Reuses",
  "resume UUID on disk, and any drift. No mutation. Reuses"),
 ("without spinning up a Kanban monitor worker.",
  "without spinning up the monitor loop."),
 ("**No mutation. No Kanban. No polling loop.**",
  "**No mutation. No polling loop.**"),
 ("dispatch `multi-agent-mux-monitor` (Kanban worker) or run",
  "run `multi-agent-mux-monitor` or"),
 ("- Quick sanity check before dispatching a long Kanban task",
  "- Quick sanity check before dispatching a long-running job"),
 ("- Continuous live tracking → `multi-agent-mux-monitor` (Kanban worker)",
  "- Continuous live tracking → `multi-agent-mux-monitor`"),
])

sub('.agents/skills/multi-agent-mux-create/SKILL.md', [
 ('If any check fails → `kanban_block(reason="...")` (worker path) or report to user (interactive path).',
  "If any check fails → abort with a non-zero exit and report the reason (automated path) or report to user (interactive path)."),
 ("(e.g., cmux, Kanban workers, manual herdr sessions)",
  "(e.g., cmux, background workers, manual herdr sessions)"),
])

sub('README.md', [
 ("A long-running Kanban reconcile worker that dynamically monitors",
  "A long-running reconcile worker that dynamically monitors"),
])

sub('README.ko.md', [
 ("백그라운드에서 Kanban Reconcile 프로세스로 실행되어",
  "백그라운드 Reconcile 프로세스로 실행되어"),
])
```

실측 출력:
```
ok .agents/skills/multi-agent-mux-status/SKILL.md  (6 edits)
ok .agents/skills/multi-agent-mux-create/SKILL.md  (2 edits)
ok README.md  (1 edits)
ok README.ko.md  (1 edits)
```

---

## 부록 C — G-C 게이트 전문 (`.mam/gates/gate_gc.py`) 🆕 Rev.2

토큰 목록은 **F1 이 삭제하는 텍스트에서 파생**한 것이지 손으로 열거한 것이 아니다.
HEAD 에서 `61 FAIL`, 적용 후 `0 PASS` 로 red/green 실측 완료.

```python
#!/usr/bin/env python3
"""G-C — Kanban CLI/API surface residue gate.
Token list is DERIVED from the text F1 deletes, not hand-enumerated."""
import sys, glob

KANBAN_SURFACE = [
    "kanban", "KANBAN", "Kanban",
    "kanban_comment", "kanban_block", "kanban_heartbeat",
    "HERMES_KANBAN_TASK", "goal_mode",
    "--goal", "--goal-max-turns", "--comment-card",
    "--assignee", "--max-runtime", "--max-retries",
    "--title", "--branch", "--body", "--skill",
]
# Removed by F1 but NOT Kanban surface: a real env var (reconcile.sh:23) whose doc
# line goes away for a different reason (false .cache claim, plan §8-a). Never
# make it a forbidden word — it may legitimately be re-documented later.
EXCLUDED = {"AGENT_SESSIONS_STATE_DIR"}

targets = sorted(glob.glob('.agents/skills/*/SKILL.md')) + ['README.md', 'README.ko.md']
hits = []
for f in targets:
    try:
        lines = open(f, encoding='utf-8').read().splitlines()
    except FileNotFoundError:
        continue
    for i, ln in enumerate(lines, 1):
        for t in KANBAN_SURFACE:
            if t in ln:
                hits.append((f, i, t, ln.strip()[:70]))
if hits:
    print(f"G-C FAIL — {len(hits)} Kanban-surface hit(s):")
    for f, i, t, s in hits:
        print(f"   {f}:{i}  [{t}]  {s}")
    sys.exit(1)
print("G-C PASS — 0 Kanban CLI/API surface tokens")
```

**호출 시 주의**: 실패 판정은 **exit code** 로 받아야 한다. `| head` 같은 파이프를 붙이면 파이프라인 종료코드가 마지막 명령의 것으로 바뀌어 FAIL 이 0 으로 보인다.
```bash
python3 .mam/gates/gate_gc.py && echo "G-C ok" || echo "G-C FAILED"
```

**파생 절차 재현법** (토큰 목록을 다시 뽑아야 할 때):
```python
import re, subprocess, io
head = subprocess.run(['git','show','HEAD:.agents/skills/multi-agent-mux-monitor/SKILL.md'],
                      capture_output=True, text=True).stdout
new  = io.open('.agents/skills/multi-agent-mux-monitor/SKILL.md', encoding='utf-8').read()
TOK = re.compile(r'--[a-zA-Z][\w-]+|[A-Za-z_][\w]*_[\w]+|\b[A-Z][A-Z0-9_]{3,}\b')
print(sorted(set(TOK.findall(head)) - set(TOK.findall(new))))
```
이 절차가 산출한 16개 토큰 중 15개가 Kanban 표면, 1개(`AGENT_SESSIONS_STATE_DIR`)가 제외 대상이다.

---

[AGREEMENT: REACHED]
