Skip to content

Latest commit

Β 

History

History
1423 lines (1133 loc) Β· 51.4 KB

File metadata and controls

1423 lines (1133 loc) Β· 51.4 KB

Mantis v2 β€” 섀계 청사진

ν•œμ€„ μš”μ•½

v1의 Phase νŒŒμ΄ν”„λΌμΈμ„ μœ μ§€ν•˜λ©΄μ„œ, 도ꡬ λͺ©λ‘μ΄ 생성기·싀행기·검증기λ₯Ό κ΄€ν†΅ν•˜μ—¬ λ™μ μœΌλ‘œ 흐λ₯΄κ²Œ ν•œλ‹€. μƒŒλ“œλ°•μŠ€λ₯Ό λ„κ΅¬λ‘œ λ…ΈμΆœν•˜κ³ , μΊ”λ²„μŠ€ 없이도 μ›Œν¬ν”Œλ‘œμš°λ₯Ό μ½”λ“œλ‘œ 쑰립·뢄기할 수 있게 ν•œλ‹€. 핡심 섀계 μ² ν•™: ν•˜λ‚˜μ˜ ToolRegistryλ₯Ό λͺ¨λ“  μ»΄ν¬λ„ŒνŠΈκ°€ κ³΅μœ ν•˜κ³ , λ§€ iterationλ§ˆλ‹€ μ΅œμ‹  도ꡬλ₯Ό μ‘°νšŒν•œλ‹€.


0. v1 기반 ꡬ쑰 (μœ μ§€)

v2λŠ” v1의 5-Phase νŒŒμ΄ν”„λΌμΈμ„ κ·ΈλŒ€λ‘œ μœ μ§€ν•œλ‹€. Phase μƒμ„ΈλŠ” blueprint.md μ°Έμ‘°.

Phase 1: PREPARE     μ „μ²˜λ¦¬ β€” μ›Œν¬ν”Œλ‘œμš° λ‘œλ“œ, 도ꡬ μˆ˜μ§‘, μ»¨ν…μŠ€νŠΈ 쑰립
Phase 2: RESOLVE     κ²°μ • β€” 도ꡬ 검색, μž…λ ₯ λ§€ν•‘, μŠ€ν‚€λ§ˆ κ²°μ •
Phase 3: EXECUTE     μ‹€ν–‰ β€” Thinkβ†’Actβ†’Observe 루프, 도ꡬ 호좜
Phase 4: STREAM      전달 β€” μ‹€ν–‰ 이벀트 β†’ SSE λ³€ν™˜ β†’ ν΄λΌμ΄μ–ΈνŠΈ
Phase 5: PERSIST     μ €μž₯ β€” DB, Trace, μ„Έμ…˜ μƒνƒœ, λ¦¬μ†ŒμŠ€ 정리

v2λŠ” 이 μœ„μ— 동적 도ꡬ 흐름, μƒŒλ“œλ°•μŠ€ 도ꡬ화, μ›Œν¬ν”Œλ‘œμš° μ—”μ§„, Tool Storeλ₯Ό μ–ΉλŠ”λ‹€.


1. v1μ—μ„œ 무엇이 λΆ€μ‘±ν•œκ°€

문제 1: 도ꡬ λͺ©λ‘μ΄ Phase 2μ—μ„œ κ³ μ •λœλ‹€

v1 흐름:
  Phase 2 (RESOLVE) β†’ tools_schema κ²°μ • (κ³ μ •)
  Phase 3 (EXECUTE) β†’ 이 κ³ μ •λœ λͺ©λ‘μœΌλ‘œ 계속 μ‹€ν–‰

  Iteration 1: LLM β†’ create_tool("send_slack") β†’ 생성 + Registry 등둝
  Iteration 2: LLM β†’ send_slack("#general", "hello") β†’ ❌ 도ꡬ μ—†μŒ
  β†’ tools_schemaκ°€ Phase 2μ—μ„œ 찍은 μŠ€λƒ…μƒ·μ΄λΌ μƒˆ 도ꡬ가 μ•ˆ λ³΄μž„

mantis v2 ν•΄κ²° 방식:

v2 흐름:
  Executor._resolve_tools()κ°€ λ§€ iterationλ§ˆλ‹€ 호좜됨
  β†’ registry.to_openai_tools()κ°€ ν˜„μž¬ μ‹œμ μ˜ 전체 도ꡬ λ°˜ν™˜
  β†’ create_tool둜 λ“±λ‘λœ 도ꡬ가 λ‹€μŒ iterationμ—μ„œ μ¦‰μ‹œ μ‚¬μš© κ°€λŠ₯

문제 2: μƒŒλ“œλ°•μŠ€κ°€ 인프라일 뿐 도ꡬ가 μ•„λ‹ˆλ‹€

v1:
  DockerSandbox β†’ ToolGenerator λ‚΄λΆ€μ—μ„œλ§Œ μ‚¬μš©
  Agentκ°€ "이 μ½”λ“œ 돌렀봐" ν•  수 μ—†μŒ

mantis v2:
  @tool(name="execute_code") β†’ Agentκ°€ 자유둭게 μ½”λ“œ μ‹€ν–‰
  @tool(name="execute_code_with_test") β†’ μ½”λ“œ + ν…ŒμŠ€νŠΈ ν•¨κ»˜ μ‹€ν–‰
  β†’ 일반 도ꡬ와 λ™μΌν•˜κ²Œ Registry에 등둝됨

OpenAI Codex:
  Containerλ₯Ό λ„κ΅¬μ²˜λŸΌ 던짐 β†’ μ‹€ν–‰ β†’ κ²°κ³Ό β†’ λ‹€μŒ νŒλ‹¨

문제 3: μ›Œν¬ν”Œλ‘œμš°κ°€ μΊ”λ²„μŠ€μ—μ„œλ§Œ λ§Œλ“€μ–΄μ§„λ‹€

xgen-workflow:
  async_workflow_executor.py β†’ μΊ”λ²„μŠ€ JSON의 λ…Έλ“œ/μ—£μ§€λ₯Ό 순회
  도ꡬ λͺ©λ‘ = μΊ”λ²„μŠ€ 엣지에 μ—°κ²°λœ κ²ƒλ§Œ (κ³ μ •)
  Router = 정적 λΆ„κΈ° (μ‹€ν–‰ 쀑 쑰건 λ³€κ²½ λΆˆκ°€)

  β†’ μ½”λ“œλ‘œ μ›Œν¬ν”Œλ‘œμš°λ₯Ό 쑰립할 수 μ—†μŒ
  β†’ AIκ°€ μ›Œν¬ν”Œλ‘œμš°λ₯Ό μžλ™ 생성할 수 μ—†μŒ
  β†’ μ‹€ν–‰ 쀑 동적 λΆ„κΈ° λΆˆκ°€

문제 4: 도ꡬ λͺ©λ‘μ΄ μ‹œμŠ€ν…œ 간에 연동 μ•ˆ λœλ‹€

도ꡬ 생성기              μ‹€ν–‰κΈ°                 xgen-workflow (μΊ”λ²„μŠ€)
  도ꡬλ₯Ό λ§Œλ“¦               도ꡬλ₯Ό 씀              λ…Έλ“œλ‘œ 도ꡬ μ—°κ²°
  └─ 싀행기와 뢄리됨         └─ 생성기와 뢄리        └─ μ‹€μ‹œκ°„ 동기화 μ—†μŒ

미래: xgen μ•± μ—¬λŸ¬ 개
  μ•± A (고객 상담)    μ•± B (데이터 뢄석)    μ•± C (운영 μžλ™ν™”)
  β†’ 같은 도ꡬ 풀을 κ³΅μœ ν•΄μ•Ό 함

μš”μ•½: v2μ—μ„œ ν•΄κ²°ν•  것

문제 v1 μƒνƒœ v2 λͺ©ν‘œ
도ꡬ λͺ©λ‘ κ³ μ • Phase 2μ—μ„œ μŠ€λƒ…μƒ· λ§€ iterationλ§ˆλ‹€ Registry 재쑰회
μƒŒλ“œλ°•μŠ€ ToolGenerator λ‚΄λΆ€ 인프라 Agentκ°€ ν˜ΈμΆœν•˜λŠ” 도ꡬ
도ꡬ 생성 β†’ μ¦‰μ‹œ μ‚¬μš© λΆˆκ°€ 생성 β†’ Registry 등둝 β†’ λ‹€μŒ iterationμ—μ„œ μ‚¬μš©
μ›Œν¬ν”Œλ‘œμš° μΊ”λ²„μŠ€ JSON만 μ½”λ“œ 쑰립 + μΊ”λ²„μŠ€ ν˜Έν™˜ + 동적 λΆ„κΈ°
λ©€ν‹° μ•± 단일 ν”„λ‘œμ„ΈμŠ€ 곡유 Registry λ°±μ—”λ“œ

2. 핡심 섀계 μ² ν•™ β€” 곡유 ToolRegistry

v1: Phase κ°„ 데이터 전달

Phase 2 (RESOLVE)                       Phase 3 (EXECUTE)
  tools_schema κ²°μ • ──(κ³ μ • 전달)──→    이 λͺ©λ‘μœΌλ‘œ μ‹€ν–‰

