# ๐Ÿ”Œ 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 --kind -- ) โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ–ผ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ 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 ) โ”‚ โ–ผ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ 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() โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ–ผ โ–ผ โ–ผ โ–ผ โ–ผ โ”‚ โ”‚ ClaudeAgentAdapter AgyAgentAdapter ClineAgentAdapter HermesAgentAdapter [NewAgentAdapter] โ”‚ โ”‚ (Claude Code) (Antigravity) (Cline) (Hermes) (Grok / Codex / ...) โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ ``` --- ## 2. 5๋‹จ๊ณ„ ํ•„์ˆ˜ ๊ตฌํ˜„ ๊ฐ€์ด๋“œ (Step-by-Step Implementation) ์‹ ๊ทœ ์—์ด์ „ํŠธ ``(์˜ˆ: `grok`)๋ฅผ ์—ฐ๋™ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋‹ค์Œ 5๊ฐœ ํŒŒ์ผ/์˜์—ญ์˜ ์ˆ˜์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ``` ๐Ÿ“ .agents/skills/lib_py/agents/adapters/.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/.py`) `BaseAgentAdapter`๋ฅผ ์ƒ์†ํ•˜๋Š” `AgentAdapter` ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ```python """ .py โ€” agent adapter for Multi-Agent Mux (MAM) """ import os from typing import Any, List, Optional from ..base import BaseAgentAdapter, DiscoveryContext 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 [] ``` --- ### 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(), 'cline': ClineAgentAdapter(), '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 ` โž” ๋ณต์› ์ปค๋งจ๋“œ ์ƒ์„ฑ > - `python -m lib_py.agents resolve ` โž” ์„ธ์…˜ ์ด๋ฆ„ ๊ธฐ๋ฐ˜ ์—์ด์ „ํŠธ ์ž๋™ ์‹๋ณ„ --- ### 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-cline|*-planner-cline|*-reviewer-cline) kind="cline" ;; *-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 "cline"; then kind="cline" 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 cline 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', 'cline', '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** | `~/.opencode/sessions/*.json` | ๋กœ์ปฌ ํ† ํฐ / API Key | ์‰ฌ์›€ (`OpenCode\|Chat`) | **Tier 1 (๋‚ฎ์Œ)** | **~0.5์ผ** | | **Codex CLI** | `~/.codex/projects/*.jsonl` | `OPENAI_API_KEY` | ์‰ฌ์›€ (`Codex\|โฏ`) | **Tier 1 (๋‚ฎ์Œ)** | **~0.5์ผ** | | **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/.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`) ๋™์ž‘ ๊ฒ€์ฆ ์™„๋ฃŒ