k
korAI
고급 전체
🔥 고급2026-05-056~8분

멀티 에이전트 환경에서 안전한 Tool 실행 아키텍처

에이전트가 에이전트를 호출하는 계층 구조에서 tool 실행 권한을 잘못 설계하면 권한 에스컬레이션과 무한 루프가 발생한다. 샌드박스 격리와 실행 예산 패턴으로 이를 방지한다.

multi-agenttool-usesafety

멀티 에이전트 Tool 실행의 핵심 위협

오케스트레이터가 서브에이전트에게 tool을 위임할 때 세 가지 위협이 존재한다:

  1. 권한 에스컬레이션: 서브에이전트가 오케스트레이터 권한을 상속받아 의도치 않은 시스템 접근
  2. 무한 루프: Agent A → Agent B → Agent A 순환 호출로 토큰 비용 폭증 (실사례: 2시간에 $340 소모)
  3. Prompt Injection: 외부 데이터(웹, DB)를 읽는 tool이 악의적 명령을 에이전트에 주입

실행 예산 패턴 구현

import anthropic
from dataclasses import dataclass, field
from typing import Any

@dataclass
class ExecutionBudget:
    max_tool_calls: int = 20
    max_depth: int = 3
    current_calls: int = 0
    current_depth: int = 0
    allowed_tools: set = field(default_factory=set)

def run_subagent(
    task: str,
    budget: ExecutionBudget,
    tools: list[dict]
) -> str:
    if budget.current_depth >= budget.max_depth:
        return "[BLOCKED] 최대 에이전트 깊이 초과"
    
    client = anthropic.Anthropic()
    # 서브에이전트에게 허용된 tool만 전달 (권한 축소)
    filtered_tools = [
        t for t in tools 
        if t["name"] in budget.allowed_tools
    ]
    
    messages = [{"role": "user", "content": task}]
    
    while True:
        if budget.current_calls >= budget.max_tool_calls:
            return "[BLOCKED] tool 호출 예산 소진"
        
        response = client.messages.create(
            model="claude-opus-4-5",
            max_tokens=4096,
            tools=filtered_tools,
            messages=messages,
            system="당신은 제한된 권한의 서브에이전트입니다. 주어진 tool만 사용하세요."
        )
        
        if response.stop_reason == "end_turn":
            return response.content[-1].text
        
        # tool_use 블록 처리
        tool_results = []
        for block in response.content:
            if block.type == "tool_use":
                budget.current_calls += 1
                # 실제 tool 실행은 샌드박스에서
                result = execute_in_sandbox(block.name, block.input)
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": result
                })
        
        messages.append({"role": "assistant", "content": response.content})
        messages.append({"role": "user", "content": tool_results})

def execute_in_sandbox(tool_name: str, inputs: dict) -> Any:
    # Docker/gVisor 격리 실행 (타임아웃 30초)
    # 네트워크: allowlist 기반
    # 파일시스템: read-only 마운트
    pass

트레이드오프와 운영 전략

깊이 제한 설정 기준:

  • depth=2: 오케스트레이터 + 단일 전문가 에이전트 (안전, 느림)
  • depth=3: 계층적 분해 가능 (권장 최대치)
  • depth=5+: 예측 불가능한 비용, 디버깅 난이도 급증

tool 권한 축소 원칙: 서브에이전트는 상위 에이전트 권한의 부분집합만 상속. 오케스트레이터가 DB write 권한을 가져도 서브에이전트는 read-only로 제한.

Prompt Injection 방어: 외부 데이터를 읽는 tool 결과는 별도 XML 태그로 감싸 에이전트 명령과 구분한다: <external_data>...</external_data>

운영 체크리스트

  • [ ] 모든 tool 호출에 agent_id, depth, parent_call_id 로깅 (추적성)
  • [ ] 호출 트리 시각화 대시보드 구축 (Langsmith 또는 자체 구현)
  • [ ] 서브에이전트별 토큰 예산 상한 설정 (max_tokens 엄격 제한)
  • [ ] 동일 tool+인자 조합 중복 호출 감지 및 캐시 처리
  • [ ] 프로덕션 배포 전 혼돈 테스트: 의도적 루프 시나리오로 예산 차단 검증