v2: ν•˜λ‚˜μ˜ Registryλ₯Ό λͺ¨λ“  μ»΄ν¬λ„ŒνŠΈκ°€ 곡유

                    ToolRegistry (ν•˜λ‚˜μ˜ μΈμŠ€ν„΄μŠ€)
                           β”‚
      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
      β”‚                    β”‚                    β”‚
  Executor             ToolGenerator        WorkflowEngine
  λ§€ iterationλ§ˆλ‹€      생성 β†’ μ¦‰μ‹œ 등둝     λ…Έλ“œλ³„ 도ꡬ 바인딩
  to_openai_tools()
  재쑰회

mantis v2의 νŒ¨ν„΄:

# ν•˜λ‚˜μ˜ registryλ₯Ό λͺ¨λ“  κ³³μ—μ„œ μ°Έμ‘°
tool_registry = ToolRegistry()
# β†’ Agent, ToolGenerator, GraphToolManager λͺ¨λ‘ 이 ν•˜λ‚˜μ˜ μΈμŠ€ν„΄μŠ€ 곡유
# β†’ create_tool이 registry에 λ“±λ‘ν•˜λ©΄ Agent의 λ‹€μŒ iterationμ—μ„œ μ¦‰μ‹œ λ³΄μž„

3. ToolRegistry ν™•μž₯

v1 ToolRegistry

μž…λ ₯: ToolSpec (name, description, parameters, fn)
좜λ ₯: OpenAI function calling μŠ€ν‚€λ§ˆ
ν•˜λŠ” 일: 등둝/쑰회/μ‹€ν–‰

v2 ToolRegistry β€” μ„Έμ…˜ μŠ€μ½”ν”„ + μ†ŒμŠ€ 좔적

μž…λ ₯: ToolSpec + source + session_id (선택)
좜λ ₯: OpenAI function calling μŠ€ν‚€λ§ˆ (κΈ€λ‘œλ²Œ + μ„Έμ…˜ 도ꡬ ν•©μ‚°)
ν•˜λŠ” 일:
  1. 도ꡬ 등둝 (κΈ€λ‘œλ²Œ λ˜λŠ” μ„Έμ…˜ μŠ€μ½”ν”„)
  2. μ†ŒμŠ€ 좔적 (builtin, generated, sandbox, mcp, openapi)
  3. μ„Έμ…˜λ³„ 독립 도ꡬ 관리
  4. μ„Έμ…˜ μ’…λ£Œ μ‹œ 정리
class ToolRegistry:
    """v2: μ„Έμ…˜ μŠ€μ½”ν”„ + μ†ŒμŠ€ 좔적.

    v1κ³Ό ν•˜μœ„ ν˜Έν™˜: session_idλ₯Ό μ•ˆ μ“°λ©΄ v1κ³Ό λ™μΌν•˜κ²Œ λ™μž‘.
    """

    _tools: dict[str, ToolSpec]                          # κΈ€λ‘œλ²Œ 도ꡬ
    _sources: dict[str, str]                              # tool_name β†’ source
    _session_tools: dict[str, dict[str, ToolSpec]]        # session_id β†’ {name: spec}

    def register(self, spec: ToolSpec, source: str = "manual", session_id: str | None = None):
        """도ꡬ 등둝.

        session_id 있으면 β†’ ν•΄λ‹Ή μ„Έμ…˜μ—μ„œλ§Œ λ³΄μ΄λŠ” 도ꡬ
        session_id μ—†μœΌλ©΄ β†’ κΈ€λ‘œλ²Œ (λͺ¨λ“  μ„Έμ…˜μ—μ„œ μ‚¬μš©)
        """
        if session_id:
            self._session_tools.setdefault(session_id, {})[spec.name] = spec
        else:
            self._tools[spec.name] = spec
        self._sources[spec.name] = source

    def to_openai_tools(self, session_id: str | None = None) -> list[dict]:
        """ν˜„μž¬ μ‹œμ μ˜ 도ꡬ λͺ©λ‘ λ°˜ν™˜.

        β˜… 핡심: ν˜ΈμΆœν•  λ•Œλ§ˆλ‹€ μ΅œμ‹  μƒνƒœλ₯Ό λ°˜ν™˜ν•œλ‹€.
        create_tool둜 λ“±λ‘λœ 도ꡬ가 μ¦‰μ‹œ 포함됨.
        """
        tools = dict(self._tools)
        if session_id and session_id in self._session_tools:
            tools.update(self._session_tools[session_id])
        return [spec.to_openai_schema() for spec in tools.values()]

    def cleanup_session(self, session_id: str):
        """μ„Έμ…˜ μ’…λ£Œ μ‹œ μ„Έμ…˜ μŠ€μ½”ν”„ 도ꡬ 정리."""
        self._session_tools.pop(session_id, None)

4. Executor λ³€κ²½ β€” λ§€ iterationλ§ˆλ‹€ 도ꡬ 재쑰회

v1 ExecutePhase

μž…λ ₯: ResolvedContext (tools_schema κ³ μ •)
β†’ λͺ¨λ“  iterationμ—μ„œ 같은 도ꡬ λͺ©λ‘ μ‚¬μš©

v2 ExecutePhase

μž…λ ₯: ResolvedContext + ToolRegistry μ°Έμ‘°
β†’ λ§€ iterationλ§ˆλ‹€ registry.to_openai_tools(session_id) 재쑰회
β†’ create_tool둜 λ§Œλ“  도ꡬ가 λ‹€μŒ iterationμ—μ„œ μ¦‰μ‹œ μ‚¬μš© κ°€λŠ₯
class ExecutePhase:
    async def run(self, ctx: ResolvedContext) -> AsyncGenerator[ExecutionEvent]:

        for iteration in range(max_iterations):
            # β˜… v2: λ§€ iterationλ§ˆλ‹€ μ΅œμ‹  도ꡬ 쑰회
            tools_schema = self._resolve_tools(
                query=ctx.context.message,
                session_id=ctx.context.session_id,
            )

            # Think
            yield ExecutionEvent("thinking", {"iteration": iteration})
            response = await self.llm.generate(
                messages=conversation.to_messages(),
                tools=tools_schema,
            )

            # μ’…λ£Œ
            if not response.tool_calls:
                yield ExecutionEvent("done", {"text": response.text})
                return

            # Act
            for tc in response.tool_calls:
                tc = self.validate(tc)

                if self.approval and self.approval.needs(tc):
                    yield ExecutionEvent("approval_required", tc.to_dict())
                    decision = await self.approval.wait()
                    if not decision.approved:
                        continue

                yield ExecutionEvent("tool_call", {"name": tc.name, "args": tc.args})
                result = await self.tool_registry.execute(tc)
                yield ExecutionEvent("tool_result", {"name": tc.name, "result": result})

                ctx.messages.add_tool_result(tc, result)

    def _resolve_tools(self, query: str, session_id: str | None) -> list[dict]:
        """λ§€ iterationλ§ˆλ‹€ μ΅œμ‹  도ꡬ λͺ©λ‘ 쑰회."""
        if self.graph_search and self.graph_search.should_use_search:
            result = self.graph_search.retrieve_as_openai_tools(query)
            if result:
                return result
        return self.tool_registry.to_openai_tools(session_id=session_id)

λ™μž‘ μ‹œλ‚˜λ¦¬μ˜€

μ‚¬μš©μž: "μŠ¬λž™ 도ꡬ λ§Œλ“€μ–΄μ„œ #general에 인사해"

Iteration 1:
  _resolve_tools() β†’ [create_tool, execute_code, ...]
  LLM β†’ create_tool(description="μŠ¬λž™ 채널에 λ©”μ‹œμ§€λ₯Ό λ³΄λ‚΄λŠ” 도ꡬ")
  ToolGenerator β†’ LLM μ½”λ“œμƒμ„± β†’ Sandbox 검증 β†’ registry.register("send_slack", session_id="s1")

Iteration 2:
  _resolve_tools(session_id="s1") β†’ [..., send_slack]  ← μ¦‰μ‹œ 포함됨
  LLM β†’ send_slack(channel="#general", text="μ•ˆλ…•ν•˜μ„Έμš”!")
  μ‹€ν–‰ β†’ 성곡

Iteration 3:
  LLM β†’ "λ©”μ‹œμ§€λ₯Ό λ³΄λƒˆμŠ΅λ‹ˆλ‹€." (done)

5. Sandbox 도ꡬ화

v1: μΈν”„λΌλ‘œμ„œμ˜ Sandbox

DockerSandbox
  └─ ToolGenerator._test_syntax()μ—μ„œ μ‚¬μš©
  └─ ToolGenerator._test_functional()μ—μ„œ μ‚¬μš©
  └─ ToolTester.smoke_test()μ—μ„œ μ‚¬μš©
  β†’ Agentκ°€ 직접 호좜 λΆˆκ°€

v2: λ„κ΅¬λ‘œμ„œμ˜ Sandbox

Sandboxλ₯Ό @tool ν•¨μˆ˜λ‘œ κ°μ‹Έμ„œ Registry에 등둝:

DockerSandbox
  β”œβ”€ 인프라: ToolGenerator, ToolTesterκ°€ λ‚΄λΆ€μ μœΌλ‘œ μ‚¬μš© (v1κ³Ό 동일)
  └─ 도ꡬ: execute_code, execute_code_with_testλ₯Ό ToolRegistry에 등둝
           β†’ Agentκ°€ 자유둭게 μ½”λ“œ μ‹€ν–‰/μ‹€ν—˜ κ°€λŠ₯
# mantis/sandbox/tools.py

