Files
multi-agent-mux/docs/NEW_AGENT_INTEGRATION_GUIDE.md
T
GodopuandClaude Opus 5.5 feccfd5e03 fix(agents): pin post-discovered session ids on a verified completed; agy never takes another workspace's or session's conversation (Issue #40)
- BaseAgentAdapter: id_lifecycle (pre_assigned: claude, grok / post_discovered)
  and capture_active_session_id - exactly one verified candidate newer than the
  session that no other row records, else a reason (no-candidate, ambiguous, ...).
- agy: verify_artifact (discover) rejects an id last_conversations.json maps to
  another workspace and always excludes ids other rows record (a cache hit no
  longer bypasses it). capture uses the cache only as a proposal: the
  conversation's presence lock or DB must be held by the row's pane process or
  a descendant (lsof/ps); anything unprovable is not pinned.
- lib_py/agents/autopin.py: handle_completed, run by the side that verified the
  job's completed - the delegate-job wrapper (direct), create --onboard and the
  loop's wait_for_job (recovery, as a subprocess: G9). Local herdr jobs only;
  one (job, dispatch token, attempt) is handled once (job-scoped flock + marker
  in the job dir); writes go through atomic_dump_yaml; a pin records
  session_id_source=discovered / session_id_verified=true; a failure leaves the
  reason on the row and never changes the job's result.
- reconcile.sh drift C for agy, hermes, opencode and codex calls the same
  capture (the agy *.db glob is gone); _pin_and_verify_resume uses pin_row.
- Tests: tests/test_issue40_auto_pin.py; issue64 :407 now expects False; the
  static reconcile-source checks follow the move to the adapters; the two tier2
  drift-C component tests create with --no-onboard (onboarding now pins).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 23:40:03 +09:00

298 lines
18 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.
# 🔌 Multi-Agent Mux (MAM): 신규 에이전트 타입 추가 가이드 (New Agent Integration Guide)
본 문서는 Multi-Agent Mux (MAM) 프레임워크에 새로운 AI 에이전트 CLI/TUI 백엔드(예: `grok`, `codex`, `opencode`, `kimi`, `cursor` 등)를 추가하기 위한 아키텍처 구조와 **5단계 필수 작업 절차**를 안내합니다.
---
## 1. 아키텍처 개요 (Architecture Overview)
MAM은 에이전트별 동작 특성(TUI 프롬프트 패턴, 세션 복원 인자, 아티팩트 저장소 등)을 Python 기반의 **어댑터 패턴(`BaseAgentAdapter`)**으로 격리하여 관리합니다. 코어 런타임(Shell, Herdr Daemon, MQTT 브로커)을 수정할 필요 없이 어댑터를 플러그인 형태로 추가할 수 있습니다.
```
┌──────────────────────────────────────────────────────────┐
│ Herdr Runtime │
│ (herdr agent start <name> --kind <kind> -- <cmd>) │
└────────────────────────────┬─────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ lib.sh (Shell Runtime) │
│ • resolve_agent_type_from_registry() │
│ • wait_for_tui_ready() (via MAM_READY_TOKENS) │
│ • send_keys_safe() / stop_session.sh │
└────────────────────────────┬─────────────────────────────┘
│
eval $(python -m lib_py.agents facts <agent>)
│
▼
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ lib_py.agents Framework │
│ │
│ ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ │
│ │ BaseAgentAdapter │ │
│ │ • name, own_key, ready_tokens, exit_key, delegate_agent_key, identity_cache_fields │ │
│ │ • input_prompt, input_placeholder, input_rule_pattern │ │
│ │ • artifact_path(), verify_artifact(), purge_artifacts() │ │
│ │ • spawn_spec(), resume_spec(), auth_ok(), discover() │ │
│ │ • id_lifecycle, capture_active_session_id() (Issue #40) │ │
│ └────────────────────────────────────────────────────────────────┬─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────────────────────┬───────────────────────────────┬───────────────────────────────┬─────────────────────┐ │
│ ▼ ▼ ▼ ▼ ▼ │
│ ClaudeAgentAdapter AgyAgentAdapter HermesAgentAdapter GrokAgentAdapter [NewAgentAdapter] │
│ (Claude Code) (Antigravity) (Hermes) (Grok) (Codex / ...) │
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
```
---
## 2. 5단계 필수 구현 가이드 (Step-by-Step Implementation)
신규 에이전트 `<agent>`(예: `grok`)를 연동하기 위해서는 다음 5개 파일/영역의 수정이 필요합니다.
```
📁 .agents/skills/lib_py/agents/adapters/<agent>.py # Step 1: 어댑터 클래스 구현
📁 .agents/skills/lib_py/agents/registry.py # Step 2: 어댑터 레지스트리 등록
📁 .agents/skills/lib.sh # Step 3: 쉘 디스패치 및 kind 매핑
📁 Herdr Runtime Configuration # Step 4: Herdr CLI 데몬 kind 호환성 확인
📁 tests/test_a4_adapter_contract.py # Step 5: 어댑터 계약 단위 테스트 작성
```
---
### Step 1: Python 어댑터 구현 (`.agents/skills/lib_py/agents/adapters/<agent>.py`)
`BaseAgentAdapter`를 상속하는 `<Agent>AgentAdapter` 클래스를 생성합니다.
```python
"""
<agent>.py — <Agent> agent adapter for Multi-Agent Mux (MAM)
"""
import os
from typing import Any, List, Optional
from ..base import BaseAgentAdapter, DiscoveryContext, PRE_ASSIGNED
class GrokAgentAdapter(BaseAgentAdapter):
@property
def name(self) -> str:
"""에이전트 고유 식별자 (소문자 영문)"""
return 'grok'
@property
def own_key(self) -> str:
"""YAML (.mam/agent-sessions.yaml) 세션 상태에 저장될 키 이름"""
return 'grok_session_id_own'
@property
def ready_tokens(self) -> str:
"""TUI 부팅 완료 및 사용자 입력 대기 상태를 감지하는 정규식 패턴"""
return r'Grok|xAI|Assistant|❯|>>>'
@property
def exit_key(self) -> str:
"""정상 종료(Graceful exit) 시 TUI에 입력할 키/명령어"""
return '/exit' # 또는 'Exit', 'quit', ':q'
@property
def delegate_agent_key(self) -> str:
"""delegate-job MQTT 이벤트 채널에서 사용할 수신자 식별자"""
return 'grok-cli'
@property
def identity_cache_fields(self) -> tuple:
"""세션 복원 시 캐시할 식별자 필드 튜플"""
return ('session_id',)
# =========================================================================
# TUI 프롬프트 영역 비주얼 파싱 (선택적 커스터마이징)
# =========================================================================
@property
def input_prompt(self) -> str:
return '❯'
@property
def input_placeholder(self) -> str:
return ''
@property
def input_rule_pattern(self) -> str:
return r'─{10,}'
# =========================================================================
# 아티팩트 및 대화 히스토리 수명 주기 관리
# =========================================================================
def artifact_path(self, uuid: str, ctx: DiscoveryContext) -> str:
"""해당 세션 UUID의 대화 로그/아티팩트가 저장되는 절대 경로 반환"""
return f"{ctx.home_dir}/.grok/sessions/{uuid}.json"
def verify_artifact(self, uuid: str, ctx: DiscoveryContext) -> bool:
"""디스크 상의 아티팩트 유효성 및 타임스탬프 검증"""
path = self.artifact_path(uuid, ctx)
if not os.path.exists(path):
return False
if ctx.epoch and os.path.getmtime(path) < ctx.epoch:
return False
return True
def purge_artifacts(self, uuid: str, ctx: DiscoveryContext) -> List[str]:
"""--purge-conversation 옵션 호출 시 디스크의 대화 파일 삭제"""
path = self.artifact_path(uuid, ctx)
if os.path.exists(path):
os.remove(path)
return [path]
return []
# =========================================================================
# CLI 실행 및 세션 복원 인자 생성
# =========================================================================
def spawn_spec(self, binary: str, session_uuid: str = "", use_wrapper: bool = False) -> str:
"""신규 세션 기동 시 실행할 커맨드라인 문자열 생성"""
if session_uuid:
return f"{binary} --session {session_uuid}"
return binary
def resume_spec(self, binary: str, session_uuid: str, materialized: bool = False) -> str:
"""기존 세션 복원(Resume) 시 실행할 커맨드라인 문자열 생성"""
if materialized and session_uuid:
return f"{binary} --resume {session_uuid}"
return f"{binary} --session {session_uuid}" if session_uuid else binary
# =========================================================================
# 인증 상태 검사 및 세션 디스커버리
# =========================================================================
def auth_ok(self, run_cmd: Optional[Any] = None) -> bool:
"""에이전트 실행에 필요한 API 키/토큰/설정 파일 존재 여부 확인"""
token_file = os.path.expanduser('~/.grok/token.json')
return os.path.exists(token_file) or bool(os.environ.get('GROK_API_KEY') or os.environ.get('XAI_API_KEY'))
def discover(self, ctx: DiscoveryContext) -> List[str]:
"""디스크의 세션 저장소에서 워크스페이스와 일치하는 세션 UUID 목록 탐색"""
return []
# =========================================================================
# 세션 ID 라이프사이클 (Issue #40)
# =========================================================================
@property
def id_lifecycle(self) -> str:
"""PRE_ASSIGNED: 기동 시 --session-id 로 ID 를 넘긴다 (claude, grok).
POST_DISCOVERED(기본값): 첫 턴 뒤 디스크에 생긴 ID 를 찾는다 (agy, opencode, codex, hermes)."""
return PRE_ASSIGNED
```
**세션 ID 자동 확정 (Issue #40)**: `POST_DISCOVERED` 어댑터는 `capture_active_session_id(ctx)` 를 제공합니다. 기본 구현은 `discover()` 후보 중 `verify_session_uuid` 를 통과하고 다른 행이 기록하지 않은 것이 **정확히 하나**일 때만 `capture_result('captured', session_id=...)` 를 돌려주고, 아니면 `no-candidate` / `ambiguous` 등 이유를 돌려줍니다(읽기 전용). 후보 출처가 하나의 값만 주는 경우(agy 의 cwd 캐시처럼)에는 이 유일성이 아무것도 증명하지 못하므로, 그 어댑터는 이 메서드를 재정의해 소유를 따로 증명해야 합니다(agy: presence lock 을 쥔 프로세스가 그 행의 pane 프로세스 또는 자손). 실제 기록은 `completed` 를 검증한 수신 측(`lib_py.agents.autopin`: 위임 래퍼, `create --onboard`, 루프의 잡 대기)과 reconcile drift C 가 같은 판정으로 하며, 어댑터는 YAML/SQLite 에 쓰지 않습니다.
---
### Step 2: 어댑터 레지스트리 등록 (`.agents/skills/lib_py/agents/registry.py`)
`registry.py`의 `_ADAPTERS` 딕셔너리에 새 어댑터 인스턴스를 등록합니다.
```python
# 1. 어댑터 임포트
from .adapters.grok import GrokAgentAdapter
# 2. 레지스트리 맵에 등록
_ADAPTERS: Dict[str, BaseAgentAdapter] = {
'claude': ClaudeAgentAdapter(),
'agy': AgyAgentAdapter(),
'hermes': HermesAgentAdapter(),
'grok': GrokAgentAdapter(), # 👈 추가
}
```
> **등록 후 즉시 사용 가능한 CLI 명령**:
> - `python -m lib_py.agents facts grok` ➔ 쉘 환경변수(`MAM_READY_TOKENS`, `MAM_EXIT_KEY` 등) 자동 출력
> - `python -m lib_py.agents spawn-spec grok grok` ➔ 기동 커맨드 생성
> - `python -m lib_py.agents resume-spec grok grok <uuid>` ➔ 복원 커맨드 생성
> - `python -m lib_py.agents resolve <session_name>` ➔ 세션 이름 기반 에이전트 자동 식별
---
### Step 3: 쉘 런타임 디스패치 연동 (`.agents/skills/lib.sh`)
`lib.sh`에서 Herdr 세션 기동 시 올바른 `kind`가 전달되도록 패턴 매칭을 추가합니다.
#### 1) Herdr Session Kind 매핑 (`lib.sh:357-371`)
```bash
case "$name" in
*-creator-claude|*-planner-claude|*-reviewer-claude) kind="claude" ;;
*-creator-agy|*-planner-agy|*-reviewer-agy) kind="agy" ;;
*-creator-hermes|*-planner-hermes|*-reviewer-hermes) kind="hermes" ;;
*-creator-grok|*-planner-grok|*-reviewer-grok) kind="grok" ;; # 👈 추가
*)
if echo "$name" | grep -qi "claude"; then kind="claude"
elif echo "$name" | grep -qi "agy"; then kind="agy"
elif echo "$name" | grep -qi "hermes"; then kind="hermes"
elif echo "$name" | grep -qi "grok"; then kind="grok" # 👈 추가
else kind="generic"; fi
;;
esac
```
#### 2) 바이너리 이름 중복 제거 튜플 (`lib.sh:385`)
`herdr agent start` 호출 시 첫 번째 인자로 전달되는 바이너리 중복을 방지하기 위해 등록합니다:
```bash
if [[ " claude agy hermes grok " =~ " ${cmd_binary} " ]]; then
```
---
### Step 4: Herdr CLI 데몬 호환성 확인
- **내장 kind 지원 여부 확인**:
- `herdr` 데몬이 해당 `--kind`를 자체적으로 파싱하는지 확인합니다.
- 별도 내장 파서가 없는 경우 `--kind generic`으로도 세션 기동 및 터미널 I/O 제어가 완전하게 지원됩니다.
---
### Step 5: 테스트 작성 및 계약 검증 (`tests/test_a4_adapter_contract.py`)
신규 어댑터가 MAM 계약 규격을 완벽하게 충족하는지 검증하는 단위 테스트를 등록합니다.
```python
# tests/test_a4_adapter_contract.py
def test_agent_adapter_registry():
"""모든 등록된 에이전트 어댑터 인스턴스 검증"""
adapters = get_all_adapters()
assert set(adapters.keys()) == {'claude', 'agy', 'hermes', 'grok'}
def test_adapter_required_properties():
"""어댑터 필수 프로퍼티 무결성 검증"""
# ('grok', ('grok_session_id_own', r'Grok|xAI|Assistant|❯|>>>', '/exit', 'grok-cli', ('session_id',)))
```
테스트 실행:
```bash
.venv/bin/python -m pytest tests/test_a4_adapter_contract.py -v
```
---
## 3. 에이전트 백엔드별 구현 난이도 매트릭스
| 에이전트 백엔드 | 세션 저장소 형식 | 인증 방식 | 프롬프트 감지 난이도 | 난이도 Tier | 예상 소요 공수 |
| :--- | :--- | :--- | :--- | :---: | :---: |
| **OpenCode** (2.x 전용, Issue #60) | `~/.local/share/opencode/opencode.db` (SQLite `session_v2`, 경로는 `opencode debug paths`) | 멀티 Provider Key (DB `credential`) / 무료 모델 | 쉬움 (`OpenCode\|Chat`) | **Tier 1 (낮음)** | **~1.0일** |
| **Codex CLI** | `~/.codex/sessions/*/*/*/rollout-*.jsonl` (SQLite `state_*.sqlite`) | `auth.json` / `OPENAI_API_KEY` | 쉬움 (`Codex\|Ask Codex to do anything`) | **v4.2.0 완료** | **완료** |
| **Kimi CLI** | `~/.kimi/history.db` (SQLite) | `~/.kimi/config` | 쉬움 (`Moonshot\|Kimi`) | **Tier 1 (낮음)** | **~0.5일** |
| **Grok-Build** | `~/.grok/sessions/*.json` | 토큰 파일 / Env | 쉬움 (`Grok\|Building`) | **Tier 1 (낮음)** | **~0.5일** |
| **Cursor CLI** | `~/.cursor/` (Headless/RPC) | OAuth / 쿠키 | 보통 (RPC 포트/상태 파싱) | **Tier 2 (중간)** | **~1.5일** |
| **Local LLM** | `ollama` / `vllm` CLI stdout | 로컬 호스트 | 보통 (`>>>` ANSI 패턴 감지) | **Tier 2 (중간)** | **~1.0일** |
---
## 4. 완료 정의 (Definition of Done Checklist)
신규 에이전트 연동 PR 또는 커밋 전 다음 항목을 확인합니다:
- [ ] `.agents/skills/lib_py/agents/adapters/<agent>.py`에 `BaseAgentAdapter` 모든 추상 메서드/프로퍼티 구현 완료
- [ ] `.agents/skills/lib_py/agents/registry.py`에 어댑터 등록 완료
- [ ] `.agents/skills/lib.sh`에 `kind` 매핑 및 바이너리 패턴 등록 완료
- [ ] `tests/test_a4_adapter_contract.py` 테스트 케이스 추가 및 통과
- [ ] `tests/test_tier1_unit.py` 및 전체 테스트 스위트 통과 (`pytest tests/`)
- [ ] 세션 기동(`multi-agent-mux-create`), 정지(`multi-agent-mux-stop`), 복원(`multi-agent-mux-resume`) 동작 검증 완료