Files
multi-agent-mux/README.ko.md
T

243 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# herdr-agent-orchestration (다중 에이전트 Herdr 오케스트레이션 및 메시징 백플레인)
[herdr](https://herdr.dev)와 MQTT 위에 만든 **다중 에이전트 오케스트레이션·메시징 백플레인**입니다. 장시간 실행되는 코딩 에이전트(`claude`, `agy`, `hermes`, `grok`, `opencode`, `codex`)를 격리된 herdr pane 에서 돌리고, 작업을 추적되는 잡으로 위임하며, 모든 리뷰어가 통과시킬 때까지 planner / creator / reviewer 루프를 돌립니다.
[![Version: v4.9.8](https://img.shields.io/badge/version-v4.9.8-blue.svg)](VERSIONS.md)
---
## 🚀 개요
긴 에이전트 작업은 정해진 방식으로 깨집니다. 터미널과 함께 세션이 죽고, 한 pane 에 잡 두 개가 들어가고, 오케스트레이터는 바쁜 에이전트와 죽은 에이전트를 구분하지 못하고, 리뷰는 읽은 뒤에 바뀐 코드를 승인합니다. Multi-Agent Mux(MAM)는 이를 다음으로 막습니다.
1. **격리되고 재개 가능한 에이전트 세션.** 에이전트마다 자기 herdr pane 에서 돌고, 보통 워크스페이스 전용 herdr 서버를 씁니다. 세션은 대화 ID 와 함께 `.mam/agent-sessions.yaml`(SQLite 백업)에 기록되므로, 멈춘 에이전트를 나중에 같은 대화로 재개할 수 있습니다.
2. **화면이 아니라 이벤트로 판정하는 잡.** 위임 잡은 레지스트리 레코드와 MQTT 이벤트 흐름(`started`, `progress`, `completed`, `error`)입니다. 에이전트는 정해진 시간 안에 프롬프트 수신을 알리고, 일하는 동안 heartbeat 를 보내야 합니다. 잡을 닫는 것은 프로토콜 이벤트나 시간 초과뿐이며, 터미널을 읽어 판정하지 않습니다.
3. **세션당 한 번에 잡 하나.** 프롬프트를 넣기 전에 디스패처가 레지스트리로 그 세션의 앞선 잡(온보딩 포함)이 끝나기를 기다립니다. pane 배치는 herdr 서버마다 직렬화되어, 동시에 실행한 `create` 가 같은 pane 을 차지하지 않습니다.
4. **근거를 고정한 리뷰 루프.** `multi-agent-mux-loop` 는 변경을 선택한 리뷰어에게 보내고, 그 전원의 `PASS` 를 요구합니다. 리뷰어는 `--reviewer` 로 지정하거나 `--all-reviewer` 로 실행 중인 리뷰어 전원을 고르며, 둘 다 없으면 creator 가 스스로 리뷰합니다. `--worktree`/`--base` 를 쓰면 리뷰 라운드마다 소스 스냅샷에 묶이며, 라운드 중 소스가 바뀌면 그 승인은 버리고 다시 리뷰합니다.
---
## 📦 설치 및 설정
아무 워크스페이스 디렉터리에서 MAM 을 설치합니다:
```bash
curl -fsSL https://git.godopu.com/tmpl/multi-agent-mux/raw/branch/main/deploy/install.sh | bash
```
로컬 클론에서는:
```bash
bash deploy/install.sh
```
`install.sh` 는 여러 번 실행해도 안전합니다. 하는 일은 다음과 같습니다.
- herdr 와 Python 3.9+ 가 설치돼 있는지, 시스템 `python3` 에 PyYAML 이 있는지 확인합니다.
- `.agents/` 와 `.mam.env.example` 을 워크스페이스에 복사합니다.
- `.venv` 를 만들고 `paho-mqtt`·PyYAML 을 설치합니다.
- `MQTT_BROKER=localhost` 로 된 초기 `.mam.env` 를 씁니다. 이미 있는 `.mam.env` 는 그대로 둡니다. 예전 `.env` 만 있으면 그것을 `.mam.env` 로 복사하고 원본은 남깁니다.
- `.venv`, `.mam/`, `.mam.env` 를 `.gitignore` 에 추가합니다.
`.mam.env` 의 `MQTT_BROKER` 를 직접 운영하는 브로커로 바꾸십시오(사설 브로커는 [MESSAGING.md](MESSAGING.md) 참고). `MQTT_BROKER` 가 아예 설정되지 않으면 코드는 공개 브로커 `broker.hivemq.com` 을 씁니다. 시험용으로는 괜찮지만 실제 작업용은 아닙니다. `deploy/generate-env.sh` 는 `.mam.env` 가 없을 때 `.mam.env.example` 로 만듭니다(`--force` 는 `.mam.env.bak` 을 남기고 덮어씁니다). 업데이트와 제거는 `deploy/update.sh`, `deploy/remove.sh` 입니다. `deploy/install_mam.sh` 는 로컬 클론에서 다른 디렉터리로 복사해 설치하는 별도 설치기입니다.
---
## 🛠️ 핵심 스킬
모든 스킬은 `.agents/skills/` 아래에 있고, 각 `SKILL.md` 가 옵션과 종료 코드를 설명합니다.
| 스킬 | 하는 일 |
|---|---|
| **`multi-agent-mux-create`** | 에이전트를 herdr pane 에서 시작하고 세션을 등록합니다. `--onboard` 는 온보딩 잡이 끝날 때까지 기다립니다(실패하면 종료 코드 44, 시간 안에 끝나지 않으면 45; 세션은 남김). `scripts/worktree_prepare.sh` 는 creator 용 이슈 워크트리를 준비합니다. |
| **`multi-agent-mux-stop`** | 세션을 정상 종료합니다. 대화 ID 를 행에 저장합니다(`status: stopped`, `resumable: true`). `--purge-conversation --yes` 는 대신 대화를 지웁니다. `scripts/worktree_remove.sh` 는 서브모듈까지 깨끗한지 확인한 뒤에만 이슈 워크트리를 지웁니다. |
| **`multi-agent-mux-resume`** | 멈춘 세션을 저장된 대화로 되살립니다(`claude -r`, `agy --conversation`, `hermes --resume`, `grok --resume`, `opencode --session`, `codex resume`). 세션이 원래 돌던 디렉터리에서만 재개합니다. |
| **`multi-agent-mux-status`** | 모든 세션의 읽기 전용 스냅샷: 레지스트리 상태, herdr 생존, pane 명령과 cwd, 재개 ID, 불일치(drift). |
| **`multi-agent-mux-monitor`** | 장기 실행 조정자(`reconcile.sh --subscribe`). 잡 이벤트에 반응하고, 브로커가 죽으면 폴링으로 돌아가며, 레지스트리 불일치를 고칩니다. 예를 들어 죽은 세션을 `terminated` 로 표시하고, 레지스트리가 모르는 살아 있는 세션을 등록합니다. |
| **`multi-agent-mux-delegate-job`** | 세션(`herdr:<name>`)이나 다른 호스트의 러너(`remote:<name>`)에 잡을 위임하고 결과를 기다립니다. [위임 계약](#-위임-계약)을 보십시오. |
| **`multi-agent-mux-loop`** | 계획·구현·리뷰: 선택적인 planner 와 creator, 그다음 선택한 리뷰어(`--reviewer A,B`, 또는 `--all-reviewer` 로 creator 를 뺀 실행 중인 리뷰어 전원)가 모두 `PASS` 를 낼 때까지(또는 `--max-loop` 까지). 둘 다 없으면 creator 가 스스로 리뷰합니다. 루프 밖에서 끝난 작업은 `--review-only` 로 리뷰합니다. 통과한 리포트는 `.agents/reports/<session>/` 으로 옮겨 보관합니다. |
| **`multi-agent-mux-orc-onboard`** | 오케스트레이터 자신의 세션 ID 를 등록해, 세션 탐색이 그것을 하위 에이전트로 착각하지 않게 합니다. |
---
## 📐 아키텍처
MAM 은 각자 레지스트리를 가진 두 계층으로 이루어집니다.
1. **세션 (herdr)**: `lib.sh` 와 create/stop/resume/status/monitor 스크립트가 에이전트를 돌리고, `.mam/agent-sessions.yaml`(+ `.mam/agent-sessions.db`)을 기준 기록으로 유지합니다.
2. **잡 (위임)**: `lib_py/delegation/` 이 `.mam/jobs/<id>.json` 에 잡을 등록하고, 브리프를 주입하고, MQTT 로 잡의 이벤트를 따라갑니다. 모든 이벤트는 잡의 감사 로그 `.mam/delegate_job_logs/<id>/` 에도 덧붙여집니다.
```text
+--------------+ register / pick +----------------------+
| orchestrator | -----------------> | .mam/jobs/<id>.json | live record
| (wrapper) | +----------+-----------+
+------+-------+ | every transition
| inject brief v
v +----------------------+
+--------------+ publish_event | .mam/delegate_job_ | append-only
| agent pane | --------+ | logs/<id>/ | audit log
+--------------+ | +----------------------+
v
+-------------+ events + retained state
| MQTT broker | -------------------------> job_subscriber (orchestrator)
+-------------+ -------------------------> monitor (reconcile.sh)
|
v
.mam/agent-sessions.yaml / .db
```
### 🔒 Herdr 서버 격리
- **워크스페이스 전용 서버.** `create` 는 기본으로 워크스페이스 이름(slug)을 딴 herdr 서버에서 에이전트를 돌립니다. 이 서버는 기본 herdr 와 분리된 자기 소켓을 씁니다. `--herdr-session <name>` 또는 `HERDR_SESSION_NAME` 으로 다른 이름을 고를 수 있습니다. 서버 이름은 세션 행에 기록되고, 이후 모든 명령이 그 서버로 갑니다.
- **shim.** `lib.sh` 를 source 하면 `<workspace>/.mam/shim/herdr` 를 쓰고 `PATH` 맨 앞에 둡니다. shim 은 스크립트가 쓰는 tmux 식 명령(`new-session`, `has-session` …)을 herdr 의 에이전트 API 위에 제공합니다. 이름 있는 서버면 호출마다 `--session <server>` 를 붙이고, 그 서버가 돌고 있지 않으면 헤드리스로 시작합니다. 단 `MAM_NO_AUTOSTART=1` 이면 시작하지 않습니다. `status` 와 `monitor` 가 이 값을 설정하므로, 조회만으로 서버가 뜨는 일은 없습니다. `default` 서버 호출은 바꾸지 않고 사용자의 herdr 로 그대로 갑니다.
- **예상치 못한 재실행 없음.** MAM 이 시작한 서버는 보통 사용자의 herdr 설정에 `resume_agents_on_restore = false` 를 더해 씁니다. 그래서 herdr 가 재시작돼도 저장된 에이전트를 권한 플래그 없이 다시 띄우지 않습니다. `tomllib` 이 없는 Python(macOS 기본 3.9 등)에서는 이 파일에 그 키만 들어가므로, restore 는 꺼지지만 사용자의 다른 herdr 설정은 반영되지 않습니다. 이때 부트스트랩이 경고합니다. 설정 파일을 아예 쓰지 못한 경우에만 서버가 restore 가 켜진 채 시작하며, 이때도 경고를 남깁니다.
- **pane 배치는 한 번에 하나.** pane 배치는 서버별 잠금(`lib_py/pane_lock.py`, macOS·Linux 에서 `flock`)을 잡고 합니다. 한 폴더에서 동시에 실행한 `create` 도 각자 자기 pane 을 받습니다.
### 🛡️ 레지스트리 쓰기와 동시성
- **직렬화된 쓰기.** `agent-sessions.yaml` 의 모든 변경은 `lib.sh::atomic_dump_yaml` 을 거칩니다. `.mam/agent-sessions.db` 의 배타적 SQLite 트랜잭션(`BEGIN IMMEDIATE`) 안에서 실행되고, 검증한 뒤에 YAML 을 원자적으로 다시 씁니다. 잡 레코드는 별도의 레지스트리 잠금을 씁니다.
- **Python 인터프리터.** 레지스트리 쓰기와 위임 헬퍼는 이 순서로 Python 을 고릅니다: `AGENT_PYTHON_BIN`, 활성화된 가상환경, 스킬 위쪽에서 가장 가까운 `.venv`, 마지막으로 `PATH` 의 `python3`. shim 과 `create` 의 일부 같은 셸 경로는 `PATH` 의 `python3` 를 직접 부릅니다.
- **네트워크 파일시스템.** `.mam/` 이 NFS/CIFS/SSHFS 에 있으면 `lib.sh` 가 경고하고, SQLite 저널 모드를 `WAL` 에서 `DELETE` 로 바꿉니다.
- **체크아웃당 루프 하나.** 루프 잠금이 같은 제어 평면에서 두 번째 `run_loop.sh` 를 거부합니다.
---
## 📨 위임 계약
전체 명세는 [MESSAGING.md §2.5](MESSAGING.md)에 있습니다.
- **전달**: 프롬프트를 넘긴 뒤 **T** 초(기본 180) 안에 에이전트가 `started` 를 발행해야 합니다. 그러지 않으면 잡은 실패로 닫히고(`agent_blocked`, 종료 코드 1), 프롬프트를 두 번 넣지 않습니다.
- **생존**: `started --heartbeat N` 이 자동 heartbeat 를 시작합니다(기본 30 초마다). **K×N** 초(기본 120 초) 동안 아무 이벤트가 없으면 무응답으로 봅니다. herdr 상태와 pane 텍스트는 진단용으로만 로그에 남깁니다.
- **예산**: 잡마다 벽시계 예산이 있습니다(`--timeout` / `MAM_JOB_TIMEOUT_S`, 기본 3600 초). 예산을 다 쓴 잡은 `wall_clock_timeout` 으로 닫힙니다.
- **바쁜 세션**: 래퍼는 그 세션의 앞선 열린 잡이 끝나기를 `MAM_DISPATCH_WAIT_S` 만큼 기다립니다. 끝나지 않으면 `cancelled`(`session_busy`, 종료 코드 5)로 끝나고, 프롬프트는 전달되지 않습니다.
- **원격 에이전트**: `remote:<name>` 은 브리프를 서명된 retained 브로커 메시지로 다른 호스트의 `remote.py run` 러너에 보냅니다. 사설 브로커에서만 쓰십시오.
| 종료 코드 | 의미 |
|:---:|---|
| `0` | `completed` |
| `1` | 실패, 또는 T 안에 `started` 없음 |
| `2` | K×N 동안 이벤트 없음(`deadline_timeout`) 또는 예산 소진(`wall_clock_timeout`) |
| `3` | 브로커를 쓸 수 없고, 디스크에도 종결 상태가 없음 |
| `5` | 이번 시도는 프롬프트를 전달하지 않음: 세션이 바쁘거나 대상을 찾지 못함(잡은 `cancelled`), 또는 기다리는 동안 다른 시도가 레코드를 가져감(레코드는 새 소유자에게 그대로 둠) |
---
## 🔁 리뷰 루프
```mermaid
sequenceDiagram
autonumber
actor User
participant L as run_loop.sh
participant P as Planner (optional)
participant C as Creator
participant R as Reviewers
User->>L: --task, --creator, --reviewer / --all-reviewer
opt --plan
L->>P: write the plan (Creator challenges it for --plan-talk turns)
end
L->>C: implement and commit (skipped with --review-only)
loop up to --max-loop
L->>R: review the snapshot diff (base..head)
alt every reviewer PASS and the source unchanged
R-->>L: [VERDICT: PASS]
else NOT PASS
R-->>L: [VERDICT: NOT PASS] + findings
L->>C: fix (or [REBUT:] up to --max-rebut)
end
end
L->>User: reports promoted to .agents/reports/<session>/
```
- **엄격한 판정**: 판정 토큰은 리포트 마지막 줄에 단독으로 있어야 합니다. 판정이 없으면 `NOT PASS` 로 봅니다.
- **리뷰 identity** (`--worktree` / `--base`): 라운드마다 커밋하지 않은 파일까지 담은 스냅샷을 리뷰합니다. 라운드 중 소스가 바뀌면 그 승인은 버립니다. 보관되는 리포트 옆에는 base·head·tree·리뷰어를 적은 `report-<job>.identity.json` 이 붙습니다.
- **하위 잡**: creator 가 작업 일부를 위임하면(`--parent-job`), 루프는 리뷰 전에 그 잡들을 기다리고 상태를 리뷰어에게 보여 줍니다.
---
## 🔐 보안
- **서명된 이벤트**: 토큰을 직접 넘기거나 브로커가 TLS 또는 사용자 이름을 쓰면, 잡은 고유 토큰(`auth_token`)을 받습니다. 그때 모든 이벤트와 retained 상태 메시지에 그 토큰으로 만든 HMAC-SHA256 서명이 붙고, 구독자는 검증에 실패한 메시지를 버립니다. 이벤트와 상태 메시지에는 토큰이 아니라 서명만 실립니다. 토큰을 넘기지 않았고 브로커에 TLS 도 사용자 이름도 없으면(PoC 구성), 잡에 토큰이 없어 서명을 검사하지 않습니다.
- **원격 디스패치에는 토큰이 실린다**: `remote:<name>` 디스패치 메시지에는 잡의 토큰이 들어갑니다(`MAM_DISPATCH_SECRET` 으로 서명될 뿐 암호화되지는 않음). 접근이 통제된 사설 브로커에서만 쓰십시오.
- **재전송과 순서**: 이벤트마다 증가하는 `seq` 가 붙습니다. 구독자는 지금까지 본 것보다 큰 번호만 받고, 빈 번호는 `seq_gap` 으로 기록합니다. 타임스탬프는 참고용입니다.
- **자격 증명**: 브로커 자격 증명은 `.mam.env` 에만 있고, 잡 레코드에는 없습니다.
---
## 🧪 테스트
```bash
.venv/bin/python -m pytest -q -p no:cacheprovider tests
```
- 테스트는 약 1,900 개이고, 전체 실행에 한 시간 넘게 걸립니다.
- 프로세스 안에서 자체 MQTT 브로커를 띄우므로 `.mam.env` 가 필요 없습니다. 테스트용으로 `.mam.env` 를 export 하지 마십시오.
- 모든 테스트는 임시 잡 레지스트리와 감사 로그 디렉터리를 씁니다. 약 3분의 1 은 임시 HOME 을 설정하는 샌드박스 픽스처도 쓰고, 그보다 적은 수는 mock herdr 도 씁니다.
- 실제 herdr 를 다루는 테스트는 일회용 herdr 서버를 직접 띄웁니다.
- 실행이 끝나면 세션 단위 가드가 실제 제어 평면과 사용자의 에이전트 홈(`~/.claude/projects`, `~/.codex`, `~/.gemini` …)을 확인하고, 테스트가 남긴 항목이 있으면 실행을 실패시킵니다.
- 일부 테스트는 플랫폼에 따라 다릅니다. Python 3.11+(`tomllib`), Linux(`/proc`), `nats-docker` 서브모듈이 필요한 테스트는 그 밖의 환경에서 건너뜁니다.
---
## 📁 저장소 구조
```text
.
├── .agents/
│ ├── MULTI_AGENT_RULES.md / .ko.md # 역할, 워크플로우, 협업 규칙
│ ├── reports/ # 보관된 리뷰 리포트 (+ identity 사이드카)
│ └── skills/
│ ├── lib.sh # 공용 셸 라이브러리 (herdr shim, 레지스트리 쓰기)
│ ├── lib_py/ # Python 코어
│ │ ├── agents/ # 에이전트별 CLI 어댑터 (spawn/resume/discover)
│ │ ├── delegation/ # 잡 레지스트리, 래퍼, MQTT 발행/구독, 원격 러너
│ │ ├── loop/ # 리뷰 루프 런타임
│ │ └── *.py # pane_lock, stall_watch, worktree, herdr_config, ...
│ └── multi-agent-mux-*/ # 스킬마다 디렉터리 하나 (SKILL.md + scripts/)
├── .mam/ # 런타임 상태 (git 제외): 세션, 잡, 감사 로그, shim
├── deploy/ # install.sh, install_mam.sh, update.sh, remove.sh, generate-env.sh
├── docs/ # contracts/, design/, 에이전트 통합·브리지 가이드
├── nats-docker/ # 사설 NATS 브로커 배포 (서브모듈)
├── tests/ # pytest 스위트
├── AGENTS.md # 에이전트 행동 지침
├── BOOTSTRAP.md / BOOTSTRAP.ko.md # 단계별 설치·검증 가이드
├── IMPROVEMENTS.md # 백로그
├── MESSAGING.md # MQTT 와이어 프로토콜과 위임 계약
├── README.md # 영문 README
└── VERSIONS.md # 릴리스 노트 (락스텝 버전 원장)
```
---
## 🚦 빠른 시작
전체 과정은 [BOOTSTRAP.ko.md](BOOTSTRAP.ko.md)를 보십시오. 요약하면:
```bash
# 1. 워크스페이스에 설치한 뒤 .mam.env 에 브로커를 설정
bash deploy/install.sh
# 2. 전용 herdr 서버에서 creator 와 reviewer 시작
bash .agents/skills/multi-agent-mux-create/scripts/create_session.sh \
--workspace "$PWD" --agent claude --role developer --session creator-claude-01 \
--herdr-session my-project --onboard
bash .agents/skills/multi-agent-mux-create/scripts/create_session.sh \
--workspace "$PWD" --agent codex --role reviewer --session reviewer-codex-01 \
--herdr-session my-project --onboard
# 3. 세션 확인
bash .agents/skills/multi-agent-mux-status/scripts/status.sh
# 4. 루프로 작업 실행
bash .agents/skills/multi-agent-mux-loop/scripts/run_loop.sh \
--creator creator-claude-01 --all-reviewer --task "Fix the typo in docs/guide.md"
```
---
## 📝 프로젝트에 합류하는 에이전트에게
1. 역할과 규칙은 [AGENTS.md](AGENTS.md)와 [MULTI_AGENT_RULES.ko.md](.agents/MULTI_AGENT_RULES.ko.md)를 읽으십시오.
2. 긴 출력이 pane 스크롤백 밖으로 사라지지 않도록 `MULTI_AGENT_RULES.ko.md` §4 의 스냅샷 규칙을 따르십시오.
3. 브리프가 지시하는 대로 잡 이벤트를 발행하십시오(`started --heartbeat`, 그다음 `completed` 또는 `error`). 리뷰받은 diff 없이 핵심 로직을 바꾸지 마십시오.