def make_sandbox_tools(sandbox: DockerSandbox) -> list[ToolSpec]:
    """DockerSandboxλ₯Ό Agentκ°€ μ“Έ 수 μžˆλŠ” 도ꡬ 2개둜 λ³€ν™˜."""

    @tool(
        name="execute_code",
        description="Python μ½”λ“œλ₯Ό 격리된 Docker μ»¨ν…Œμ΄λ„ˆμ—μ„œ μ‹€ν–‰ν•œλ‹€. "
                    "데이터 뢄석, 계산, 파일 처리, API ν…ŒμŠ€νŠΈ 등에 μ‚¬μš©.",
        parameters={
            "code": {"type": "string", "description": "μ‹€ν–‰ν•  Python μ½”λ“œ"},
            "pip_packages": {
                "type": "array", "items": {"type": "string"},
                "description": "μ„€μΉ˜ν•  pip νŒ¨ν‚€μ§€ (선택)",
            },
            "timeout": {
                "type": "integer",
                "description": "νƒ€μž„μ•„μ›ƒ 초 (κΈ°λ³Έ 30, μ΅œλŒ€ 120)",
            },
        },
    )
    async def execute_code(code: str, timeout: int = 30, pip_packages: list = None) -> dict:
        result = await sandbox.execute(code, pip_packages=pip_packages, timeout=min(timeout, 120))
        return {"stdout": result.stdout, "stderr": result.stderr, "exit_code": result.exit_code}

    @tool(
        name="execute_code_with_test",
        description="Python μ½”λ“œμ™€ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό ν•¨κ»˜ μ‹€ν–‰ν•˜μ—¬ κ²€μ¦ν•œλ‹€.",
        parameters={
            "code": {"type": "string", "description": "검증할 μ½”λ“œ"},
            "test_code": {"type": "string", "description": "ν…ŒμŠ€νŠΈ μ½”λ“œ"},
        },
    )
    async def execute_code_with_test(code: str, test_code: str) -> dict:
        combined = code + "\n\n# --- Tests ---\n\n" + test_code
        result = await sandbox.execute(combined, timeout=30)
        return {
            "stdout": result.stdout, "stderr": result.stderr,
            "exit_code": result.exit_code,
            "tests_passed": "ALL_TESTS_PASSED" in result.stdout,
        }

    return [execute_code._tool_spec, execute_code_with_test._tool_spec]

Registry 등둝

sandbox = DockerSandbox()

# Sandbox 도ꡬλ₯Ό 일반 도ꡬ와 λ™μΌν•˜κ²Œ 등둝
for spec in make_sandbox_tools(sandbox):
    registry.register(spec, source="sandbox")

# 이제 AgentλŠ” execute_code, execute_code_with_testλ₯Ό 자유둭게 호좜

6. ToolGenerator μ •λΉ„

도ꡬ 생성 νŒŒμ΄ν”„λΌμΈ (6단계)

1. LLM μ½”λ“œ 생성 (temperature=0.3, ꡬ쑰화 ν”„λ‘¬ν”„νŠΈ)
   β†’ @tool λ°μ½”λ ˆμ΄ν„° 포함 Python μ½”λ“œ + ν…ŒμŠ€νŠΈ μ½”λ“œ

2. 도ꡬ 이름 μΆ”μΆœ
   β†’ regex: @tool(name="xxx")

3. Sandbox 문법 검증 (timeout=15s)
   β†’ _MOCK_PREAMBLE + code + print("SYNTAX_OK")
   β†’ DockerSandboxμ—μ„œ μ‹€ν–‰

4. Sandbox κΈ°λŠ₯ ν…ŒμŠ€νŠΈ (timeout=30s)
   β†’ _MOCK_PREAMBLE + code + test_code
   β†’ "ALL_TESTS_PASSED" in stdout

5. 파일 μ €μž₯
   β†’ tools/{tool_name}.py

6. Registry 등둝
   β†’ registry.load_from_file(path)
   β†’ λ‹€μŒ iterationμ—μ„œ μ¦‰μ‹œ μ‚¬μš© κ°€λŠ₯

mantis v2 ToolGenerator

μž…λ ₯: description (λ¬Έμžμ—΄) + session_id
좜λ ₯: ToolSpec (이미 Registry에 λ“±λ‘λœ μƒνƒœ)

ν•˜λŠ” 일:
  1. LLM μ½”λ“œ 생성
  2. 도ꡬ 이름 μΆ”μΆœ
  3. Sandbox 문법 검증
  4. Sandbox κΈ°λŠ₯ ν…ŒμŠ€νŠΈ
  5. 파일 μ €μž₯ (선택 β€” λΌμ΄λΈŒλŸ¬λ¦¬μ΄λ―€λ‘œ)
  6. Registry 등둝 (session_id 지원 μΆ”κ°€)
class ToolGenerator:
    """도ꡬ 생성 νŒŒμ΄ν”„λΌμΈ.

    핡심 λ³€κ²½ (vs v1):
    - session_id 지원 β†’ μ„Έμ…˜λ³„ 도ꡬ 격리
    - registry.register(source="generated") β†’ μ¦‰μ‹œ μ‚¬μš© κ°€λŠ₯
    - make_create_tool()둜 Agent 호좜 κ°€λŠ₯ν•œ λ„κ΅¬λ‘œ λ³€ν™˜
    """

    def __init__(
        self,
        llm: ModelClient,
        registry: ToolRegistry,
        sandbox: DockerSandbox,
        tools_dir: Path | None = None,  # None이면 파일 μ €μž₯ μ•ˆ 함
    ):
        self.llm = llm
        self.registry = registry
        self.sandbox = sandbox
        self.tools_dir = tools_dir

    async def create(self, description: str, session_id: str | None = None) -> ToolSpec:
        """도ꡬ 생성 전체 νŒŒμ΄ν”„λΌμΈ. λ°˜ν™˜ μ‹œμ μ— 이미 Registry에 등둝됨."""

        # 1. LLM μ½”λ“œ 생성
        code, test_code = await self._generate_code(description)
        tool_name = self._extract_tool_name(code)

        # 2. Sandbox 문법 검증
        syntax = await self.sandbox.execute(
            code=_MOCK_PREAMBLE + code + '\nprint("SYNTAX_OK")',
            timeout=15,
        )
        if not syntax.success or "SYNTAX_OK" not in syntax.stdout:
            raise ToolGenerationError("syntax", syntax.stderr)

        # 3. Sandbox κΈ°λŠ₯ ν…ŒμŠ€νŠΈ
        test = await self.sandbox.execute(
            code=_MOCK_PREAMBLE + code + "\n\n" + test_code,
            timeout=30,
        )
        if "ALL_TESTS_PASSED" not in test.stdout:
            raise ToolGenerationError("test", test.stderr)

        # 4. 파일 μ €μž₯ (선택)
        if self.tools_dir:
            (self.tools_dir / f"{tool_name}.py").write_text(code)

        # 5. Registry에 μ¦‰μ‹œ 등둝
        spec = self._code_to_spec(code, tool_name)
        self.registry.register(spec, source="generated", session_id=session_id)

        return spec


def make_create_tool(generator: ToolGenerator) -> ToolSpec:
    """ToolGeneratorλ₯Ό Agentκ°€ ν˜ΈμΆœν•  수 μžˆλŠ” λ„κ΅¬λ‘œ λ³€ν™˜."""

    @tool(
        name="create_tool",
        description="μƒˆλ‘œμš΄ 도ꡬλ₯Ό λ§Œλ“ λ‹€. μ„€λͺ…을 λ°›μ•„ Python μ½”λ“œλ₯Ό μƒμ„±ν•˜κ³ , "
                    "Dockerμ—μ„œ ν…ŒμŠ€νŠΈν•œ ν›„ λ“±λ‘ν•œλ‹€. 등둝 μ¦‰μ‹œ μ‚¬μš© κ°€λŠ₯.",
        parameters={
            "description": {"type": "string", "description": "λ§Œλ“€ λ„κ΅¬μ˜ κΈ°λŠ₯ μ„€λͺ…"},
        },
    )
    async def create_tool(description: str) -> dict:
        spec = await generator.create(description)
        return {"tool_name": spec.name, "status": "registered"}

    return create_tool._tool_spec

7. WorkflowEngine β€” μΊ”λ²„μŠ€ μ‹€ν–‰κΈ°λ₯Ό 라이브러리둜

xgen-workflow μ‹€ν–‰ λͺ¨λΈ 뢄석

async_workflow_executor.py 핡심 둜직:

  execution_order = topological_sort(nodes, edges)    # Kahn μ•Œκ³ λ¦¬μ¦˜
  node_outputs: dict[str, dict[str, Any]] = {}        # λ…Έλ“œλ³„ 좜λ ₯ μ €μž₯
  excluded_nodes: set[str] = set()                     # Router에 μ˜ν•΄ λΉ„ν™œμ„±

  for node_id in execution_order:
      if node_id in excluded_nodes: continue

      # μ—£μ§€μ—μ„œ μž…λ ₯ μˆ˜μ§‘
      kwargs = {}
      for edge in edges_to(node_id):
          kwargs[edge.target_port] = node_outputs[edge.source_node][edge.source_port]

      # λ…Έλ“œ μ‹€ν–‰
      result = node.execute(**kwargs)
      node_outputs[node_id] = {port_id: result}

      # Routerλ©΄ λΆ„κΈ° 처리
      if is_router(node_id):
          inactive_branches = get_inactive_branches(result)
          excluded_nodes.update(inactive_branches)

mantis WorkflowEngine

