11 Commits
24 changed files with 482 additions and 78 deletions
+7 -7
View File
@@ -1,6 +1,6 @@
# MULTI_AGENT_RULES.md # MULTI_AGENT_RULES.md
본 문서는 새로운 프로젝트에 **MQTT 메시징 백플레인 및 Tmux 기반 멀티 에이전트 오케스트레이션 워크플로우**를 도입하고, 협업하는 에이전트들이 일관된 규칙과 아키텍처에 따라 안전하고 견고하게 작업을 수행할 수 있도록 정의한 공통 지침 및 규약입니다. 본 문서는 새로운 프로젝트에 **MQTT 메시징 백플레인 및 Herdr 기반 멀티 에이전트 오케스트레이션 워크플로우**를 도입하고, 협업하는 에이전트들이 일관된 규칙과 아키텍처에 따라 안전하고 견고하게 작업을 수행할 수 있도록 정의한 공통 지침 및 규약입니다.
새로운 프로젝트에서 작업하는 모든 에이전트는 작업을 시작하기 전 이 문서를 반드시 정독하고 규약을 준수해야 합니다. 새로운 프로젝트에서 작업하는 모든 에이전트는 작업을 시작하기 전 이 문서를 반드시 정독하고 규약을 준수해야 합니다.
@@ -54,7 +54,7 @@
### 🗃️ 레지스트리 및 상태 관리 ### 🗃️ 레지스트리 및 상태 관리
- 본 아키텍처는 목적에 따라 두 가지 레지스트리를 분리하여 운영합니다: - 본 아키텍처는 목적에 따라 두 가지 레지스트리를 분리하여 운영합니다:
- **잡 레지스트리 (Job Registry)**: 각 비동기 잡의 메타데이터와 생명주기는 개별 JSON 파일(`.mam/jobs/<id>.json`)로 기록되며, 다중 세션 간의 동시 청구(claiming) 경합은 파일 단위의 `fcntl` advisory lock(`registry_lock` via `registry.py`)을 통해 방어합니다. - **잡 레지스트리 (Job Registry)**: 각 비동기 잡의 메타데이터와 생명주기는 개별 JSON 파일(`.mam/jobs/<id>.json`)로 기록되며, 다중 세션 간의 동시 청구(claiming) 경합은 파일 단위의 `fcntl` advisory lock(`registry_lock` via `registry.py`)을 통해 방어합니다.
- **세션 레지스트리 (Session Registry)**: TMUX 모니터링 상태 및 에이전트 구동 정보는 SQLite WAL 데이터베이스(`.mam/agent-sessions.db`)를 통해 단일 호스트 내에서 안정적인 동시 트랜잭션으로 일관되게 제어합니다. 단, SQLite WAL 모드는 NFS(네트워크 파일 시스템) 환경에서는 완전한 파일 락이 보장되지 않으므로 로컬 파일 시스템 사용을 권장합니다. - **세션 레지스트리 (Session Registry)**: Herdr 모니터링 상태 및 에이전트 구동 정보는 SQLite WAL 데이터베이스(`.mam/agent-sessions.db`)를 통해 단일 호스트 내에서 안정적인 동시 트랜잭션으로 일관되게 제어합니다. 단, SQLite WAL 모드는 NFS(네트워크 파일 시스템) 환경에서는 완전한 파일 락이 보장되지 않으므로 로컬 파일 시스템 사용을 권장합니다.
### 🛡️ 보안 프로토콜 (HMAC-SHA256) ### 🛡️ 보안 프로토콜 (HMAC-SHA256)
- **무인증 PoC 모드**: 잡 레지스트리 생성 시 `auth_token``null`로 지정된 경우(PoC 기본 모드), 별도의 서명 검증을 생략하고 모든 이벤트를 수용합니다 (`verify_hmac`이 항상 `True`를 반환). - **무인증 PoC 모드**: 잡 레지스트리 생성 시 `auth_token``null`로 지정된 경우(PoC 기본 모드), 별도의 서명 검증을 생략하고 모든 이벤트를 수용합니다 (`verify_hmac`이 항상 `True`를 반환).
@@ -113,22 +113,22 @@ sequenceDiagram
장기 실행 에이전트 분석 중 발생하는 유실 및 인프라적 장애를 예방하기 위한 중요 지침입니다. 장기 실행 에이전트 분석 중 발생하는 유실 및 인프라적 장애를 예방하기 위한 중요 지침입니다.
### 📸 TUI 뷰포트 절단 방지 (Pane Snapshotting 3대 규칙) ### 📸 TUI 뷰포트 절단 방지 (Pane Snapshotting 3대 규칙)
TMUX 환경에서 실행되는 에이전트가 화면 스크롤 한계로 인해 이전 출력이나 장문의 디버깅 로그를 잃지 않도록 아래의 **스냅샷 패턴을 의무적으로 수행**합니다. Herdr 환경에서 실행되는 에이전트가 화면 스크롤 한계로 인해 이전 출력이나 장문의 디버깅 로그를 잃지 않도록 아래의 **스냅샷 패턴을 의무적으로 수행**합니다.
1. **Pre-brief Capture**: 작업 지침(Brief)을 전송한 직후, 즉시 해당 세션의 pane을 캡처(`capture-pane -S -200`)해두어 입력 기록의 시작점을 백업합니다. 1. **Pre-brief Capture**: 작업 지침(Brief)을 전송한 직후, 즉시 해당 세션의 pane을 캡처(`capture-pane -S -200`)해두어 입력 기록의 시작점을 백업합니다.
2. **Loop Snapshot**: 장기 실행(5분 이상) 중인 에이전트 세션의 경우, 주기적으로(예: 30초마다) 뷰포트를 스캔하여 증분 데이터를 `/tmp/pane-snap.txt`에 계속 누적(append) 기록합니다. 2. **Loop Snapshot**: 장기 실행(5분 이상) 중인 에이전트 세션의 경우, 주기적으로(예: 30초마다) 뷰포트를 스캔하여 증분 데이터를 `/tmp/pane-snap.txt`에 계속 누적(append) 기록합니다.
3. **Post-job Capture**: 잡 완료/에러 반환 즉시 전체 pane 상태를 마지막으로 캡처하여 전체 작업 궤적을 보존합니다. 3. **Post-job Capture**: 잡 완료/에러 반환 즉시 전체 pane 상태를 마지막으로 캡처하여 전체 작업 궤적을 보존합니다.
### 📄 마크다운 기반 협업 및 결과 전달 (Markdown-Based Workflow & Communication) ### 📄 마크다운 기반 협업 및 결과 전달 (Markdown-Based Workflow & Communication)
- **핵심 원칙**: TMUX `send-keys`나 입력 버퍼를 통해 긴 지시사항을 직렬로 입력하는 과정에서 문자 누락이나 레이아웃 유실이 발생하는 것을 방지하기 위해, 에이전트 간의 모든 주요 협업 소통은 파일 기반 마크다운 문서 생성을 원칙으로 합니다. - **핵심 원칙**: herdr `send-keys`나 입력 버퍼를 통해 긴 지시사항을 직렬로 입력하는 과정에서 문자 누락이나 레이아웃 유실이 발생하는 것을 방지하기 위해, 에이전트 간의 모든 주요 협업 소통은 파일 기반 마크다운 문서 생성을 원칙으로 합니다.
- **세부 규칙 및 규약**: - **세부 규칙 및 규약**:
- **예외 사항**: 1~2줄 내외의 매우 단순한 요청, 상태 확인, 수락 진행 등의 단발성 프롬프트는 tmux 입력을 통해 직접 보낼 수 있습니다. - **예외 사항**: 1~2줄 내외의 매우 단순한 요청, 상태 확인, 수락 진행 등의 단발성 프롬프트는 herdr 입력을 통해 직접 보낼 수 있습니다.
- **작업 위임**: - **작업 위임**:
- *수동 경로*: 상세 사양과 계획 수립 등의 복잡한 작업 지시는 먼저 로컬 마크다운 파일(예: `.mam/reports/brief-<job_id>.md` 또는 지정된 워크스페이스 경로)로 작성한 후, 에이전트에게 `"Read <파일경로> and execute."` 라는 실행 명령만 전달하십시오. - *수동 경로*: 상세 사양과 계획 수립 등의 복잡한 작업 지시는 먼저 로컬 마크다운 파일(예: `.mam/reports/brief-<job_id>.md` 또는 지정된 워크스페이스 경로)로 작성한 후, 에이전트에게 `"Read <파일경로> and execute."` 라는 실행 명령만 전달하십시오.
- *자동 경로*: 자동화 잡 런너(`multi-agent-mux-delegate-job submit`)는 잡 등록 시 `.mam/jobs/<job_id>/brief.md` 디렉터리에 지시서를 자동 집필하고 단일 라인 포인터 프롬프트만 에이전트 세션에 인가합니다. - *자동 경로*: 자동화 잡 런너(`multi-agent-mux-delegate-job submit`)는 잡 등록 시 `.mam/jobs/<job_id>/brief.md` 디렉터리에 지시서를 자동 집필하고 단일 라인 포인터 프롬프트만 에이전트 세션에 인가합니다.
- **결과 안내 및 피드백**: - **결과 안내 및 피드백**:
- *수동/영구 리뷰*: 상세 리뷰 결과, 설계 제안서 등은 `.mam/reports/<tmux_session_name>/report-<job_id>.md` 경로에 저장합니다. - *수동/영구 리뷰*: 상세 리뷰 결과, 설계 제안서 등은 `.mam/reports/<herdr_session_name>/report-<job_id>.md` 경로에 저장합니다.
- *자동화 잡 보고서*: 자동 위임된 비동기 작업의 완료 결과는 잡 디렉터리 하위인 `.mam/jobs/<job_id>/<agent_name>-reports/report-final.md` (루프/Discuss 위임 시에는 `<clean_session_name>-reports/`) 경로에 기록해야 합니다. - *자동화 잡 보고서*: 자동 위임된 비동기 작업의 완료 결과는 잡 디렉터리 하위인 `.mam/jobs/<job_id>/<agent_name>-reports/report-final.md` (루프/Discuss 위임 시에는 `<clean_session_name>-reports/`) 경로에 기록해야 합니다.
- *버전 관리 이관*: 버전 관리가 필요한 주요 산출물(최종 설계 계획, 최종 리뷰 보고서, 보안 감사 리포트 등)은 gitignore 대상인 `.mam/` 하위가 아닌, 버전 관리 대상 경로(구체적으로 `.agents/reports/<tmux_session_name>/` 또는 `docs/reports/` 등)로 명시적으로 복사하여 이관 보존해야 합니다. - *버전 관리 이관*: 버전 관리가 필요한 주요 산출물(최종 설계 계획, 최종 리뷰 보고서, 보안 감사 리포트 등)은 gitignore 대상인 `.mam/` 하위가 아닌, 버전 관리 대상 경로(구체적으로 `.agents/reports/<herdr_session_name>/` 또는 `docs/reports/` 등)로 명시적으로 복사하여 이관 보존해야 합니다.
- **디스크 정리 및 보존 정책 계약 (Cleanup & Retention)**: `.mam/jobs/<job_id>/``.mam/reports/` 폴더 아래의 파일들은 휘발성 감사 이력(audit-trail) 산출물입니다. 버전 관리가 필요한 문서들은 `.agents/reports/` 하위로 수동 복사하여 커밋해야 하며, `stop_session.sh` 세션 종료 스크립트는 이들 보고서 디렉터리를 자동으로 삭제하지 않으므로 수동 또는 주기적 클린업이 권장됩니다. - **디스크 정리 및 보존 정책 계약 (Cleanup & Retention)**: `.mam/jobs/<job_id>/``.mam/reports/` 폴더 아래의 파일들은 휘발성 감사 이력(audit-trail) 산출물입니다. 버전 관리가 필요한 문서들은 `.agents/reports/` 하위로 수동 복사하여 커밋해야 하며, `stop_session.sh` 세션 종료 스크립트는 이들 보고서 디렉터리를 자동으로 삭제하지 않으므로 수동 또는 주기적 클린업이 권장됩니다.
### ⏱️ 타임아웃 구성 및 정렬 규칙 ### ⏱️ 타임아웃 구성 및 정렬 규칙
+6 -6
View File
@@ -1,6 +1,6 @@
# MULTI_AGENT_RULES.md # MULTI_AGENT_RULES.md
This document serves as the common guidelines and protocol for introducing the **MQTT messaging backplane and Tmux-based multi-agent orchestration workflow** to a new project. It defines the rules and architecture to ensure collaborating agents perform tasks safely, robustly, and consistently. This document serves as the common guidelines and protocol for introducing the **MQTT messaging backplane and Herdr-based multi-agent orchestration workflow** to a new project. It defines the rules and architecture to ensure collaborating agents perform tasks safely, robustly, and consistently.
All agents working on a new project must read this document thoroughly and comply with the defined protocols before starting any tasks. All agents working on a new project must read this document thoroughly and comply with the defined protocols before starting any tasks.
@@ -54,7 +54,7 @@ Asynchronous communication and state management between agents are controlled vi
### 🗃️ Registry & State Management ### 🗃️ Registry & State Management
- This architecture maintains two distinct registries based on their purpose: - This architecture maintains two distinct registries based on their purpose:
- **Job Registry**: The metadata and lifecycle of each asynchronous job are recorded in individual JSON files (`.mam/jobs/<id>.json`). Concurrency conflicts (claiming races) across multiple sessions are prevented via file-based `fcntl` advisory locks (`registry_lock` via `registry.py`). - **Job Registry**: The metadata and lifecycle of each asynchronous job are recorded in individual JSON files (`.mam/jobs/<id>.json`). Concurrency conflicts (claiming races) across multiple sessions are prevented via file-based `fcntl` advisory locks (`registry_lock` via `registry.py`).
- **Session Registry**: TMUX monitoring states and running agent metadata are consistently controlled using a SQLite WAL database (`.mam/agent-sessions.db`) to support reliable concurrent transactions on a single host. However, since SQLite WAL mode does not guarantee complete file locking in Network File System (NFS) environments, we recommend using a local file system. - **Session Registry**: Herdr monitoring states and running agent metadata are consistently controlled using a SQLite WAL database (`.mam/agent-sessions.db`) to support reliable concurrent transactions on a single host. However, since SQLite WAL mode does not guarantee complete file locking in Network File System (NFS) environments, we recommend using a local file system.
### 🛡️ Security Protocol (HMAC-SHA256) ### 🛡️ Security Protocol (HMAC-SHA256)
- **Unauthenticated PoC Mode**: If the `auth_token` in the job registry is set to `null` (the default PoC mode), signature verification is skipped and all events are accepted (`verify_hmac` always returns `True`). - **Unauthenticated PoC Mode**: If the `auth_token` in the job registry is set to `null` (the default PoC mode), signature verification is skipped and all events are accepted (`verify_hmac` always returns `True`).
@@ -113,7 +113,7 @@ sequenceDiagram
These are critical instructions for preventing data loss and infrastructure-level failures during long-running agent analyses. These are critical instructions for preventing data loss and infrastructure-level failures during long-running agent analyses.
### 📸 Preventing TUI Viewport Truncation (The 3 Pane Snapshotting Rules) ### 📸 Preventing TUI Viewport Truncation (The 3 Pane Snapshotting Rules)
To ensure that agents running in TMUX environments do not lose debug logs or previous outputs due to screen scrollback limits, the following **snapshotting pattern must be enforced**: To ensure that agents running in Herdr environments do not lose debug logs or previous outputs due to screen scrollback limits, the following **snapshotting pattern must be enforced**:
1. **Pre-brief Capture**: Capture the pane (`capture-pane -S -200`) immediately after sending the task instruction (Brief) to back up the starting point of the input history. 1. **Pre-brief Capture**: Capture the pane (`capture-pane -S -200`) immediately after sending the task instruction (Brief) to back up the starting point of the input history.
2. **Loop Snapshot**: For long-running agent sessions (5 minutes or more), periodically (e.g., every 30 seconds) scan the viewport and append the incremental data to `/tmp/pane-snap.txt`. 2. **Loop Snapshot**: For long-running agent sessions (5 minutes or more), periodically (e.g., every 30 seconds) scan the viewport and append the incremental data to `/tmp/pane-snap.txt`.
3. **Post-job Capture**: Capture the complete pane state one final time immediately after a job completes or returns an error to preserve the entire execution trajectory. 3. **Post-job Capture**: Capture the complete pane state one final time immediately after a job completes or returns an error to preserve the entire execution trajectory.
@@ -121,14 +121,14 @@ To ensure that agents running in TMUX environments do not lose debug logs or pre
### 📄 Markdown-Based Workflow & Communication (마크다운 기반 협업 및 결과 전달) ### 📄 Markdown-Based Workflow & Communication (마크다운 기반 협업 및 결과 전달)
- **Core Principle**: To prevent TUI character loss, truncation, and layout breakage during sequential input typing, all collaborative workflows must favor file-based markdown communication. - **Core Principle**: To prevent TUI character loss, truncation, and layout breakage during sequential input typing, all collaborative workflows must favor file-based markdown communication.
- **Rules & Protocols**: - **Rules & Protocols**:
- **Exception**: Extremely simple prompts (e.g., "Re-evaluate", "Check status", "Proceed") of 1 or 2 lines may be sent directly via tmux input buffers. - **Exception**: Extremely simple prompts (e.g., "Re-evaluate", "Check status", "Proceed") of 1 or 2 lines may be sent directly via herdr input buffers.
- **Task Delegation**: - **Task Delegation**:
- *Manual path*: Detailed task briefs may be written to a local Markdown file (e.g., `.mam/reports/brief-<job_id>.md` or a workspace path) first. The sender then issues a simple trigger command: `"Read <file_path> and execute."` - *Manual path*: Detailed task briefs may be written to a local Markdown file (e.g., `.mam/reports/brief-<job_id>.md` or a workspace path) first. The sender then issues a simple trigger command: `"Read <file_path> and execute."`
- *Automated path*: The automated job runner (`multi-agent-mux-delegate-job submit`) automatically provisions the brief at `.mam/jobs/<job_id>/brief.md` and sends a short pointer instruction to the agent. - *Automated path*: The automated job runner (`multi-agent-mux-delegate-job submit`) automatically provisions the brief at `.mam/jobs/<job_id>/brief.md` and sends a short pointer instruction to the agent.
- **Result Reporting & Feedback**: - **Result Reporting & Feedback**:
- *Manual/Durable reviews*: Detailed reviews, design proposals, or audit reports must be saved under `.mam/reports/<tmux_session_name>/report-<job_id>.md`. - *Manual/Durable reviews*: Detailed reviews, design proposals, or audit reports must be saved under `.mam/reports/<herdr_session_name>/report-<job_id>.md`.
- *Automated job reports*: Automated execution results are saved directly to `.mam/jobs/<job_id>/<agent_name>-reports/report-final.md` (or `<clean_session_name>-reports/` for loops) as transient files. - *Automated job reports*: Automated execution results are saved directly to `.mam/jobs/<job_id>/<agent_name>-reports/report-final.md` (or `<clean_session_name>-reports/` for loops) as transient files.
- *Versioned promotions*: Any final design plans, review verdicts, or security audit reports that require version control must be explicitly copied to tracked directory paths (specifically under `.agents/reports/<tmux_session_name>/` or `docs/reports/`). - *Versioned promotions*: Any final design plans, review verdicts, or security audit reports that require version control must be explicitly copied to tracked directory paths (specifically under `.agents/reports/<herdr_session_name>/` or `docs/reports/`).
- **Cleanup & Retention Contract**: Files under `.mam/jobs/<job_id>/` and `.mam/reports/` are transient audit-trail artifacts. While durable outcomes are committed to version control under `.agents/reports/`, ephemeral directory trees can be cleaned up manually as needed; `stop_session.sh` does not automatically purge these report trees during session exit. - **Cleanup & Retention Contract**: Files under `.mam/jobs/<job_id>/` and `.mam/reports/` are transient audit-trail artifacts. While durable outcomes are committed to version control under `.agents/reports/`, ephemeral directory trees can be cleaned up manually as needed; `stop_session.sh` does not automatically purge these report trees during session exit.
### ⏱️ Timeout Configuration & Alignment Rules ### ⏱️ Timeout Configuration & Alignment Rules
@@ -0,0 +1,33 @@
# 리뷰 리포트 — Job 4094502a (MULTI_AGENT_RULES.md/ko.md tmux→herdr 개정)
- **리뷰 대상**: 작업 트리 미커밋 diff — `.agents/MULTI_AGENT_RULES.md`(6개소), `.agents/MULTI_AGENT_RULES.ko.md`(7개소), cline 작성
- **리뷰어**: claude (planner-reviewer)
## 1. 변경 무결성 — 토큰 단위 검증
`git diff --word-diff` 전수 집계 결과, 변경은 **정확히 23개 토큰 치환**(TMUX/Tmux/tmux → Herdr/herdr, `Tmux-based``Herdr-based`, `<tmux_session_name>``<herdr_session_name>` 경로 플레이스홀더 4건 포함)뿐이며 **문장 추가·삭제·구조 변경 0건**. 번역 유실이나 mermaid/sequenceDiagram 블록 훼손 없음. 양 언어판의 치환 지점이 상호 대응함(ko의 send-keys 문구 1건은 원래 ko에만 존재하는 기존 번역 차이로, 이번 diff와 무관).
## 2. 잔존 레거시 스윕
- 두 파일 모두 대소문자 무시 `tmux` 검색 **0건** — 누락된 레거시 없음.
- 저장소 전체에서 `<tmux_session_name>` 플레이스홀더 잔존은 `.agents/reports/` 하위 **아카이브된 과거 리뷰 리포트 3건뿐** — 역사적 감사 기록이므로 개정 대상이 아님(방치 아님).
## 3. herdr 사양 교차 검증 (실구현 대조)
| 문서 표기 | 실구현 근거 | 판정 |
|---|---|---|
| "herdr `send-keys`" / "herdr 입력" | shim(`.mam/shim/herdr:318`)에 `send-keys` 의사 명령 실재 | ✅ 부합 |
| `capture-pane -S -200` (유지된 기존 문구) | shim `capture-pane` 의사 명령 실재(:301) | ⚠️ 명령은 실재하나 아래 비차단 지적 1 참조 |
| `.agents/reports/<herdr_session_name>/` 관례 | 실제 디렉터리(`.agents/reports/canary-projects-…-cline` 등)가 세션명 기반으로 운영 중 | ✅ 부합 |
| "Herdr 모니터링 상태 … SQLite WAL" | `.mam/agent-sessions.db` + `herdr_sessions` 스키마 현행 일치 | ✅ 부합 |
## 4. 비차단(Non-blocking) 지적
1. **`capture-pane -S -200`의 시맨틱 공백(기존 문구, 이번 diff 무관)**: shim의 `capture-pane``-S -200` 플래그를 파싱하지 않고 조용히 무시하며 항상 `agent read --source visible --lines 100`으로 동작한다. 즉 문서가 약속하는 "스크롤백 200행 백업"이 실제로는 "가시 영역 100행"으로 축소 실행된다. 스냅샷 규칙의 취지(뷰포트 절단 방지)가 약화되므로, 후속 개선으로 (a) shim이 `-S -N``--lines N`으로 매핑하거나 (b) 문서에서 플래그 표기를 herdr 실사양으로 갱신할 것을 권장.
2. **의사 명령 전제 미표기**: 다른 SKILL.md들은 `send-keys`/`capture-pane`이 "lib.sh 소싱 후에만 동작하는 tmux-compat 의사 명령"임을 명시하나, 본 규칙 문서는 전제 없이 사용한다. 규칙서가 신규 에이전트의 첫 관문임을 고려하면 각주 1줄 추가 가치가 있음.
## 5. 결론
치환은 토큰 단위로 정밀하고(내용 유실 0), 잔존 레거시 0건이며, 도입된 어휘가 현행 스킬/shim/디렉터리 관례와 전부 부합한다. 비차단 2건은 이번 diff가 만들지 않은 기존 문구의 후속 개선 사항이다.
[VERDICT: PASS]
@@ -0,0 +1,77 @@
# 구현 계획서 — deploy/ 배포 설정 tmux→herdr 전환 (Job baa15c96)
- **Planner**: claude (planner-reviewer)
- **입력**: `.mam/deploy_brief.md` + `deploy/` 전수 분석 + 현행 스킬 구현(단일 진실 소스) 대조
- **핵심 원칙**: 문서·스크립트가 브리프의 *추정* 어휘가 아니라 **현행 스킬이 실제 소비하는 어휘**와 일치해야 한다. 실측 결과 브리프의 제안 중 2건은 실제 구현과 다르므로 아래와 같이 교정한다:
- ~~`HERDR_SESSION_NAME`~~ → **`HERDR_SERVER_NAME`** (스킬 전체가 이 이름만 소비, `TMUX_SERVER_NAME` 소비처는 0곳)
- ~~`--herdr-session`~~ → **`--herdr-server`** (`create_session.sh:63`이 수용하는 유일한 플래그, `--tmux-server`는 이미 제거되어 **미지원**)
## 0. 실측 현황 (전수 스윕: `grep -in "tmux" deploy/` + 스킬 대조)
| 파일 | 행 | 현재 내용 | 판정 |
|---|---|---|---|
| install_mam.sh | 224 | `--tmux-server multi-agent-mux` (Quick Start 예시) | 🔴 **기능 파손** — create_session.sh가 이 플래그를 거부(unknown arg, exit 2). 사용자가 복붙 시 즉시 실패 |
| install_mam.sh | 227 | `$ tmux -L multi-agent-mux attach -t <session_name>` | 🔴 죽은 명령 |
| install.sh | 31 | `check_cmd tmux` | 🟠 잘못된 의존성 진단(herdr 미검사) |
| install.sh | 249 | `.env``TMUX_SERVER_NAME=default` 기록 | 🟠 죽은 설정(소비처 0) |
| README.md | 9 | requirements "(`tmux`, `python3`)" | 🟡 문서 불일치 |
| INSTALL.md | 21, 37 | 진단 목록에 `tmux` | 🟡 문서 불일치(install_mam.sh:86 실제 DEPS는 이미 `herdr python3 rsync uuidgen`) |
| INSTALL.md | 69 | "호스트 재기동으로 tmux가 소멸한 경우" | 🟡 문서 불일치 |
| plugin.json | 3 | `"... Backplane on Tmux & MQTT."` | 🟡 메타데이터 불일치 |
| remove.sh / update.sh | — | tmux/kill 로직 **없음**(파일 삭제·venv·문서 갱신뿐) | ✅ 수정 불요(브리프 검토 항목 종결) |
| generate-env.sh / gitea-ci.yml | — | tmux 참조 없음 | ✅ 수정 불요 |
## 1. 파일별 수정 계획 (변경 대비표)
### 1-1. `deploy/install_mam.sh` (우선순위 1 — 기능 파손 수정)
| 행 | 변경 전 | 변경 후 |
|---|---|---|
| 224 | `--tmux-server multi-agent-mux` | `--herdr-server multi-agent-mux` |
| 227 | `$ tmux -L multi-agent-mux attach -t <session_name>` | `$ HERDR_SERVER_NAME=multi-agent-mux herdr agent attach <session_name>` |
근거: 224는 `create_session.sh:63`의 실제 플래그. 227은 create_session.sh:328이 YAML `attach_command`로 방출하는 **canonical 형식**(`HERDR_SERVER_NAME=<server> herdr agent attach <name>`)과 동일하게 맞춘다(개별 에이전트 pane attach). 전체 서버 화면이 필요하면 `herdr session attach multi-agent-mux`도 각주로 병기 가능.
### 1-2. `deploy/install.sh`
| 행 | 변경 전 | 변경 후 |
|---|---|---|
| 31 | `check_cmd tmux` | `check_cmd herdr` |
| 249 | `TMUX_SERVER_NAME=default` | `HERDR_SERVER_NAME=default` |
권장 추가(선택): `check_cmd herdr` 실패 시 install_mam.sh:9698과 동일한 설치 안내(`curl -fsSL https://herdr.dev/install.sh \| sh`)를 출력하도록 `check_cmd` 호출부 뒤에 힌트 블록 추가 — 두 인스톨러의 UX 일관성 확보.
마이그레이션 노트: 기존 설치본 `.env``TMUX_SERVER_NAME`은 소비처가 없어 잔존해도 무해하므로 자동 치환 로직은 불요(계획서 기록으로 갈음).
### 1-3. `deploy/README.md`
| 행 | 변경 전 | 변경 후 |
|---|---|---|
| 9 | ``checks system requirements (`tmux`, `python3`)`` | ``checks system requirements (`herdr`, `python3`)`` |
### 1-4. `deploy/INSTALL.md`
| 행 | 변경 전 | 변경 후 |
|---|---|---|
| 21 | ``시스템의 `tmux`, `python3`, `rsync`, `uuidgen` …을 진단`` | ``시스템의 `herdr`, `python3`, `rsync`, `uuidgen` …을 진단`` |
| 37 | ``**의존성 진단**: … `tmux`, `python3`, …`` | ``**의존성 진단**: … `herdr`, `python3`, …`` |
| 69 | `호스트 재기동으로 tmux가 소멸한 경우에도` | `호스트 재기동으로 herdr 서버가 소멸한 경우에도` |
### 1-5. `deploy/plugin.json`
| 행 | 변경 전 | 변경 후 |
|---|---|---|
| 3 | `"… Backplane on Tmux & MQTT."` | `"… Backplane on Herdr & MQTT."` |
### 1-6. `deploy/remove.sh`, `deploy/update.sh` — **수정 없음** (분석 결과 기록)
두 스크립트 모두 세션 킬링 로직 자체가 존재하지 않고 파일 자산 삭제/갱신만 수행하므로 tmux→herdr 마이그레이션 대상이 아니다. (선택적 후속 개선: remove.sh가 스킬 제거 전 실행 중인 herdr 에이전트 세션을 `multi-agent-mux-stop`으로 정리하도록 권고하는 안내 문구 추가 — 본 브리프 범위 밖이므로 별도 결정.)
## 2. 구현 순서
1. install_mam.sh (기능 파손 우선) → 2. install.sh → 3. 문서 3종(README/INSTALL/plugin.json) → 4. 검증 → 5. 커밋(예: `fix(deploy): migrate deployment scripts and docs from tmux to herdr`).
## 3. 검증 계획 (Reviewer/Creator 공용 DoD)
1. **잔존 스윕**: `grep -rin "tmux" deploy/` → **0건** (대소문자 무시 필수 — `Tmux` 표기가 plugin.json에 존재했음).
2. **정적**: 수정된 .sh에 `bash -n` + `shellcheck -S warning`, 변경 전 대비 신규 경고 0건. plugin.json은 `python3 -m json.tool`로 유효성 확인.
3. **기능(핵심)**: Quick Start 예시를 **그대로 복붙 실행** — 스크래치 워크스페이스에서 `create_session.sh --workspace <scratch> --agent claude --role developer --isolate --herdr-server <scratch서버명> --dry-run` 이 exit 0. (--dry-run이 spawn 전에 종료하므로 라이브 무접촉. `--isolate` 플래그는 create_session.sh:66에서 여전히 수용됨을 확인 완료.)
4. **attach 명령 실증**: 문서의 attach 형식이 실제 YAML `attach_command`(예: `.mam/agent-sessions.yaml:18`)와 동일 형식인지 대조.
5. **회귀**: `.env` 신규 생성 경로에서 `HERDR_SERVER_NAME=default` 기록 확인, `TMUX_SERVER_NAME` 소비처 0곳 재확인(`grep -rn TMUX_SERVER_NAME .agents/skills` = 0).
## 4. 완료 기준
- deploy/ 내 tmux 참조(대소문자 무관) 0건, Quick Start 예시 실행 가능, 정적 검사 클린, 문서 어휘가 현행 스킬 구현(`--herdr-server`/`HERDR_SERVER_NAME`/`herdr agent attach`)과 1:1 일치.
@@ -0,0 +1,144 @@
# ✅ Peer Review Report: delegate-job run_agent() herdr 버그 3건 수정 검토
**Reviewer**: Reviewer B (Cline, `canary-projects-multi-agent-mux-reviewer-cline`)
**Job ID**: 7f25e72e
**Review Target**: `.agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job``run_agent()` 함수 herdr 관련 버그 3건 수정 (커밋 `6df4b03`에 반영됨, working tree의 해당 파일은 clean)
**Review Scope**: (a) 수정이 실제 herdr CLI 문법과 맞는지, (b) source 순서 변경이 스크립트 다른 부분과 충돌하지 않는지, (c) HERDR_SERVER_NAME 자동 해석이 비격리(default) 세션에 회귀 없이 동작하는지
**Method**: 실제 herdr v0.7.4 도움말 직접 열람 + shim has-session/agent attach 실행 + resolve_herdr_workspace python3 시뮬레이션 + 형제 스킬(resume/stop) 일관성 비교 + 55개 테스트 실행
---
## 1. 발견된 버그 3건과 수정 내용 (커밋 6df4b03)
### 버그 1: lib.sh source 순서 — has-session이 실제 바이너리 호출 (항상 실패)
- **문제**: lib.sh를 `run_agent()` 내부, has-session 체크(라인 437)보다 늦게(라인 444) source → has-session 호출 시점엔 `herdr`이 shim이 아닌 실제 바이너리 → 존재하지 않는 `has-session` 서브커맨드 → 항상 실패
- **수정**: `source "$SCRIPT_DIR/../lib.sh"`를 라인 31(스크립트 상단, `run_agent()` 정의 라인 408보다 377줄 앞)로 이동 + 주석 "Source EARLY (before any herdr usage in run_agent)"
- **결과**: `herdr has-session -t`가 shim 경유로 `_real_herdr agent get "$sess"`로 번역되어 정상 동작
### 버그 2: HERDR_SERVER_NAME 자동 해석 누락 — 격리 세션 위임 실패
- **문제**: 호출자가 `HERDR_SERVER_NAME`를 수동 export하길 기대 → 격리된 세션 위임 시 default 세션에서 찾아 실패
- **수정**: `export HERDR_SERVER_NAME="$(resolve_herdr_workspace "$sess")"` 자동 해석 추가 (라인 443) + 주석 "Auto-resolve isolation the same way resume/stop/create do"
- **이전 코드**: `local _herdr="herdr"; if [ -n "${HERDR_SERVER_NAME:-}" ]; then _herdr="herdr -L $HERDR_SERVER_NAME"; fi` — 수동 env 의존 + `herdr -L` 직접 사용(shim 경유 아님)
- **신규 코드**: 자동 해석 + `herdr has-session -t` (shim이 `-L`를 내부 처리)
### 버그 3: 안내 메시지 'session attach' → 'agent attach'
- **문제**: 마지막 안내가 `herdr session attach $sess``session attach`는 서버 전체 단위 명령이라 개별 에이전트 이름으로 못 찾음
- **수정**: `HERDR_SERVER_NAME=$HERDR_SERVER_NAME herdr agent attach $sess` (라인 513) + 주석 "`session attach` operates on whole herdr *sessions*, not an individual agent"
- **추가**: `HERDR_SERVER_NAME` 인라인 포함 — source 안 된 fresh shell에서도 copy-paste 가능
---
## 2. 검증 관점 (a): 실제 herdr CLI 문법 일치 여부
### 2.1 ✅ has-session — shim 번역 정확
- 실제 herdr v0.7.4에 `has-session` 서브커맨드 **없음** (도움말에 없음)
- shim(lib.sh 147-163): `has-session -t <sess>``_real_herdr agent get "$sess" >/dev/null 2>&1` — agent 존재 여부로 세션 존재 판정
- **실행 검증**: 존재 세션 → exit 0, 비존재 세션 → exit 1 ✅
### 2.2 ✅ agent attach — 실제 서브커맨드 확인
- `herdr agent --help` 출력: `herdr agent attach <target> [--takeover]`**실제 존재**
- `herdr agent attach <target>`는 **개별 에이전트 target**을 받음 — `$sess`(MAM 에이전트 이름)와 일치 ✅
### 2.3 ✅ session attach vs agent attach 구분 정확
- `herdr session --help` 출력: `herdr session attach <name>`**서버 전체 session** name을 받음
- `herdr agent attach <target>`**개별 에이전트** target을 받음
- delegate-job의 `$sess`는 MAM 에이전트 이름(예: `canary-projects-multi-agent-mux-reviewer-cline`) → `agent attach`가 정답 ✅
- 수정 전 `session attach $sess`는 에이전트 이름을 session 이름으로 잘못 전달 → 실패 확정 ✅ (버그 재현 논리 타당)
### 2.4 ✅ HERDR_SERVER_NAME 인라인 메시지
---
## 3. 검증 관점 (b): source 순서 변경 충돌 여부
### 3.1 ✅ source 위치 — run_agent보다 377줄 앞
- `source "$SCRIPT_DIR/../lib.sh"` (라인 31) vs `run_agent()` 정의 (라인 408) — 377줄 선행 ✅
- has-session 호출(라인 445) 시점엔 shim 활성화 보장 ✅
### 3.2 ✅ 다른 함수/변수와 충돌 없음
- lib.sh source 시 정의되는 함수: `herdr`(shim), `resolve_herdr_workspace`, `send_keys_safe`, `load_state_json`, `derive_session_name`, `env_python`
- delegate-job이 이미 사용 중인 함수(`send_keys_safe`, `load_state_json`) — source 순서 변경 후에도 동일 동작 ✅
- `pick_python()` (라인 34)는 source 이후 정의 — lib.sh가 `pick_python`에 의존하지 않으므로 순서 충돌 없음 ✅
- 라인 26-30 주석이 "Source EARLY" 의도 명시 — 유지보수자에게 경고 ✅
### 3.3 ✅ 기존 `source` 라인(라인 444) 제거 확인
- 6df4b03 diff: `source "$SCRIPT_DIR/../lib.sh"`가 run_agent 내부(구 라인 444)에서 제거되고 상단(라인 31)로 이동 — 중복 source 아님 ✅
---
## 4. 검증 관점 (c): HERDR_SERVER_NAME 자동 해석 — 비격리(default) 회귀 여부
### 4.1 ✅ resolve_herdr_workspace 구현 (lib.sh 550-562)
- state JSON에서 session name 매칭 → `herdr_workspace`/`herdr_server` 반환
- 매칭 실패 시 `os.environ.get('HERDR_SERVER_NAME', 'default')` fallback
- **비격리(default) 경로**: session이 JSON에 없거나 `herdr_workspace` 미설정 → `default`
### 4.2 ✅ python3 시뮬레이션 검증
- **비격리**: `MAM_STATE_JSON='{}' SESSION_NAME='nonexistent'` (HERDR_SERVER_NAME unset) → `default`
- **격리**: `MAM_STATE_JSON='{"herdr_sessions":[{"name":"isolated-agent","herdr_workspace":"iso-server"}]}' SESSION_NAME='isolated-agent'``iso-server`
- **env fallback**: `HERDR_SERVER_NAME=custom_server``custom_server` (test_resume_resolve_herdr_workspace_env 검증) ✅
### 4.3 ✅ 형제 스킬 일관성
| 스크립트 | 패턴 |
|---------|------|
| resume_session.sh:40 | `HERDR_SERVER_NAME="$(resolve_herdr_workspace "$SESSION_NAME")"` |
| update_yaml_resumed.sh:36 | `HERDR_SERVER_NAME="$(resolve_herdr_workspace "$SESSION_NAME")"` |
| stop_session.sh:85 | `HERDR_SERVER_NAME="$(resolve_herdr_workspace "$SESSION_NAME")"` |
| **delegate-job:443 (신규)** | `export HERDR_SERVER_NAME="$(resolve_herdr_workspace "$sess")"` |
- delegate-job이 형제 스킬과 **동일 패턴** 채택 — 일관성 확보 ✅
- 유일한 차이: `export` 추가 — delegate-job은 subprocess(send_keys_safe 등)에 전달 필요 → export 정당 ✅
### 4.4 ✅ 비격리 회귀 없음
- default 세션의 에이전트: `resolve_herdr_workspace``default` 반환 → `HERDR_SERVER_NAME=default` → shim이 `_MAM_SESSION=""`(빈) → `_real_herdr``--session` 없이 호출 → default 서버 사용 ✅
- 55개 테스트(깨끗한 환경) 통과 — 비격리 경로 회귀 없음 ✅
---
## 5. 추가 검증: 정적 분석 & 테스트
### 5.1 ✅ bash -n
| 파일 | 결과 |
|------|------|
| `multi-agent-mux-delegate-job` | SYNTAX OK (exit 0) ✅ |
| `.agents/skills/lib.sh` | SYNTAX OK (exit 0) ✅ |
### 5.2 ✅ 테스트 (깨끗한 환경)
- `env -u HERDR_SERVER_NAME pytest tests/test_tier1_unit.py tests/test_tier2_component.py`: **55 passed**
- **주의**: 리뷰어 세션 환경(`HERDR_SERVER_NAME=multi-agent-mux`)에서 실행 시 `test_resume_resolve_herdr_workspace_default` 실패 — 이는 **테스트 하네스 env 격리 한계**(test가 `env=` 미전달하여 부모 환경 상속), 코드 결함 아님. `env -u HERDR_SERVER_NAME`로 실행 시 통과 ✅
---
## 6. 잔여 결함
### 6.1 ⚠️ test_resume_resolve_herdr_workspace_default env 격리 부족 (LOW, 본 수정 외)
- 테스트가 `run_lib_func` 호출 시 `env=` 미전달 → 부모 shell의 `HERDR_SERVER_NAME` 상속
- 격리 herdr 세션 내에서 pytest 실행 시 실패(환경 artifact)
- **영향**: 본 delegate-job 수정과 무관, 기존 테스트 하네스 한계. CI(깨끗한 env)에서는 통과
- **심각도**: LOW — 테스트 격로 보강 권장(`env={}` 명시 또는 `monkeypatch.delenv`)
### 6.2 ️ 주석 "tmux-compat shim" (INFO)
- 라인 27 주석 "turns plain `herdr` into the tmux-compat shim" — tmux 호환성 레퍼런스는 의도된 설명(실제 shim이 tmux 문법을 herdr로 번역)
- **심각도**: INFO — 유지
---
## 7. 종합 평가
### 버그 3건 수정 품질
1.**버그 1 (source 순서)**: lib.sh를 라인 31로 조기 이동 — has-session이 shim 경유 `agent get`으로 정상 동작. run_agent보다 377줄 선행, 다른 함수와 충돌 없음
2.**버그 2 (HERDR_SERVER_NAME 자동 해석)**: `resolve_herdr_workspace` 자동 호출 — 형제 스킬(resume/stop)과 동일 패턴, 비격리(default) 회귀 없음, 격리 세션 정상 해석
3.**버그 3 (session attach → agent attach)**: 실제 herdr v0.7.4 도움말로 `agent attach <target>` 존재 확인 — 에이전트 이름 전달 정확, `session attach`는 서버 단위로 부적절
### 검증 관점 충족
- **(a) herdr CLI 문법**: has-session(shim `agent get` 번역), agent attach(실제 서브커맨드), session attach(서버 단위 구분) — 모두 실제 도움말로 확인 ✅
- **(b) source 순서 충돌**: 라인 31 조기 source, run_agent(408) 선행, 중복 source 없음, 다른 함수 충돌 없음 ✅
- **(c) 비격리 회귀**: resolve_herdr_workspace가 default fallback, 55개 테스트(깨끗한 env) 통과, 형제 스킬 일관성 ✅
### 잔여 결함 (본 수정 외, LOW/INFO)
- LOW 6.1: test env 격리 부족 (본 수정 무관, CI 통과)
- INFO 6.2: "tmux-compat shim" 주석 (의도된 설명)
### 판정 근거
3건 버그 수정 모두 실제 herdr v0.7.4 CLI 문법과 정확히 일치(has-session/agent attach/session attach 도움말 직접 확인). source 순서 변경은 run_agent 선행 보장 + 다른 함수 충돌 없음. HERDR_SERVER_NAME 자동 해석은 형제 스킬과 동일 패턴으로 일관성 확보 + 비격리 회귀 없음. 정적 분석 통과, 55개 테스트(깨끗한 환경) 통과. 잔여 결함은 본 수정 외 LOW/INFO로 BLOCKING 아님. 설계 변경/재작업 불필요. PASS.
[VERDICT: PASS]
@@ -0,0 +1,46 @@
# ✅ deploy/ 배포 설정 tmux→herdr 전환 수정 완료 보고
**Job ID**: 94ce2fcc (동일 작업: 60af70e4)
**Role**: Implementation (Cline)
**Plan**: [report-baa15c96.md](.agents/reports/canary-projects-multi-agent-mux-planner-reviewer-claude/report-baa15c96.md)
**Brief**: [.mam/deploy_patch_brief.md](.mam/deploy_patch_brief.md)
## 적용된 변경 (5 파일, 7 에디트)
### 1. deploy/install_mam.sh (기능 파손 수정 — 우선순위 1)
- **L224**: `--tmux-server multi-agent-mux``--herdr-server multi-agent-mux`
- 근거: `create_session.sh:63`의 실제 플래그(`--herdr-server`) — 기존 `--tmux-server`는 create_session.sh가 거부(unknown arg, exit 2)하여 복붙 시 즉시 실패하는 기능 파손
- **L227**: `tmux -L multi-agent-mux attach -t <session_name>``HERDR_SERVER_NAME=multi-agent-mux herdr agent attach <session_name>`
- 근거: `create_session.sh:328`이 YAML `attach_command`로 방출하는 canonical 형식과 1:1 일치 (`HERDR_SERVER_NAME={server} herdr agent attach {name}`)
### 2. deploy/install.sh
- **L31**: `check_cmd tmux``check_cmd herdr` (올바른 의존성 진단)
- **L249**: `TMUX_SERVER_NAME=default``HERDR_SERVER_NAME=default` (소비처 0인 죽은 설정 → 실제 소비 변수)
### 3. deploy/README.md
- **L9**: `checks system requirements (\`tmux\`, \`python3\`)` → `\`herdr\`, \`python3\``
### 4. deploy/INSTALL.md
- **L21**: 진단 목록 `tmux``herdr`
- **L37**: 의존성 진단 `tmux``herdr`
- **L69**: `tmux가 소멸한 경우``herdr 서버가 소멸한 경우`
### 5. deploy/plugin.json
- **L3**: `Backplane on Tmux & MQTT.``Backplane on Herdr & MQTT.`
## 검증 결과 (DoD)
1.**잔존 스윕**: `grep -rin tmux deploy/`**0건** (대소문자 무시)
2.**정적**: `bash -n` install_mam.sh/install.sh 모두 exit 0; `shellcheck -S warning` exit 0 (신규 경고 0건); `python3 -m json.tool plugin.json` VALID
3.**기능**: `--herdr-server``create_session.sh:35/63`의 실제 플래그 확인; attach 형식이 `create_session.sh:328` canonical 형식과 1:1 일치
4.**회귀**: `env -u HERDR_SERVER_NAME pytest tests/test_tier1_unit.py tests/test_tier2_component.py`**55 passed**
## 계획서 교정사항 반영
- ~~`HERDR_SESSION_NAME`~~ → **`HERDR_SERVER_NAME`** (스킬 전체가 소비하는 유일한 변수명) ✅
- ~~`--herdr-session`~~ → **`--herdr-server`** (create_session.sh:63의 유일한 플래그) ✅
## 제외 항목 (계획서 §1-6)
- remove.sh / update.sh: tmux/kill 로직 없음 → 수정 불요 ✅
- generate-env.sh / gitea-ci.yml: tmux 참조 없음 → 수정 불요 ✅
[VERDICT: PASS]
@@ -0,0 +1,58 @@
# ✅ MULTI_AGENT_RULES tmux→herdr 전환 수정 완료 보고
**Job ID**: b92c34d7
**Role**: Implementation (Cline)
**Brief**: [.mam/rules_patch_brief.md](.mam/rules_patch_brief.md)
**Target Files**: `.agents/MULTI_AGENT_RULES.md` (EN) + `.agents/MULTI_AGENT_RULES.ko.md` (KO)
## 적용된 변경 (2 파일, 13 에디트 — 정확한 1:1 치환)
### 1. MULTI_AGENT_RULES.md (EN) — 6 에디트
| 행 | 변경 전 | 변경 후 |
|---|---|---|
| 3 | `Tmux-based multi-agent orchestration` | `Herdr-based multi-agent orchestration` |
| 57 | `TMUX monitoring states` | `Herdr monitoring states` |
| 116 | `running in TMUX environments` | `running in Herdr environments` |
| 124 | `via tmux input buffers` | `via herdr input buffers` |
| 129 | `<tmux_session_name>` | `<herdr_session_name>` |
| 131 | `<tmux_session_name>` | `<herdr_session_name>` |
### 2. MULTI_AGENT_RULES.ko.md (KO) — 7 에디트
| 행 | 변경 전 | 변경 후 |
|---|---|---|
| 3 | `Tmux 기반 멀티 에이전트` | `Herdr 기반 멀티 에이전트` |
| 57 | `TMUX 모니터링 상태` | `Herdr 모니터링 상태` |
| 116 | `TMUX 환경에서 실행되는` | `Herdr 환경에서 실행되는` |
| 122 | `TMUX `send-keys`나 입력 버퍼` | `herdr `send-keys`나 입력 버퍼` |
| 124 | `tmux 입력을 통해` | `herdr 입력을 통해` |
| 129 | `<tmux_session_name>` | `<herdr_session_name>` |
| 131 | `<tmux_session_name>` | `<herdr_session_name>` |
## 치환/보완 가이드 대응표 (브리프 §치환/보완 가이드)
| 브리프 지시 | 적용 | 비고 |
|---|---|---|
| `Tmux-based``Herdr-based` | ✅ EN L3, KO L3 | |
| `TMUX monitoring states``Herdr monitoring states` | ✅ EN L57, KO L57 | |
| `running in TMUX environments``running in Herdr environments` | ✅ EN L116, KO L116 | |
| `via tmux input buffers``via herdr input buffers` | ✅ EN L124 | KO L124 `tmux 입력``herdr 입력` (맥락 동일) |
| `tmux_session_name``herdr_session_name` | ✅ EN L129/L131, KO L129/L131 | 경로 placeholder 4곳 |
| KO: `herdr`/`herdr 서버`로 개정 | ✅ KO L3/L57/L116/L122/L124 | |
## 검증 결과 (DoD)
1.**잔존 스윕**: `grep -in 'tmux\|TMUX'` 두 파일 → **0건** (대소문자 무시)
2.**마크다운 포맷팅**: 헤딩 개수 EN 17 / KO 17 (변경 전후 동일 — 레이아웃 유지); diff 13 insertions / 13 deletions (정확한 1:1 치환, 구조 변경 없음)
3.**herdr 치환 확인**: EN 6곳, KO 7곳 herdr/Herdr 표기 존재
4.**회귀**: `env -u HERDR_SERVER_NAME pytest tests/test_tier1_unit.py tests/test_tier2_component.py`**55 passed**
## 관찰 사항 (INFO, 본 브리프 범위 외)
### INFO: `capture-pane -S -200` 명령어 예시 잔존
- 스냅샷 3대 규칙 섹션(EN L117-118, KO L117-118)에 `capture-pane -S -200` 명령어 예시가 잔존
- 이는 tmux 명령어이나 본 브리프의 치환 대상(`tmux`/`TMUX`/`tmux_session_name` 텍스트 표기)이 아님 — "tmux" 단어를 포함하지 않으므로 grep 스윕에 걸리지 않음
- **심각도**: INFO — 본 브리프 범위 밖. 후속 작업에서 `herdr agent read --lines 200` 등 herdr 네이티브 명령으로 교체 권장 (별도 결정 사항)
## 제외 항목
- 본 브리프는 지침 문서 2종만 대상 — 다른 파일은 수정하지 않음 ✅
[VERDICT: PASS]
+63 -29
View File
@@ -58,12 +58,6 @@ _resolve_real_herdr_path() {
_init_herdr_isolation() { _init_herdr_isolation() {
local wrapper_dir="$WORKSPACE_ROOT/.mam/shim" local wrapper_dir="$WORKSPACE_ROOT/.mam/shim"
mkdir -p "$wrapper_dir" mkdir -p "$wrapper_dir"
if [ -x "$wrapper_dir/herdr" ]; then
if [[ ":$PATH:" != *":$wrapper_dir:"* ]]; then
export PATH="$wrapper_dir:$PATH"
fi
return 0
fi
local tmp_file local tmp_file
tmp_file=$(mktemp "$wrapper_dir/herdr.XXXXXX") tmp_file=$(mktemp "$wrapper_dir/herdr.XXXXXX")
@@ -123,7 +117,7 @@ except Exception:
# Headless bootstrap avoids herdr's "nested herdr is disabled" guard that # Headless bootstrap avoids herdr's "nested herdr is disabled" guard that
# blocks a normal interactive `herdr --session <name>` launch from inside # blocks a normal interactive `herdr --session <name>` launch from inside
# an existing herdr pane (which is how MAM's own agents usually run). # an existing herdr pane (which is how MAM's own agents usually run).
nohup "$REAL_HERDR" --session "$_MAM_SESSION" server >/dev/null 2>&1 & setsid "$REAL_HERDR" --session "$_MAM_SESSION" server </dev/null >/dev/null 2>&1 &
disown 2>/dev/null || true disown 2>/dev/null || true
for _mam_wait_i in $(seq 1 40); do for _mam_wait_i in $(seq 1 40); do
[ -S "${HOME:-$HOME_DIR}/.config/herdr/sessions/$_MAM_SESSION/herdr.sock" ] && break [ -S "${HOME:-$HOME_DIR}/.config/herdr/sessions/$_MAM_SESSION/herdr.sock" ] && break
@@ -221,17 +215,47 @@ print('\t'.join(env_flags) + '\n' + ' '.join(binary_tokens))
# fresh workspace inside whatever session is active (default or # fresh workspace inside whatever session is active (default or
# isolated) and place the agent there. No cross-session label lookup # isolated) and place the agent there. No cross-session label lookup
# needed since `--session` already scopes everything. # needed since `--session` already scopes everything.
ws_id=$(_real_herdr workspace create --cwd "${ws:-.}" --no-focus 2>/dev/null | python3 -c " # Resolve ws_id and split direction:
import sys, json # - If we have up to 3 agents, split 'right' (columns)
# - If we have 4 or more agents, split 'down' (rows)
res=$(_real_herdr workspace list 2>/dev/null | WS_CWD="${ws:-.}" python3 -c "
import sys, json, os
try: try:
d = json.loads(sys.stdin.read()) target_label = os.path.basename(os.path.abspath(os.environ.get('WS_CWD', '.')))
print(d.get('result', {}).get('workspace', {}).get('workspace_id', '')) wlist = json.loads(sys.stdin.read()).get('result', {}).get('workspaces', [])
match = None
for w in wlist:
if w.get('label') == target_label:
match = w
break
if not match and wlist:
match = wlist[0]
if match:
ws_id = match.get('workspace_id', 'w1')
pane_count = match.get('pane_count', 0)
split_dir = 'right' if pane_count < 3 else 'down'
print(f'{ws_id}\t{split_dir}')
except Exception: except Exception:
pass pass
") ")
ws_id="${ws_id:-w1}" ws_id=$(echo "$res" | cut -f1)
split_dir=$(echo "$res" | cut -f2)
eval "_real_herdr agent start \"$name\" --workspace \"$ws_id\" --cwd \"${ws:-.}\" $env_flags -- $final_cmd" if [ -z "$ws_id" ]; then
ws_id=$(_real_herdr workspace create --cwd "${ws:-.}" --no-focus 2>/dev/null | python3 -c "
import sys, json
try:
d = json.loads(sys.stdin.read()).get('result', {}).get('workspace', {}).get('workspace_id', '')
print(d)
except Exception:
pass
")
split_dir="right"
fi
ws_id="${ws_id:-w1}"
split_dir="${split_dir:-right}"
eval "_real_herdr agent start \"$name\" --workspace \"$ws_id\" --split \"$split_dir\" --cwd \"${ws:-.}\" $env_flags -- $final_cmd"
;; ;;
kill-session) kill-session)
sess="" sess=""
@@ -339,7 +363,7 @@ except Exception:
esac esac
;; ;;
capture-pane) capture-pane)
sess="" sess="" src="visible" lines="100"
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
case "$1" in case "$1" in
-t) -t)
@@ -350,10 +374,22 @@ except Exception:
sess="$2" sess="$2"
shift 2 shift 2
;; ;;
-S)
# tmux `-S -N` (start N lines above the visible viewport) maps to
# herdr `--source recent --lines N`. Non-negative or non-numeric
# values keep the default visible viewport read.
if [ $# -ge 2 ] && [[ "$2" =~ ^-[0-9]+$ ]]; then
src="recent"
lines="${2#-}"
shift 2
else
shift
fi
;;
*) shift ;; *) shift ;;
esac esac
done done
_real_herdr agent read "$sess" --source visible --lines 100 2>/dev/null || true _real_herdr agent read "$sess" --source "$src" --lines "$lines" 2>/dev/null || true
;; ;;
send-keys) send-keys)
sess="" key="" sess="" key=""
@@ -447,7 +483,11 @@ except Exception:
esac esac
EOF EOF
chmod +x "$tmp_file" chmod +x "$tmp_file"
mv -f "$tmp_file" "$wrapper_dir/herdr" if [ -f "$wrapper_dir/herdr" ] && cmp -s "$tmp_file" "$wrapper_dir/herdr"; then
rm -f "$tmp_file"
else
mv -f "$tmp_file" "$wrapper_dir/herdr"
fi
if [[ ":$PATH:" != *":$wrapper_dir:"* ]]; then if [[ ":$PATH:" != *":$wrapper_dir:"* ]]; then
export PATH="$wrapper_dir:$PATH" export PATH="$wrapper_dir:$PATH"
fi fi
@@ -467,9 +507,9 @@ herdr() {
} }
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# resolve_herdr_server <session_name> # resolve_herdr_session <session_name>
# #
# Query agent-sessions.yaml to find the herdr_server associated with a session. # Query agent-sessions.yaml to find the herdr_session associated with a session.
# Fallback to HERDR_SERVER_NAME or 'default' if not registered or field is missing. # Fallback to HERDR_SERVER_NAME or 'default' if not registered or field is missing.
# Prints the resolved server name on stdout. # Prints the resolved server name on stdout.
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -513,13 +553,7 @@ print(json.dumps(d, ensure_ascii=False))
PYEOF PYEOF
} }
# Despite the name (kept for caller compatibility — resume/stop/update_yaml_resumed resolve_herdr_session() {
# all do `HERDR_SERVER_NAME="$(resolve_herdr_workspace "$SESSION_NAME")"`), this
# returns the isolated herdr *session* name to use for this MAM session row, not
# a workspace id. Real isolation is `--session <name>` (see `_MAM_SESSION` in the
# generated wrapper) — a workspace label match provides no actual isolation
# since agent/pane commands are server-global regardless of workspace.
resolve_herdr_workspace() {
local session_name="$1" local session_name="$1"
MAM_STATE_JSON="$(load_state_json)" SESSION_NAME="$session_name" python3 -c " MAM_STATE_JSON="$(load_state_json)" SESSION_NAME="$session_name" python3 -c "
import sys, os, json import sys, os, json
@@ -527,7 +561,7 @@ name = os.environ['SESSION_NAME']
d = json.loads(os.environ.get('MAM_STATE_JSON', '{}')) d = json.loads(os.environ.get('MAM_STATE_JSON', '{}'))
for s in d.get('herdr_sessions', []): for s in d.get('herdr_sessions', []):
if s.get('name') == name: if s.get('name') == name:
print(s.get('herdr_workspace') or s.get('herdr_server') or 'default') print(s.get('herdr_session') or s.get('herdr_workspace') or s.get('herdr_server') or 'default')
sys.exit(0) sys.exit(0)
print(os.environ.get('HERDR_SERVER_NAME', 'default')) print(os.environ.get('HERDR_SERVER_NAME', 'default'))
" "
@@ -1554,7 +1588,7 @@ send_keys_safe() {
_sks_herdr send-keys -t "$sess" C-m _sks_herdr send-keys -t "$sess" C-m
return 0 return 0
fi fi
sleep 0.5 sleep "${SKS_PASTE_SLEEP:-1.5}"
local pane_content was_popup=0 local pane_content was_popup=0
pane_content=$(_pane_capture "$sess") pane_content=$(_pane_capture "$sess")
if printf '%s\n' "$pane_content" | grep -Eq "Pasted text|paste again to expand"; then if printf '%s\n' "$pane_content" | grep -Eq "Pasted text|paste again to expand"; then
@@ -1563,7 +1597,7 @@ send_keys_safe() {
# lines at the terminal width (and some TUIs add indentation on the # lines at the terminal width (and some TUIs add indentation on the
# continuation line too), which can split/reformat the marker across two # continuation line too), which can split/reformat the marker across two
# visual lines and make a literal grep miss it even though the paste landed. # visual lines and make a literal grep miss it even though the paste landed.
elif ! printf '%s' "$pane_content" | tr -d '[:space:]' | grep -Fq "$marker_norm"; then elif [ -n "$marker_norm" ] && ! printf '%s' "$pane_content" | tr -d '[:space:]' | grep -Fq "$marker_norm"; then
echo "send_keys_safe: paste not visible ($sess)" >&2 echo "send_keys_safe: paste not visible ($sess)" >&2
return 3 return 3
fi fi
@@ -1578,7 +1612,7 @@ send_keys_safe() {
if printf '%s\n' "$cur_content" | grep -Eq "● |✽ |[A-Za-z]+ing…|[A-Za-z]+ing\.\.\.|esc to interrupt"; then if printf '%s\n' "$cur_content" | grep -Eq "● |✽ |[A-Za-z]+ing…|[A-Za-z]+ing\.\.\.|esc to interrupt"; then
return 0 return 0
fi fi
if [ "$was_popup" = "0" ] && ! _pane_tail "$sess" 3 | tr -d '[:space:]' | grep -Fq "$marker_norm" && [ "$cur_content" != "$pre_submit" ]; then if [ "$was_popup" = "0" ] && { [ -z "$marker_norm" ] || ! _pane_tail "$sess" 3 | tr -d '[:space:]' | grep -Fq "$marker_norm"; } && [ "$cur_content" != "$pre_submit" ]; then
return 0 return 0
elif [ "$was_popup" = "1" ] && \ elif [ "$was_popup" = "1" ] && \
! printf '%s\n' "$cur_content" | grep -Eq "Pasted text|paste again to expand" && \ ! printf '%s\n' "$cur_content" | grep -Eq "Pasted text|paste again to expand" && \
@@ -310,7 +310,7 @@ entry = {
'role': role, 'role': role,
'herdr_session_created_at': os.environ['NOW_ISO'], 'herdr_session_created_at': os.environ['NOW_ISO'],
'herdr_session_epoch': int(epoch) if epoch.isdigit() else 0, 'herdr_session_epoch': int(epoch) if epoch.isdigit() else 0,
'herdr_server': server_name, 'herdr_session': server_name,
'delegate_job_id': os.environ.get('DELEGATE_JOB_ID', '') or None, 'delegate_job_id': os.environ.get('DELEGATE_JOB_ID', '') or None,
'pane': { 'pane': {
'index': 0, 'index': 0,
@@ -138,6 +138,19 @@ cmd_submit() {
- **Timeout**: $TIMEOUT s (Idle: $IDLE_TIMEOUT s) - **Timeout**: $TIMEOUT s (Idle: $IDLE_TIMEOUT s)
- **Output Report Path**: .mam/jobs/$JOB_ID/$AGENT-reports/report-final.md - **Output Report Path**: .mam/jobs/$JOB_ID/$AGENT-reports/report-final.md
## 🔔 Execution Commands
On start run:
python3 .agents/skills/multi-agent-mux-delegate-job/scripts/publish_event.py --registry-dir .mam/jobs --job $JOB_ID --event started
On progress (optional):
python3 .agents/skills/multi-agent-mux-delegate-job/scripts/publish_event.py --registry-dir .mam/jobs --job $JOB_ID --event progress --detail '<short status>'
On success run:
python3 .agents/skills/multi-agent-mux-delegate-job/scripts/publish_event.py --registry-dir .mam/jobs --job $JOB_ID --event completed --detail '<one-line summary>'
On failure run:
python3 .agents/skills/multi-agent-mux-delegate-job/scripts/publish_event.py --registry-dir .mam/jobs --job $JOB_ID --event error --detail '<one-line reason>'
## 🔎 Task Description ## 🔎 Task Description
$PROMPT $PROMPT
EOF EOF
@@ -184,7 +197,8 @@ EOF
# an id from an earlier session is the #1 reason a delegated job sits idle and # an id from an earlier session is the #1 reason a delegated job sits idle and
# times out (see SKILL.md "Wrong job_id propagated to the agent"). We make the # times out (see SKILL.md "Wrong job_id propagated to the agent"). We make the
# freshness explicit in the instruction header. # freshness explicit in the instruction header.
local instructions="Your job_id is \"$JOB_ID\". Detailed task requirements, instructions, and target output paths are documented in the task brief file at: .mam/jobs/$JOB_ID/brief.md. Please READ and follow .mam/jobs/$JOB_ID/brief.md to complete your work. Commands: start='$pub --event started', success='$pub --event completed --detail <summary>', error='$pub --event error --detail <reason>'." # Keep this short and ASCII-only to prevent paste/wrap rendering issues in CLI REPLs.
local instructions="Job $JOB_ID: Read .mam/jobs/$JOB_ID/brief.md and complete the task."
run_agent "$JOB_ID" "$instructions" run_agent "$JOB_ID" "$instructions"
@@ -440,7 +454,7 @@ run_agent() {
# the caller having exported HERDR_SERVER_NAME by hand. This is what lets # the caller having exported HERDR_SERVER_NAME by hand. This is what lets
# delegation reach an agent living in an isolated herdr session (e.g. one # delegation reach an agent living in an isolated herdr session (e.g. one
# created with --herdr-server) instead of silently looking in "default". # created with --herdr-server) instead of silently looking in "default".
export HERDR_SERVER_NAME="$(resolve_herdr_workspace "$sess")" export HERDR_SERVER_NAME="$(resolve_herdr_session "$sess")"
if ! herdr has-session -t "$sess" 2>/dev/null; then if ! herdr has-session -t "$sess" 2>/dev/null; then
echo "ERROR: 에이전트 세션 '$sess'이 존재하지 않습니다. 작업을 위임하기 전에 먼저 에이전트 세션을 기동해 주세요." >&2 echo "ERROR: 에이전트 세션 '$sess'이 존재하지 않습니다. 작업을 위임하기 전에 먼저 에이전트 세션을 기동해 주세요." >&2
@@ -121,7 +121,7 @@ _changed = False
for s in d.get('herdr_sessions', []): for s in d.get('herdr_sessions', []):
if s.get('delegate_job_id') == _jid and s.get('status') == 'running': if s.get('delegate_job_id') == _jid and s.get('status') == 'running':
_name = s.get('name') _name = s.get('name')
_srv = s.get('herdr_workspace') or s.get('herdr_server') or 'default' _srv = s.get('herdr_session') or s.get('herdr_workspace') or s.get('herdr_server') or 'default'
if _event == 'completed': if _event == 'completed':
s['delegate_job_id'] = None s['delegate_job_id'] = None
print('MQTT Monitor: job completed on ' + str(_name) + ' — session kept alive', flush=True) print('MQTT Monitor: job completed on ' + str(_name) + ' — session kept alive', flush=True)
@@ -131,7 +131,8 @@ for s in d.get('herdr_sessions', []):
s['terminated_at'] = _now.strftime('%Y-%m-%dT%H:%M:%SZ') s['terminated_at'] = _now.strftime('%Y-%m-%dT%H:%M:%SZ')
s['terminated_at_epoch'] = int(_now.timestamp()) s['terminated_at_epoch'] = int(_now.timestamp())
s['termination_mode'] = 'auto-detected (MQTT ' + _event + ')' s['termination_mode'] = 'auto-detected (MQTT ' + _event + ')'
_cmd = ['herdr'] + (['-L', _srv] if _srv != 'default' else []) + ['kill-session', '-t', _name] _shim = os.path.join(os.environ.get('WORKSPACE_ROOT', os.getcwd()), '.mam/shim/herdr')
_cmd = [_shim] + (['-L', _srv] if _srv != 'default' else []) + ['kill-session', '-t', _name]
subprocess.run(_cmd, capture_output=True) subprocess.run(_cmd, capture_output=True)
print('MQTT Monitor: terminated + killed ' + str(_name) + ' on ' + str(_srv) + ' due to MQTT ' + _event, flush=True) print('MQTT Monitor: terminated + killed ' + str(_name) + ' on ' + str(_srv) + ' due to MQTT ' + _event, flush=True)
_changed = True _changed = True
@@ -309,6 +310,8 @@ import yaml
yaml_path = os.environ['YAML_PATH'] yaml_path = os.environ['YAML_PATH']
home = os.environ['HOME_DIR'] home = os.environ['HOME_DIR']
claude_project_dir = os.environ.get('CLAUDE_PROJECT_DIR', f"{home}/.claude/projects") claude_project_dir = os.environ.get('CLAUDE_PROJECT_DIR', f"{home}/.claude/projects")
workspace_root = os.environ.get('WORKSPACE_ROOT', os.getcwd())
shim_herdr = os.path.join(workspace_root, '.mam/shim/herdr')
now_iso = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ') now_iso = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')
@@ -342,12 +345,12 @@ unique_servers = {'default'}
if 'HERDR_SERVER_NAME' in os.environ: if 'HERDR_SERVER_NAME' in os.environ:
unique_servers.add(os.environ['HERDR_SERVER_NAME']) unique_servers.add(os.environ['HERDR_SERVER_NAME'])
for s in d.get('herdr_sessions', []): for s in d.get('herdr_sessions', []):
srv = s.get('herdr_workspace') or s.get('herdr_server') or 'default' srv = s.get('herdr_session') or s.get('herdr_workspace') or s.get('herdr_server') or 'default'
unique_servers.add(srv) unique_servers.add(srv)
try: try:
for srv in sorted(unique_servers): for srv in sorted(unique_servers):
cmd = ['herdr'] cmd = [shim_herdr]
if srv != 'default': if srv != 'default':
cmd += ['-L', srv] cmd += ['-L', srv]
cmd += ['ls', '-F', '#{session_name}|#{session_created}'] cmd += ['ls', '-F', '#{session_name}|#{session_created}']
@@ -369,7 +372,7 @@ except Exception:
def pane_meta(session, srv): def pane_meta(session, srv):
try: try:
cmd = ['herdr'] cmd = [shim_herdr]
if srv != 'default': if srv != 'default':
cmd += ['-L', srv] cmd += ['-L', srv]
cmd += ['list-panes', '-t', session, '-F', cmd += ['list-panes', '-t', session, '-F',
@@ -396,7 +399,7 @@ if herdr_confirmed:
# (없으면 herdr-dead stopped 세션을 'terminated' 로 덮어써 resumable 플래그가 소실됨) # (없으면 herdr-dead stopped 세션을 'terminated' 로 덮어써 resumable 플래그가 소실됨)
if s.get('status') in ('terminated', 'archived', 'stopped'): if s.get('status') in ('terminated', 'archived', 'stopped'):
continue continue
srv = s.get('herdr_workspace') or s.get('herdr_server') or 'default' srv = s.get('herdr_session') or s.get('herdr_workspace') or s.get('herdr_server') or 'default'
if (name, srv) not in alive_set: if (name, srv) not in alive_set:
s['status'] = 'terminated' s['status'] = 'terminated'
s['terminated_at'] = now_iso s['terminated_at'] = now_iso
@@ -37,7 +37,7 @@ if [ -z "$UUID" ]; then
exit 1 exit 1
fi fi
HERDR_SERVER_NAME="$(resolve_herdr_workspace "$SESSION_NAME")" HERDR_SERVER_NAME="$(resolve_herdr_session "$SESSION_NAME")"
export HERDR_SERVER_NAME export HERDR_SERVER_NAME
# 2. If herdr is alive, print warning or attach. # 2. If herdr is alive, print warning or attach.
@@ -33,7 +33,7 @@ done
[ -n "$UUID" ] || { echo "ERROR: --uuid required" >&2; exit 2; } [ -n "$UUID" ] || { echo "ERROR: --uuid required" >&2; exit 2; }
[ -f "$AGENT_SESSIONS_YAML" ] || { echo "ERROR: $AGENT_SESSIONS_YAML not found" >&2; exit 1; } [ -f "$AGENT_SESSIONS_YAML" ] || { echo "ERROR: $AGENT_SESSIONS_YAML not found" >&2; exit 1; }
HERDR_SERVER_NAME="$(resolve_herdr_workspace "$SESSION_NAME")" HERDR_SERVER_NAME="$(resolve_herdr_session "$SESSION_NAME")"
export HERDR_SERVER_NAME export HERDR_SERVER_NAME
# --agent 미지정 시 이름 suffix 로 fallback (P1-F: 가능하면 --agent 명시) # --agent 미지정 시 이름 suffix 로 fallback (P1-F: 가능하면 --agent 명시)
@@ -93,7 +93,7 @@ def get_job_status(s):
sessions_detail = [] sessions_detail = []
for s in d.get('herdr_sessions', []): for s in d.get('herdr_sessions', []):
name = s.get('name', '?') name = s.get('name', '?')
server = s.get('herdr_workspace') or s.get('herdr_server') or 'default' server = s.get('herdr_session') or s.get('herdr_workspace') or s.get('herdr_server') or 'default'
jid, jstatus = get_job_status(s) jid, jstatus = get_job_status(s)
pane = s.get('pane') or {} pane = s.get('pane') or {}
sessions_detail.append({ sessions_detail.append({
@@ -197,7 +197,7 @@ if not sessions:
print("(no sessions registered)") print("(no sessions registered)")
for s in sessions: for s in sessions:
name = s.get('name', '?') name = s.get('name', '?')
server = s.get('herdr_workspace') or s.get('herdr_server') or 'default' server = s.get('herdr_session') or s.get('herdr_workspace') or s.get('herdr_server') or 'default'
status = s.get('status', '?') status = s.get('status', '?')
herdr = 'alive' if f"{name}|{server}" in alive else 'dead' herdr = 'alive' if f"{name}|{server}" in alive else 'dead'
cmd = (s.get('pane') or {}).get('cmd', '?') cmd = (s.get('pane') or {}).get('cmd', '?')
@@ -82,7 +82,7 @@ if [ "$PURGE" = "1" ]; then
trap 'rm -f "$WORKSPACE_ROOT/.mam/purging-$SESSION_NAME"' EXIT trap 'rm -f "$WORKSPACE_ROOT/.mam/purging-$SESSION_NAME"' EXIT
fi fi
HERDR_SERVER_NAME="$(resolve_herdr_workspace "$SESSION_NAME")" HERDR_SERVER_NAME="$(resolve_herdr_session "$SESSION_NAME")"
export HERDR_SERVER_NAME export HERDR_SERVER_NAME
# --agent 미지정 시 이름 suffix 로 fallback (P1-F) # --agent 미지정 시 이름 suffix 로 fallback (P1-F)
+3 -3
View File
@@ -18,7 +18,7 @@ MAM 스킬 및 스크립트들은 호스트 시스템의 다음 도구들에 의
## 2. 🚀 자동 설치 방법 ## 2. 🚀 자동 설치 방법
MAM의 자동 설치 스크립트(`deploy/install_mam.sh`)를 사용하여 10초 만에 필요한 규칙과 라이프사이클 툴킷을 타겟 프로젝트에 이식할 수 있습니다. 스크립트는 실행 시 자동으로 시스템의 `tmux`, `python3`, `rsync`, `uuidgen` 및 필수 파이썬 모듈들을 진단합니다. MAM의 자동 설치 스크립트(`deploy/install_mam.sh`)를 사용하여 10초 만에 필요한 규칙과 라이프사이클 툴킷을 타겟 프로젝트에 이식할 수 있습니다. 스크립트는 실행 시 자동으로 시스템의 `herdr`, `python3`, `rsync`, `uuidgen` 및 필수 파이썬 모듈들을 진단합니다.
> [!IMPORTANT] > [!IMPORTANT]
> **설치 전제조건**: MAM 스킬을 타겟 프로젝트에 설치하려면 **먼저 MAM 레포지토리가 로컬 머신에 clone 되어 있어야 합니다.** > **설치 전제조건**: MAM 스킬을 타겟 프로젝트에 설치하려면 **먼저 MAM 레포지토리가 로컬 머신에 clone 되어 있어야 합니다.**
@@ -34,7 +34,7 @@ $ bash deploy/install_mam.sh --target /path/to/your/project --force
``` ```
### 설치 스크립트가 수행하는 작업: ### 설치 스크립트가 수행하는 작업:
1. **의존성 진단**: 시스템에 `tmux`, `python3`, `rsync`, `uuidgen` CLI 바이너리와 파이썬 `pyyaml`/`sqlite3` 모듈이 설치되어 있는지 확인합니다. 1. **의존성 진단**: 시스템에 `herdr`, `python3`, `rsync`, `uuidgen` CLI 바이너리와 파이썬 `pyyaml`/`sqlite3` 모듈이 설치되어 있는지 확인합니다.
2. **규칙 및 스킬 복제**: 오케스트레이션 가이드(`.agents/` 하위 전체)를 타겟 프로젝트 하위로 이식합니다. 2. **규칙 및 스킬 복제**: 오케스트레이션 가이드(`.agents/` 하위 전체)를 타겟 프로젝트 하위로 이식합니다.
3. **지침 전파**: 에이전트가 로드하고 복종할 행동 지침 문서(`AGENTS.md`)를 프로젝트 루트에 복사합니다. 3. **지침 전파**: 에이전트가 로드하고 복종할 행동 지침 문서(`AGENTS.md`)를 프로젝트 루트에 복사합니다.
4. **형상 제외 설정**: 세션 DB 및 격리 캐시 저장소인 `.mam/` 디렉토리를 타겟 프로젝트의 `.gitignore` 에 자동 주입하여 불필요한 형상 관리를 방지합니다. 4. **형상 제외 설정**: 세션 DB 및 격리 캐시 저장소인 `.mam/` 디렉토리를 타겟 프로젝트의 `.gitignore` 에 자동 주입하여 불필요한 형상 관리를 방지합니다.
@@ -66,7 +66,7 @@ $ herdr session attach my-project-dev-claude
* **화면 탈출**: 대화 중 세션을 유지한 채 터미널로 돌아오려면 `Ctrl + B`를 누른 뒤 `D` 키를 차례로 입력합니다. * **화면 탈출**: 대화 중 세션을 유지한 채 터미널로 돌아오려면 `Ctrl + B`를 누른 뒤 `D` 키를 차례로 입력합니다.
### 3) 에이전트 상태 복원 (Resume) ### 3) 에이전트 상태 복원 (Resume)
세션이 중지되었거나, 호스트 재기동으로 tmux가 소멸한 경우에도 이전 대화 ID 및 격리 디렉토리를 원자적으로 이어받아 다시 기동할 수 있습니다. 세션이 중지되었거나, 호스트 재기동으로 herdr 서버가 소멸한 경우에도 이전 대화 ID 및 격리 디렉토리를 원자적으로 이어받아 다시 기동할 수 있습니다.
```bash ```bash
# 1단계: 복원 대상 세션의 UUID 자동 조회 (DB/YAML 레지스트리 기반) # 1단계: 복원 대상 세션의 UUID 자동 조회 (DB/YAML 레지스트리 기반)
$ WORKSPACE="/path/to/your/project" $ WORKSPACE="/path/to/your/project"
+1 -1
View File
@@ -6,7 +6,7 @@ This directory contains packaging templates and installation scripts to deploy t
## 📁 Deployment Directory Structure ## 📁 Deployment Directory Structure
* **`install.sh`**: A self-contained, idempotent remote shell installer (via curl) that checks system requirements (`tmux`, `python3`), detects NFS/network filesystem mounts, sets up a local python virtual environment (`.venv`), and initializes environment configuration (`.env`). * **`install.sh`**: A self-contained, idempotent remote shell installer (via curl) that checks system requirements (`herdr`, `python3`), detects NFS/network filesystem mounts, sets up a local python virtual environment (`.venv`), and initializes environment configuration (`.env`).
* **`install_mam.sh`**: A local-clone installer that copies rules/skills (`.agents/`), `AGENTS.md`, and sets up environment bootstrap on target projects. * **`install_mam.sh`**: A local-clone installer that copies rules/skills (`.agents/`), `AGENTS.md`, and sets up environment bootstrap on target projects.
* **`generate-env.sh`**: Environment configuration bootstrap helper. * **`generate-env.sh`**: Environment configuration bootstrap helper.
* **`INSTALL.md`**: Detailed installation and quick-start user manual. * **`INSTALL.md`**: Detailed installation and quick-start user manual.
+2 -2
View File
@@ -28,7 +28,7 @@ check_cmd() {
fi fi
} }
check_cmd tmux check_cmd herdr
check_cmd python3 check_cmd python3
# Verify Python Version # Verify Python Version
@@ -246,7 +246,7 @@ MQTT_BROKER=broker.hivemq.com
MQTT_PORT=1883 MQTT_PORT=1883
MQTT_TLS=0 MQTT_TLS=0
MQTT_CLIENT_ID_PREFIX=mam-agent MQTT_CLIENT_ID_PREFIX=mam-agent
TMUX_SERVER_NAME=default HERDR_SERVER_NAME=default
EOF EOF
chmod 0600 "$ENV_FILE" chmod 0600 "$ENV_FILE"
echo "✅ Config file .env initialized with chmod 0600." echo "✅ Config file .env initialized with chmod 0600."
+2 -2
View File
@@ -221,10 +221,10 @@ cat <<EOF
1. Initialize a new isolated session: 1. Initialize a new isolated session:
$ bash .agents/skills/multi-agent-mux-create/scripts/create_session.sh \\ $ bash .agents/skills/multi-agent-mux-create/scripts/create_session.sh \\
--workspace "$TARGET_DIR" --agent claude --role developer --isolate \\ --workspace "$TARGET_DIR" --agent claude --role developer --isolate \\
--tmux-server multi-agent-mux --herdr-server multi-agent-mux
2. Attach to the running session: 2. Attach to the running session:
$ tmux -L multi-agent-mux attach -t <session_name> $ HERDR_SERVER_NAME=multi-agent-mux herdr agent attach <session_name>
3. Gracefully stop the session: 3. Gracefully stop the session:
$ bash .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \\ $ bash .agents/skills/multi-agent-mux-stop/scripts/stop_session.sh \\
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"name": "multi-agent-mux", "name": "multi-agent-mux",
"description": "Multi-Agent Orchestration & Messaging Backplane on Tmux & MQTT.", "description": "Multi-Agent Orchestration & Messaging Backplane on Herdr & MQTT.",
"disabled": false "disabled": false
} }
-5
View File
@@ -1,5 +0,0 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages: {}
sdks:
dart: ">=3.0.0 <4.0.0"
+1 -1
View File
@@ -124,7 +124,7 @@ d['herdr_sessions'] = [{{
'name': '{session_name}', 'name': '{session_name}',
'status': 'running', 'status': 'running',
'role': 'Creator', 'role': 'Creator',
'herdr_workspace': 'my-custom-workspace', 'herdr_session': 'my-custom-workspace',
'pane': {{ 'pane': {{
'cwd': 'WS_PLACEHOLDER', 'cwd': 'WS_PLACEHOLDER',
'pid': 7777, 'pid': 7777,
+6 -6
View File
@@ -104,15 +104,15 @@ def test_create_validate_env_key(mam_sandbox):
# FEATURE 2: Resume Session (6 Test Cases) # FEATURE 2: Resume Session (6 Test Cases)
# ============================================================================== # ==============================================================================
def test_resume_resolve_herdr_workspace_default(mam_sandbox): def test_resume_resolve_herdr_session_default(mam_sandbox):
"""Test resolve_herdr_workspace fallback behavior when session is not in YAML.""" """Test resolve_herdr_session fallback behavior when session is not in YAML."""
res = run_lib_func(mam_sandbox, "resolve_herdr_workspace", "non-existent-session") res = run_lib_func(mam_sandbox, "resolve_herdr_session", "non-existent-session")
assert res.returncode == 0 assert res.returncode == 0
assert res.stdout.strip() == "default" assert res.stdout.strip() == "default"
def test_resume_resolve_herdr_workspace_env(mam_sandbox): def test_resume_resolve_herdr_session_env(mam_sandbox):
"""Test resolve_herdr_workspace fallback to HERDR_SERVER_NAME env var.""" """Test resolve_herdr_session fallback to HERDR_SERVER_NAME env var."""
res = run_lib_func(mam_sandbox, "resolve_herdr_workspace", "non-existent-session", env={"HERDR_SERVER_NAME": "custom_server"}) res = run_lib_func(mam_sandbox, "resolve_herdr_session", "non-existent-session", env={"HERDR_SERVER_NAME": "custom_server"})
assert res.returncode == 0 assert res.returncode == 0
assert res.stdout.strip() == "custom_server" assert res.stdout.strip() == "custom_server"
+1 -1
View File
@@ -63,7 +63,7 @@ def test_integration_create_options_combination(mam_sandbox, mock_herdr, mock_ag
session = sessions[0] session = sessions[0]
assert session["role"] == "Creator" assert session["role"] == "Creator"
assert session["herdr_server"] == "custom_server" assert session.get("herdr_session") == "custom_server"
assert session["delegate_job_id"] is not None assert session["delegate_job_id"] is not None
assert session["status"] == "running" assert session["status"] == "running"