diff --git a/docs/NEW_AGENT_INTEGRATION_GUIDE.md b/docs/NEW_AGENT_INTEGRATION_GUIDE.md new file mode 100644 index 0000000..a5b9134 --- /dev/null +++ b/docs/NEW_AGENT_INTEGRATION_GUIDE.md @@ -0,0 +1,288 @@ +# ๐Ÿ”Œ 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`) ๋™์ž‘ ๊ฒ€์ฆ ์™„๋ฃŒ