μž…λ ₯: λ…Έλ“œ + μ—£μ§€ (μΊ”λ²„μŠ€ JSON, μ½”λ“œ, λ˜λŠ” LLM 생성)
좜λ ₯: AsyncGenerator[WorkflowEvent]

ν•˜λŠ” 일:
  1. μœ„μƒ μ •λ ¬ (Kahn μ•Œκ³ λ¦¬μ¦˜)
  2. λ…Έλ“œλ³„ 순차 μ‹€ν–‰
  3. μ—£μ§€ 톡해 좜λ ₯β†’μž…λ ₯ 전달 (node_outputs)
  4. Router λ…Έλ“œμ—μ„œ 쑰건 λΆ„κΈ° (excluded_nodes)
  5. 각 단계λ₯Ό WorkflowEvent둜 yield

3κ°€μ§€ 생성 λͺ¨λ“œ:
  A. from_canvas(json)  β†’ xgen-workflow ν˜Έν™˜
  B. μ½”λ“œλ‘œ 직접 쑰립    β†’ μΊ”λ²„μŠ€ 없이 μ‚¬μš©
  C. from_llm(description) β†’ AIκ°€ μ›Œν¬ν”Œλ‘œμš° μžλ™ 생성
class WorkflowEngine:
    """xgen-workflow의 async_workflow_executorλ₯Ό 라이브러리둜 μž¬κ΅¬μ„±."""

    def __init__(self, registry: ToolRegistry):
        self.registry = registry
        self.nodes: dict[str, WorkflowNode] = {}
        self.edges: list[Edge] = []

    # ─── λͺ¨λ“œ A: μΊ”λ²„μŠ€ ν˜Έν™˜ ───

    @classmethod
    def from_canvas(cls, workflow_data: dict, registry: ToolRegistry) -> "WorkflowEngine":
        """xgen-workflow μΊ”λ²„μŠ€ JSON β†’ WorkflowEngine λ³€ν™˜.

        λ…Έλ“œ νƒ€μž… λ§€ν•‘:
          agents/xgen  β†’ AgentNode (mantis Agent μ‹€ν–‰)
          router       β†’ RouterNode (쑰건 λΆ„κΈ°)
          api_tool     β†’ ToolNode (도ꡬ 직접 호좜)
          qdrant       β†’ RAGNode (벑터 검색)
          schema_input β†’ InputNode
          end          β†’ OutputNode
        """
        engine = cls(registry)
        for node in workflow_data["nodes"]:
            engine.add_node(cls._map_node(node, registry))
        for edge in workflow_data["edges"]:
            engine.add_edge(Edge(
                source_node=edge["source"]["nodeId"],
                source_port=edge["source"]["portId"],
                target_node=edge["target"]["nodeId"],
                target_port=edge["target"]["portId"],
            ))
        return engine

    # ─── λͺ¨λ“œ B: μ½”λ“œ 쑰립 ───

    def add_node(self, node: WorkflowNode): ...
    def add_edge(self, edge: Edge): ...

    # ─── λͺ¨λ“œ C: LLM μžλ™ 생성 ───

    @classmethod
    async def from_llm(cls, description: str, llm: ModelClient, registry: ToolRegistry) -> "WorkflowEngine":
        """LLM이 μ›Œν¬ν”Œλ‘œμš°λ₯Ό μžλ™ 생성.

        μ‚¬μš© κ°€λŠ₯ν•œ 도ꡬ λͺ©λ‘μ„ LLM에 μ•Œλ €μ£Όκ³ ,
        λ…Έλ“œ/μ—£μ§€ ꡬ쑰λ₯Ό JSON으둜 λ°›μ•„μ„œ 쑰립.
        """
        available_tools = registry.list_names()
        response = await llm.generate(messages=[
            {"role": "system", "content": f"Available tools: {available_tools}\n"
                                          "Design a workflow as JSON with nodes and edges."},
            {"role": "user", "content": description},
        ])
        return cls.from_canvas(json.loads(response.text), registry)

    # ─── μ‹€ν–‰ ───

    async def run(self, input_data: dict) -> AsyncIterator[WorkflowEvent]:
        """μ›Œν¬ν”Œλ‘œμš° μ‹€ν–‰.

        xgen-workflow의 _execute_workflow_syncλ₯Ό λŒ€μ²΄.
        """
        execution_order = self._topological_sort()
        node_outputs: dict[str, dict[str, Any]] = {}
        excluded: set[str] = set()

        for node_id in execution_order:
            if node_id in excluded:
                continue

            node = self.nodes[node_id]
            inputs = self._collect_inputs(node_id, node_outputs)

            yield WorkflowEvent("node_start", {"node_id": node_id, "type": node.type})

            result = await node.execute(inputs)
            node_outputs[node_id] = result

            yield WorkflowEvent("node_complete", {"node_id": node_id, "output": result})

            if isinstance(node, RouterNode):
                inactive = self._get_inactive_branches(node_id, result)
                excluded.update(inactive)

        yield WorkflowEvent("workflow_complete", {"outputs": node_outputs})

λ…Έλ“œ νƒ€μž…

class WorkflowNode(Protocol):
    id: str
    type: str
    async def execute(self, inputs: dict) -> dict[str, Any]: ...

class AgentNode:
    """mantis Agent둜 μ‹€ν–‰. xgen-workflow의 agent_core.py λŒ€μ²΄.
    LangChain λΈ”λž™λ°•μŠ€ λŒ€μ‹  Thinkβ†’Actβ†’Observe 직접 루프."""
    type = "agent"

class RouterNode:
    """쑰건 λΆ„κΈ°. xgen-workflow의 process_router_node_output λŒ€μ²΄."""
    type = "router"

class ToolNode:
    """단일 도ꡬ 직접 μ‹€ν–‰."""
    type = "tool"

class RAGNode:
    """벑터 검색 (Qdrant λ“±)."""
    type = "rag"

class InputNode:
    """μž…λ ₯ μŠ€ν‚€λ§ˆ νŒŒμ‹±."""
    type = "input"

class OutputNode:
    """κ²°κ³Ό 좜λ ₯."""
    type = "output"

μ½”λ“œλ‘œ μ›Œν¬ν”Œλ‘œμš° 쑰립 (μΊ”λ²„μŠ€ 없이)

engine = WorkflowEngine(registry=registry)

engine.add_node(AgentNode(id="analyze", model="gpt-4o-mini", prompt="데이터 뢄석"))
engine.add_node(RouterNode(id="check", conditions={
    "good": lambda s: s.get("confidence", 0) > 0.8,
    "retry": lambda s: s.get("confidence", 0) <= 0.8,
}))
engine.add_node(AgentNode(id="report", model="gpt-4o-mini", prompt="리포트 μž‘μ„±"))

engine.add_edge(Edge("analyze", "result", "check", "input"))
engine.add_edge(Edge("check", "good", "report", "text"))
engine.add_edge(Edge("check", "retry", "analyze", "text"))  # 루프백

async for event in engine.run({"text": "맀좜 데이터 λΆ„μ„ν•΄μ€˜"}):
    print(event)

8. xgen-workflow 이식 β€” mantis둜 κ΅μ²΄ν•˜λŠ” 지점

xgen-workflow ν˜„μž¬ 계측

controller/workflow/utils/execution_core.py (695쀄)
  β”‚  μ „μ²˜λ¦¬ + 이벀트 νŒŒμ‹± + DB μ €μž₯
  β”‚
  β–Ό
editor/async_workflow_executor.py (1089쀄)
  β”‚  DAG μ›Œμ»€ (μŠ€λ ˆλ“œν’€ + queue.Queue β†’ async λ³€ν™˜)
  β”‚  node_outputs둜 λ…Έλ“œ κ°„ 데이터 전달
  β”‚
  β–Ό
editor/nodes/xgen/agent/agent_core.py (1193쀄)
  β”‚  LangChain create_agent(model, tools) β†’ λΈ”λž™λ°•μŠ€ μ‹€ν–‰
  β”‚  [AGENT_EVENT] λ¬Έμžμ—΄ νƒœκ·Έλ‘œ 이벀트 좜λ ₯
  β”‚
  β–Ό
editor/nodes/xgen/agent/agent_xgen.py (1525쀄)
     μΊ”λ²„μŠ€ Agent λ…Έλ“œ β€” tools ν¬νŠΈμ—μ„œ 도ꡬ μˆ˜μ§‘

mantis둜 κ΅μ²΄ν•˜λŠ” λ§€ν•‘

xgen-workflow (ν˜„μž¬)                         mantis (ꡐ체)
──────────────────────                       ──────────────
execution_core.py                        β†’   Phase Pipeline
  workflow_data λ‘œλ“œ                          PREPARE: μ›Œν¬ν”Œλ‘œμš° νŒŒμ‹±
  파일 선택, bypass μ „μ²˜λ¦¬                     PREPARE: μ „μ²˜λ¦¬
  [AGENT_EVENT] νƒœκ·Έ νŒŒμ‹±                      STREAM: ꡬ쑰화 이벀트 (νŒŒμ‹± λΆˆν•„μš”)
  ExecutionIO DB μ €μž₯                          PERSIST: DB μ €μž₯
  Redis μ„Έμ…˜ μ—…λ°μ΄νŠΈ                           PERSIST: μƒνƒœ μ—…λ°μ΄νŠΈ

async_workflow_executor.py               β†’   WorkflowEngine
  topological_sort()                          WorkflowEngine._topological_sort()
  node_outputs dict                           WorkflowEngine.node_outputs
  μ—£μ§€ 기반 μž…λ ₯ μˆ˜μ§‘                           WorkflowEngine._collect_inputs()
  Router λΆ„κΈ° + excluded_nodes                 RouterNode + _get_inactive_branches()
  μŠ€λ ˆλ“œν’€ + Queue β†’ async                     λ„€μ΄ν‹°λΈŒ async/await (μŠ€λ ˆλ“œ λΆˆν•„μš”)
  Generator tee / BufferedGeneratorFactory     AsyncGenerator 직접 μ‚¬μš©

agent_core.py + agent_xgen.py            β†’   AgentNode + Agent
  LangChain create_agent(tools)               mantis Agent(tool_registry)
  prepare_llm_components()                    ToolRegistry.to_openai_tools()
  kwargs['tools'] (μ—£μ§€μ—μ„œ κ³ μ •)              λ§€ iteration Registry 재쑰회
  [AGENT_EVENT] λ¬Έμžμ—΄ 좜λ ₯                    ExecutionEvent ꡬ쑰화 객체
  LangGraph λΈ”λž™λ°•μŠ€                           Thinkβ†’Actβ†’Observe 직접 루프

xgen-workflow에 mantis λΌμš°λŠ” μ½”λ“œ

# repos/xgen-workflow/editor/nodes/xgen/agent/agent_core.py
# LangChain μ½”λ“œλ₯Ό mantis둜 ꡐ체

# Before (LangChain)
from langchain.agents import create_agent
agent = create_agent(model=llm, tools=tools_list)
result = agent.invoke(user_input)

# After (mantis)
from mantis import Agent, ToolRegistry
from mantis.llm.openai_provider import ModelClient

registry = ToolRegistry()
for tool_obj in tools_list:  # μΊ”λ²„μŠ€ μ—£μ§€μ—μ„œ 온 도ꡬ
    registry.register(tool_obj)

agent = Agent(
    name=node_name,
    model_client=ModelClient(model=model_name, api_key=api_key),
    tool_registry=registry,
    system_prompt=system_prompt,
)

# 슀트리밍 β€” [AGENT_EVENT] νƒœκ·Έ νŒŒμ‹± λŒ€μ‹  ꡬ쑰화 이벀트
async for event in agent.run_stream(user_input):
    yield event  # {"type": "tool_call", "data": {...}}

execution_core.py λ‹¨μˆœν™”

# Before (695쀄 β€” μ „μ²˜λ¦¬+μ‹€ν–‰+νŒŒμ‹±+μ €μž₯ λ’€μ„žμž„)
async def run_workflow_execution(...):
    workflow_data = load_from_db(...)
    apply_file_selection(...)
    apply_bypass(...)
    executor = create_executor(workflow_data)
    async for chunk in executor.execute_streaming():
        # [AGENT_EVENT] μ •κ·œμ‹ νŒŒμ‹±
        # [AGENT_STATUS] νŒŒμ‹±
        # IO μ‚΄κ· 
        # μ—λŸ¬ μΉ˜ν™˜
        yield sse_format(chunk)
    save_execution_io(...)
    update_redis(...)

# After (mantis Phase νŒŒμ΄ν”„λΌμΈ)
from mantis.workflow import WorkflowEngine
from mantis.pipeline import build_pipeline

async def run_workflow_execution(workflow_data: dict, user_input: str):
    engine = WorkflowEngine.from_canvas(workflow_data, registry)
    async for event in engine.run({"text": user_input}):
        yield event.to_sse()
    # PERSIST Phaseκ°€ DB μ €μž₯ + μƒνƒœ μ—…λ°μ΄νŠΈ μžλ™ 처리

도ꡬ 전달 방식 λ³€ν™”

Before (μΊ”λ²„μŠ€ μ—£μ§€ κ³ μ •):
  [API Tool] ──엣지──→ Agent.tools 포트 β†’ kwargs['tools'] = [tool1, tool2]
  β†’ μ‹€ν–‰ 쀑 도ꡬ μΆ”κ°€ λΆˆκ°€
  β†’ 도ꡬ 30개 μ „λΆ€ LLM에 전달

After (mantis ToolRegistry):
  [API Tool] ──엣지──→ AgentNode β†’ registry.register(tool)
  β†’ registry.to_openai_tools()둜 λ§€ iteration μ΅œμ‹  쑰회
  β†’ GraphToolManager둜 κ΄€λ ¨ λ„κ΅¬λ§Œ 검색
  β†’ create_tool둜 μ‹€ν–‰ 쀑 도ꡬ μΆ”κ°€ κ°€λŠ₯

슀트리밍 λͺ¨λΈ λ³€ν™”

Before (μŠ€λ ˆλ“œ + 큐 + λ¬Έμžμ—΄ νƒœκ·Έ):
  _execute_workflow_sync()     ← 동기, μŠ€λ ˆλ“œν’€μ—μ„œ μ‹€ν–‰
    β†’ self._streaming_queue.put(('data', chunk))
    β†’ self._streaming_queue.put(('agent_event', {...}))
  execute_workflow_async_streaming()  ← 비동기, 큐 폴링
    β†’ queue.get(timeout=0.02)
  execution_core.py
    β†’ [AGENT_EVENT]{json}[/AGENT_EVENT] μ •κ·œμ‹ νŒŒμ‹±

After (λ„€μ΄ν‹°λΈŒ async + ꡬ쑰화 이벀트):
  WorkflowEngine.run()        ← λ„€μ΄ν‹°λΈŒ async
    β†’ yield WorkflowEvent("node_start", {...})
    β†’ AgentNode β†’ yield ExecutionEvent("tool_call", {...})
    β†’ yield WorkflowEvent("node_complete", {...})
  β†’ λ¬Έμžμ—΄ νŒŒμ‹± λΆˆν•„μš”, μŠ€λ ˆλ“œ λΆˆν•„μš”

9. 도ꡬ λͺ©λ‘ 전체 흐름

v2의 핡심: ν•˜λ‚˜μ˜ ToolRegistryλ₯Ό 톡해 λͺ¨λ“  κ³³μ—μ„œ 동기화.

도ꡬ μ†ŒμŠ€                    ToolRegistry              μ†ŒλΉ„μž
──────────                  ──────────────             ──────
@tool λ°μ½”λ ˆμ΄ν„°     ──register──→                  ──→ Executor (λ§€ iter 쑰회)
load_from_directory  ──register──→                  ──→ GraphToolManager (검색)
make_sandbox_tools() ──register──→  to_openai_tools ──→ LLM (function calling)
make_create_tool()   ──register──→  (session_id)    ──→ WorkflowEngine (λ…Έλ“œ 바인딩)
MCP Bridge           ──register──→
OpenAPI Loader       ──register──→

도ꡬ 검증:
  @tool 등둝 μ‹œ         β†’ ToolTester.smoke_test()
  create_tool 생성 μ‹œ   β†’ Sandbox 문법+κΈ°λŠ₯ ν…ŒμŠ€νŠΈ
  MCP/OpenAPI 등둝 μ‹œ   β†’ ToolTester.validate_schema()
  μ‹€νŒ¨ β†’ 등둝 κ±°λΆ€      β†’ κΉ¨μ§„ 도ꡬ가 LLM에 전달 μ•ˆ 됨

10. λ©€ν‹° μ•± 배포

단일 μ•± (κΈ°λ³Έ)

registry = ToolRegistry()
agent = Agent(name="bot", ..., tool_registry=registry)

λ©€ν‹° μ•± β€” 곡유 Registry λ°±μ—”λ“œ

class ToolRegistryBackend(Protocol):
    """μ €μž₯μ†Œ λ°±μ—”λ“œ β€” ꡐ체 κ°€λŠ₯."""
    async def list_tools(self, session_id: str | None) -> list[ToolSpec]: ...
    async def register(self, spec: ToolSpec, source: str, session_id: str | None): ...

class InMemoryBackend:   # 단일 ν”„λ‘œμ„ΈμŠ€ (κΈ°λ³Έκ°’)
class RedisBackend:      # λ©€ν‹° μ•± 곡유 (Redis pub/sub둜 λ³€κ²½ μ•Œλ¦Ό)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     Shared ToolRegistry (Redis)       β”‚
β”‚  builtin + MCP + generated            β”‚
β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
       β”‚          β”‚          β”‚
  xgen-app-1  xgen-app-2  xgen-app-3
  (고객 상담)  (데이터 뢄석) (운영 μžλ™ν™”)

11. Tool Store β€” 도ꡬ μ €μž₯μ†Œ/λ ˆμ§€μŠ€νŠΈλ¦¬

문제

ν˜„μž¬:
  ToolGenerator β†’ 도ꡬ 생성 β†’ 둜컬 Registryμ—λ§Œ 등둝
  β†’ μ„Έμ…˜ λλ‚˜λ©΄ 사라짐
  β†’ λ‹€λ₯Έ μ‚¬λžŒ/앱이 κ°€μ Έλ‹€ μ“Έ 방법 μ—†μŒ
  β†’ 검증 없이 등둝됨 (Sandbox ν…ŒμŠ€νŠΈλ§Œ)

ν•„μš”ν•œ 것:
  μƒμ„±λœ 도ꡬλ₯Ό μ–΄λ”˜κ°€μ— μ €μž₯ β†’ 검증 β†’ 곡개 β†’ λ‹€λ₯Έ κ³³μ—μ„œ κ°€μ Έλ‹€ 씀
  npm/PyPI 같은 도ꡬ μ „μš© λ ˆμ§€μŠ€νŠΈλ¦¬

κ°œλ…: Tool Store

도ꡬ 생λͺ…μ£ΌκΈ°:

  생성 β†’ 검증 β†’ κ²Œμ‹œ β†’ 검색 β†’ μ„€μΉ˜ β†’ μ‚¬μš©
   β”‚       β”‚       β”‚       β”‚       β”‚       β”‚
   β”‚       β”‚       β”‚       β”‚       β”‚       └─ ToolRegistry.execute()
   β”‚       β”‚       β”‚       β”‚       └─ mantis install <tool-name>
   β”‚       β”‚       β”‚       └─ mantis search "μŠ¬λž™ λ©”μ‹œμ§€"
   β”‚       β”‚       └─ mantis publish <tool-name>
   β”‚       └─ μžλ™ 검증 νŒŒμ΄ν”„λΌμΈ (Sandbox ν…ŒμŠ€νŠΈ + μŠ€ν‚€λ§ˆ 검증 + λ³΄μ•ˆ μŠ€μΊ”)
   └─ ToolGenerator.create() λ˜λŠ” μˆ˜λ™ μž‘μ„±

μ €μž₯μ†Œ ꡬ쑰

도ꡬ = Python 파일 + 메타데이터
β†’ Git μ €μž₯μ†Œμ— μžμ—°μŠ€λŸ½κ²Œ 맞음 (μ½”λ“œλ‹ˆκΉŒ)

tool-store/
β”œβ”€β”€ tools/
β”‚   β”œβ”€β”€ send_slack_message/
β”‚   β”‚   β”œβ”€β”€ tool.py              ← @tool λ°μ½”λ ˆμ΄ν„° μ½”λ“œ
β”‚   β”‚   β”œβ”€β”€ test_tool.py         ← ν…ŒμŠ€νŠΈ μ½”λ“œ
β”‚   β”‚   β”œβ”€β”€ manifest.json        ← 메타데이터 (이름, μ„€λͺ…, 버전, μ˜μ‘΄μ„±, μž‘μ„±μž)
β”‚   β”‚   └── README.md
β”‚   β”œβ”€β”€ fetch_weather/
β”‚   β”‚   β”œβ”€β”€ tool.py
β”‚   β”‚   β”œβ”€β”€ test_tool.py
β”‚   β”‚   └── manifest.json
β”‚   └── ...
β”œβ”€β”€ categories.json              ← μΉ΄ν…Œκ³ λ¦¬ λΆ„λ₯˜
└── verified.json                ← 검증 ν†΅κ³Όν•œ 도ꡬ λͺ©λ‘

manifest.json

{
  "name": "send_slack_message",
  "version": "1.0.0",
  "description": "μŠ¬λž™ 채널에 λ©”μ‹œμ§€λ₯Ό λ³΄λ‚΄λŠ” 도ꡬ",
  "author": "jinsoo",
  "category": "communication",
  "tags": ["slack", "message", "notification"],
  "parameters": {
    "channel": {"type": "string", "description": "μŠ¬λž™ 채널"},
    "text": {"type": "string", "description": "λ©”μ‹œμ§€ λ‚΄μš©"}
  },
  "dependencies": ["httpx"],
  "verified": true,
  "verification": {
    "sandbox_test": "passed",
    "schema_validation": "passed",
    "security_scan": "passed",
    "verified_at": "2026-03-19T10:00:00Z"
  }
}

검증 νŒŒμ΄ν”„λΌμΈ

도ꡬ κ²Œμ‹œ μš”μ²­ (mantis publish)
  β”‚
  β–Ό
Step 1: μŠ€ν‚€λ§ˆ 검증
  β†’ manifest.json ν•„μˆ˜ ν•„λ“œ 확인
  β†’ parameters νƒ€μž…/description 검증
  β†’ @tool λ°μ½”λ ˆμ΄ν„° 쑴재 확인
  β”‚
  β–Ό
Step 2: Sandbox ν…ŒμŠ€νŠΈ
  β†’ Docker μ»¨ν…Œμ΄λ„ˆμ—μ„œ test_tool.py μ‹€ν–‰
  β†’ "ALL_TESTS_PASSED" 확인
  β†’ νƒ€μž„μ•„μ›ƒ, λ©”λͺ¨λ¦¬ μ œν•œ 확인
  β”‚
  β–Ό
Step 3: λ³΄μ•ˆ μŠ€μΊ”
  β†’ μœ„ν—˜ν•œ import 감지 (os.system, subprocess, eval)
  β†’ λ„€νŠΈμ›Œν¬ μ ‘κ·Ό νŒ¨ν„΄ 확인
  β†’ 파일 μ‹œμŠ€ν…œ μ ‘κ·Ό λ²”μœ„ 확인
  β”‚
  β–Ό
Step 4: 등둝
  β†’ Git μ €μž₯μ†Œμ— 컀밋 (λ˜λŠ” API μ„œλ²„μ— μ—…λ‘œλ“œ)
  β†’ verified.json μ—…λ°μ΄νŠΈ
  β†’ 검색 인덱슀 κ°±μ‹ 

ToolStore 클래슀

class ToolStore:
    """도ꡬ μ €μž₯μ†Œ β€” 도ꡬλ₯Ό κ²Œμ‹œν•˜κ³  κ°€μ Έμ˜€λŠ” λ ˆμ§€μŠ€νŠΈλ¦¬.

    μ €μž₯μ†Œ λ°±μ—”λ“œ:
    - GitBackend: Git μ €μž₯μ†Œ (GitHub, GitLab λ“±)
    - APIBackend: REST API μ„œλ²„ (자체 ν˜ΈμŠ€νŒ…)
    """

    def __init__(self, backend: StoreBackend):
        self.backend = backend

    async def publish(self, tool_dir: Path, registry: ToolRegistry) -> PublishResult:
        """도ꡬλ₯Ό μ €μž₯μ†Œμ— κ²Œμ‹œ.

        1. manifest.json λ‘œλ“œ + 검증
        2. Sandbox ν…ŒμŠ€νŠΈ μ‹€ν–‰
        3. λ³΄μ•ˆ μŠ€μΊ”
        4. μ €μž₯μ†Œμ— μ—…λ‘œλ“œ
        """
        manifest = load_manifest(tool_dir / "manifest.json")
        code = (tool_dir / "tool.py").read_text()
        test_code = (tool_dir / "test_tool.py").read_text()

        # 검증
        verification = await self._verify(code, test_code, manifest)
        if not verification.passed:
            return PublishResult(success=False, errors=verification.errors)

        # κ²Œμ‹œ
        await self.backend.upload(manifest["name"], tool_dir)
        return PublishResult(success=True, name=manifest["name"], version=manifest["version"])

    async def install(self, tool_name: str, registry: ToolRegistry) -> ToolSpec:
        """μ €μž₯μ†Œμ—μ„œ 도ꡬλ₯Ό 가져와 ToolRegistry에 등둝."""
        tool_dir = await self.backend.download(tool_name)
        spec = registry.load_from_file(tool_dir / "tool.py")
        return spec

    async def search(self, query: str, category: str | None = None) -> list[ToolManifest]:
        """도ꡬ 검색."""
        return await self.backend.search(query, category=category)

    async def list_categories(self) -> list[str]:
        """μΉ΄ν…Œκ³ λ¦¬ λͺ©λ‘."""
        return await self.backend.list_categories()


class GitStoreBackend:
    """Git μ €μž₯μ†Œ 기반 λ°±μ—”λ“œ.

    GitHub/GitLab 레포λ₯Ό 도ꡬ μ €μž₯μ†Œλ‘œ μ‚¬μš©.
    κ²Œμ‹œ = PR 생성, 검증 = CI νŒŒμ΄ν”„λΌμΈ, μ„€μΉ˜ = git clone.
    """

    def __init__(self, repo_url: str, branch: str = "main"):
        self.repo_url = repo_url
        self.branch = branch

    async def upload(self, tool_name: str, tool_dir: Path):
        """PR μƒμ„±μœΌλ‘œ 도ꡬ κ²Œμ‹œ."""
        # git clone β†’ 도ꡬ 볡사 β†’ commit β†’ push β†’ PR 생성
        ...

    async def download(self, tool_name: str) -> Path:
        """νŠΉμ • 도ꡬ λ””λ ‰ν† λ¦¬λ§Œ λ‹€μš΄λ‘œλ“œ."""
        # sparse checkout으둜 tools/{tool_name}/ 만 κ°€μ Έμ˜΄
        ...

    async def search(self, query: str, **kwargs) -> list[ToolManifest]:
        """manifest.json 기반 검색."""
        ...


class APIStoreBackend:
    """REST API 기반 λ°±μ—”λ“œ.

    자체 ν˜ΈμŠ€νŒ… 도ꡬ λ ˆμ§€μŠ€νŠΈλ¦¬ μ„œλ²„.
    """

    def __init__(self, base_url: str, api_key: str | None = None):
        self.base_url = base_url
        self.api_key = api_key

    async def upload(self, tool_name: str, tool_dir: Path): ...
    async def download(self, tool_name: str) -> Path: ...
    async def search(self, query: str, **kwargs) -> list[ToolManifest]: ...

CLI μ‚¬μš©λ²•

# 도ꡬ 검색
mantis store search "μŠ¬λž™ λ©”μ‹œμ§€"
mantis store search --category communication

# 도ꡬ μ„€μΉ˜
mantis store install send_slack_message

# 도ꡬ κ²Œμ‹œ
mantis store publish ./my_tools/send_slack_message/

# 도ꡬ λͺ©λ‘
mantis store list
mantis store list --category database

# μ €μž₯μ†Œ μ„€μ •
mantis store config --backend git --repo https://github.com/myorg/tool-store.git
mantis store config --backend api --url https://tools.mycompany.com

ToolGenerator β†’ Tool Store 연동

# 도ꡬ 생성 ν›„ μžλ™μœΌλ‘œ Store에 κ²Œμ‹œν•˜λŠ” μ˜΅μ…˜
generator = ToolGenerator(
    llm=llm,
    registry=registry,
    sandbox=sandbox,
    store=ToolStore(GitStoreBackend("https://github.com/myorg/tool-store.git")),
    auto_publish=True,  # 생성 + 검증 톡과 μ‹œ μžλ™ κ²Œμ‹œ
)

# λ˜λŠ” μˆ˜λ™ κ²Œμ‹œ
spec = await generator.create("μŠ¬λž™ λ©”μ‹œμ§€ λ³΄λ‚΄λŠ” 도ꡬ")
await store.publish(spec.source_dir, registry)

awesome-mcp-servers μŠ€νƒ€μΌ μΉ΄ν…Œκ³ λ¦¬

μΉ΄ν…Œκ³ λ¦¬ μ˜ˆμ‹œ:
  communication/    β†’ μŠ¬λž™, 이메일, Teams, Discord
  database/         β†’ SQL 쿼리, MongoDB, Redis
  file/             β†’ 파일 읽기/μ“°κΈ°, CSV νŒŒμ‹±, PDF λ³€ν™˜
  api/              β†’ REST API 호좜, GraphQL, webhook
  analysis/         β†’ 데이터 뢄석, 톡계, μ‹œκ°ν™”
  search/           β†’ μ›Ή 검색, 벑터 검색, λ¬Έμ„œ 검색
  automation/       β†’ μŠ€μΌ€μ€„λ§, 크둀링, μ•Œλ¦Ό
  cloud/            β†’ AWS, GCP, Azure 연동
  dev_tools/        β†’ Git, CI/CD, μ½”λ“œ 리뷰
  custom/           β†’ μ‚¬μš©μž μ •μ˜

전체 흐름

ToolGenerator                    Tool Store                     λ‹€λ₯Έ μ•±/μ‚¬μš©μž
  β”‚                                β”‚                                β”‚
  β”‚  create("μŠ¬λž™ 도ꡬ")           β”‚                                β”‚
  β”‚  β†’ LLM μ½”λ“œμƒμ„±               β”‚                                β”‚
  β”‚  β†’ Sandbox 검증               β”‚                                β”‚
  β”‚  β†’ ToolRegistry 등둝           β”‚                                β”‚
  β”‚                                β”‚                                β”‚
  β”‚  publish(tool_dir)  ─────────→ β”‚  검증 νŒŒμ΄ν”„λΌμΈ               β”‚
  β”‚                                β”‚  β†’ μŠ€ν‚€λ§ˆ 검증                 β”‚
  β”‚                                β”‚  β†’ Sandbox ν…ŒμŠ€νŠΈ              β”‚
  β”‚                                β”‚  β†’ λ³΄μ•ˆ μŠ€μΊ”                   β”‚
  β”‚                                β”‚  β†’ Git μ €μž₯μ†Œμ— 컀밋           β”‚
  β”‚                                β”‚                                β”‚
  β”‚                                β”‚  ←──── search("μŠ¬λž™")          β”‚
  β”‚                                β”‚  ─────→ [send_slack_message]   β”‚
  β”‚                                β”‚                                β”‚
  β”‚                                β”‚  ←──── install("send_slack")   β”‚
  β”‚                                β”‚  ─────→ tool.py λ‹€μš΄λ‘œλ“œ       β”‚
  β”‚                                β”‚         ToolRegistry 등둝      β”‚

12. v2 νŒ¨ν‚€μ§€ ꡬ쑰

v1 λŒ€λΉ„ μΆ”κ°€/λ³€κ²½ λΆ€λΆ„:

mantis/
β”œβ”€β”€ __init__.py
β”œβ”€β”€ __main__.py
β”‚
β”œβ”€β”€ tools/
β”‚   β”œβ”€β”€ decorator.py          ← v1 κ·ΈλŒ€λ‘œ
β”‚   └── registry.py           ← v2: session_id, source μΆ”κ°€
β”‚
β”œβ”€β”€ engine/
β”‚   └── runner.py             ← v2: λ§€ iteration Registry 재쑰회
β”‚
β”œβ”€β”€ sandbox/
β”‚   β”œβ”€β”€ sandbox.py            ← v1 κ·ΈλŒ€λ‘œ
β”‚   β”œβ”€β”€ runner.py             ← v1 κ·ΈλŒ€λ‘œ
β”‚   └── tools.py              ← β˜… v2 μ‹ κ·œ: make_sandbox_tools()
β”‚
β”œβ”€β”€ generate/
β”‚   └── tool_generator.py     ← v2: session_id 지원 + make_create_tool()
β”‚
β”œβ”€β”€ workflow/                  ← β˜… v2 μ‹ κ·œ νŒ¨ν‚€μ§€
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ engine.py             ← WorkflowEngine (from_canvas, from_code, from_llm)
β”‚   β”œβ”€β”€ nodes.py              ← AgentNode, RouterNode, ToolNode, RAGNode
β”‚   └── models.py             ← WorkflowNode, Edge, WorkflowEvent
β”‚
β”œβ”€β”€ store/                     ← β˜… v2 μ‹ κ·œ νŒ¨ν‚€μ§€
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ store.py              ← ToolStore (publish, install, search)
β”‚   β”œβ”€β”€ backends.py           ← GitStoreBackend, APIStoreBackend
β”‚   β”œβ”€β”€ manifest.py           ← ToolManifest νŒŒμ‹±/검증
β”‚   └── verify.py             ← 검증 νŒŒμ΄ν”„λΌμΈ (μŠ€ν‚€λ§ˆ+Sandbox+λ³΄μ•ˆ)
β”‚
β”œβ”€β”€ pipeline/                 ← v1 κ·ΈλŒ€λ‘œ (ν•˜μœ„ ν˜Έν™˜)
β”œβ”€β”€ llm/                      ← v1 κ·ΈλŒ€λ‘œ
β”œβ”€β”€ context/                  ← v1 κ·ΈλŒ€λ‘œ
β”œβ”€β”€ safety/                   ← v1 κ·ΈλŒ€λ‘œ
β”œβ”€β”€ search/                   ← v1 κ·ΈλŒ€λ‘œ
β”œβ”€β”€ state/                    ← v1 κ·ΈλŒ€λ‘œ
β”œβ”€β”€ trace/                    ← v1 κ·ΈλŒ€λ‘œ
β”œβ”€β”€ testing/                  ← v1 κ·ΈλŒ€λ‘œ
└── adapters/                 ← v1 κ·ΈλŒ€λ‘œ

12. μ‚¬μš© 이미지

κ°€μž₯ κ°„λ‹¨ν•œ μ‚¬μš©λ²• (v1κ³Ό 동일)

from mantis import Agent, tool, ToolRegistry
from mantis.llm.openai_provider import ModelClient

@tool(name="greet", description="μΈμ‚¬ν•œλ‹€")
async def greet(name: str) -> dict:
    return {"message": f"μ•ˆλ…• {name}"}

registry = ToolRegistry()
registry.register(greet._tool_spec)

agent = Agent(
    name="bot",
    model_client=ModelClient(model="gpt-4o-mini", api_key="sk-..."),
    tool_registry=registry,
)
result = await agent.run("μΈμ‚¬ν•΄μ€˜")

v2 ν’€μ˜΅μ…˜ β€” 도ꡬ 생성 + μƒŒλ“œλ°•μŠ€ + μ›Œν¬ν”Œλ‘œμš°

from mantis import Agent, tool, ToolRegistry
from mantis.llm.openai_provider import ModelClient
from mantis.sandbox.sandbox import DockerSandbox
from mantis.sandbox.tools import make_sandbox_tools
from mantis.generate.tool_generator import ToolGenerator, make_create_tool
from mantis.search.graph_search import GraphToolManager

# 1. 곡유 Registry
registry = ToolRegistry()

# 2. μ‚¬μš©μž 도ꡬ
@tool(name="lookup_order", description="주문 쑰회")
async def lookup_order(order_id: str) -> dict:
    return {"order_id": order_id, "status": "shipped"}
registry.register(lookup_order._tool_spec, source="builtin")

# 3. Sandbox 도ꡬ 등둝 β†’ Agentκ°€ μ½”λ“œ μ‹€ν–‰ κ°€λŠ₯
sandbox = DockerSandbox()
for spec in make_sandbox_tools(sandbox):
    registry.register(spec, source="sandbox")

# 4. ToolGenerator + create_tool β†’ Agentκ°€ 도ꡬ 생성 κ°€λŠ₯
llm = ModelClient(model="gpt-4o-mini", api_key="sk-...")
generator = ToolGenerator(llm=llm, registry=registry, sandbox=sandbox)
registry.register(make_create_tool(generator), source="builtin")

# 5. Graph 검색
graph = GraphToolManager()
graph.ingest_from_registry(registry)

# 6. Agent 생성 β€” 도ꡬ ν˜„ν™©:
#   - lookup_order (builtin)
#   - execute_code (sandbox)
#   - execute_code_with_test (sandbox)
#   - create_tool (builtin)
agent = Agent(
    name="full-agent",
    model_client=llm,
    tool_registry=registry,
    graph_tool_manager=graph,
    approval_patterns=["DELETE *"],
)

# Agentκ°€ 도ꡬ λ§Œλ“€κ³ , μ½”λ“œ 돌리고, μ¦‰μ‹œ μ‚¬μš© κ°€λŠ₯
async for event in agent.run_stream("μŠ¬λž™ 도ꡬ λ§Œλ“€μ–΄μ„œ λ©”μ‹œμ§€ 보내"):
    print(event)

μ›Œν¬ν”Œλ‘œμš° μ‚¬μš©

from mantis.workflow import WorkflowEngine

# μΊ”λ²„μŠ€ JSONμ—μ„œ (xgen-workflow ν˜Έν™˜)
engine = WorkflowEngine.from_canvas(workflow_data, registry)

# λ˜λŠ” μ½”λ“œλ‘œ 직접
engine = WorkflowEngine(registry)
engine.add_node(AgentNode(id="agent1", model="gpt-4o-mini"))
engine.add_edge(Edge("input", "result", "agent1", "text"))

async for event in engine.run({"text": "λΆ„μ„ν•΄μ€˜"}):
    print(event)

13. v1 λŒ€λΉ„ κ°œμ„ μ 

v1 v2
도ꡬ λͺ©λ‘ Phase 2 μŠ€λƒ…μƒ· (κ³ μ •) λ§€ iteration μ΅œμ‹  쑰회 (라이브)
도ꡬ 생성 β†’ μ‚¬μš© λ‹€μŒ μ‹€ν–‰μ—μ„œμ•Ό κ°€λŠ₯ 같은 λŒ€ν™”μ—μ„œ μ¦‰μ‹œ μ‚¬μš©
μƒŒλ“œλ°•μŠ€ ToolGenerator λ‚΄λΆ€ 인프라 Agentκ°€ ν˜ΈμΆœν•˜λŠ” 도ꡬ
μ›Œν¬ν”Œλ‘œμš° μΊ”λ²„μŠ€ JSON만 지원 μΊ”λ²„μŠ€ + μ½”λ“œ + LLM 생성
μ„Έμ…˜ 격리 μ—†μŒ session_id별 도ꡬ 관리
도ꡬ μ†ŒμŠ€ 좔적 μ—†μŒ builtin/generated/sandbox/mcp/openapi
λ©€ν‹° μ•± 단일 ν”„λ‘œμ„ΈμŠ€ Redis λ°±μ—”λ“œλ‘œ 곡유 κ°€λŠ₯
ν•˜μœ„ ν˜Έν™˜ β€” v1 μ½”λ“œ κ·ΈλŒ€λ‘œ λ™μž‘

14. μ‹€ν–‰ κ³„νš

Phase A: ToolRegistry ν™•μž₯ + Executor 동적 쑰회

  • ToolRegistry에 session_id, source νŒŒλΌλ―Έν„° μΆ”κ°€
  • to_openai_tools(session_id) κΈ€λ‘œλ²Œ+μ„Έμ…˜ ν•©μ‚°
  • ExecutePhase λ§€ iterationλ§ˆλ‹€ Registry 재쑰회
  • cleanup_session() μΆ”κ°€
  • 효과: create_tool β†’ μ¦‰μ‹œ μ‚¬μš© ν•΄κ²°

Phase B: Sandbox 도ꡬ화

  • mantis/sandbox/tools.py μ‹ κ·œ (make_sandbox_tools)
  • execute_code, execute_code_with_test @tool ν•¨μˆ˜
  • 효과: Agentκ°€ μ½”λ“œ μ‹€ν–‰/μ‹€ν—˜ κ°€λŠ₯

Phase C: ToolGenerator μ •λΉ„

  • session_id 전달 지원
  • make_create_tool() 헬퍼
  • _MOCK_PREAMBLE mantis νŒ¨ν‚€μ§€μš© κ°±μ‹ 
  • 효과: 도ꡬ μƒμ„±β†’κ²€μ¦β†’λ“±λ‘β†’μ¦‰μ‹œ μ‚¬μš© 전체 νŒŒμ΄ν”„λΌμΈ

Phase D: WorkflowEngine

  • mantis/workflow/ νŒ¨ν‚€μ§€ μ‹ κ·œ
  • WorkflowEngine, WorkflowNode, Edge κ΅¬ν˜„
  • from_canvas() μΊ”λ²„μŠ€ ν˜Έν™˜
  • AgentNode, RouterNode, ToolNode κ΅¬ν˜„
  • from_llm() LLM μ›Œν¬ν”Œλ‘œμš° μžλ™ 생성
  • 효과: xgen-workflow μ‹€ν–‰κΈ° λŒ€μ²΄

Phase E: λ©€ν‹° μ•±

  • ToolRegistryBackend Protocol
  • RedisBackend κ΅¬ν˜„
  • 효과: xgen μ•± κ°„ 도ꡬ 곡유

Phase F: Tool Store

  • ToolStore, GitStoreBackend, APIStoreBackend κ΅¬ν˜„
  • 검증 νŒŒμ΄ν”„λΌμΈ (μŠ€ν‚€λ§ˆ + Sandbox + λ³΄μ•ˆ μŠ€μΊ”)
  • manifest.json 포맷 μ •μ˜
  • CLI: mantis store search/install/publish
  • ToolGenerator β†’ Store μžλ™ κ²Œμ‹œ μ˜΅μ…˜
  • 효과: 도ꡬλ₯Ό μ €μž₯μ†Œμ— κ²Œμ‹œν•˜κ³  λ‹€λ₯Έ μ•±/νŒ€μ΄ κ°€μ Έλ‹€ 씀

15. ν•œ μž₯ κ·Έλ¦Ό

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                          Mantis v2 (라이브러리)                         β”‚
β”‚                                                                        β”‚
β”‚  pip install mantis                                                    β”‚
β”‚  from mantis import Agent, tool                                        β”‚
β”‚                                                                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚               ToolRegistry (ν•˜λ‚˜μ˜ μΈμŠ€ν„΄μŠ€, 라이브)               β”‚ β”‚
β”‚  β”‚                                                                   β”‚ β”‚
β”‚  β”‚  builtin    sandbox       generated     MCP      OpenAPI         β”‚ β”‚
β”‚  β”‚  @tool()    execute_code  create_tool   mcp:     api_to_tool    β”‚ β”‚
β”‚  β”‚             execute_test  β†’ μ¦‰μ‹œ 등둝   slack                    β”‚ β”‚
β”‚  β”‚                                                                   β”‚ β”‚
β”‚  β”‚  to_openai_tools(session_id) β†’ λ§€ iteration μ΅œμ‹  도ꡬ λ°˜ν™˜       β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                   β”‚                  β”‚               β”‚                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚  β”‚    Executor       β”‚  β”‚ ToolGenerator β”‚  β”‚ WorkflowEngine   β”‚     β”‚
β”‚  β”‚                   β”‚  β”‚               β”‚  β”‚                  β”‚     β”‚
β”‚  β”‚  Thinkβ†’Act        β”‚  β”‚ LLM μ½”λ“œμƒμ„±  β”‚  β”‚ from_canvas()    β”‚     β”‚
β”‚  β”‚  β†’Observe         β”‚  β”‚ Sandbox 검증  β”‚  β”‚ μ½”λ“œ 쑰립        β”‚     β”‚
β”‚  β”‚                   β”‚  β”‚ Registry 등둝 β”‚  β”‚ from_llm()       β”‚     β”‚
β”‚  β”‚  λ§€ iteration     β”‚  β”‚ β†’ μ¦‰μ‹œ μ‚¬μš©   β”‚  β”‚ 쑰건 λΆ„κΈ°        β”‚     β”‚
β”‚  β”‚  도ꡬ 재쑰회      β”‚  β”‚               β”‚  β”‚ 루프백           β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
β”‚                                                                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚           DockerSandbox (인프라 + 도ꡬ)                            β”‚ β”‚
β”‚  β”‚  인프라: ToolGenerator 검증, ToolTester smoke_test                 β”‚ β”‚
β”‚  β”‚  도ꡬ: execute_code, execute_code_with_test β†’ Agentκ°€ 호좜        β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                                                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚              Phase Pipeline (v1 ν˜Έν™˜)                              β”‚ β”‚
β”‚  β”‚  PREPARE β†’ RESOLVE β†’ EXECUTE β†’ STREAM β†’ PERSIST                  β”‚ β”‚
β”‚  β”‚                        ↑ ToolRegistry.to_openai_tools() λ§€ iter  β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                                                        β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚              Adapters (이식 λ ˆμ΄μ–΄)                                 β”‚ β”‚
β”‚  β”‚  WorkflowAdapter β†’ μΊ”λ²„μŠ€ JSON을 Phase νŒŒμ΄ν”„λΌμΈμœΌλ‘œ λ³€ν™˜        β”‚ β”‚
β”‚  β”‚  SSEAdapter      β†’ StreamEvent β†’ SSE 포맷                        β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β–Ό                 β–Ό                 β–Ό
       xgen-workflow        xgen3.0            μƒˆ ν”„λ‘œμ νŠΈ
       (μΊ”λ²„μŠ€ μœ μ§€,       (기쑴처럼           (pip install
        μ‹€ν–‰κΈ°λ§Œ ꡐ체)      μ‚¬μš©)              ν•΄μ„œ λ°”λ‘œ)