멀티 에이전트 워크플로우의 실행 안전성 위기 — 그리고 이를 해결하는 아키텍처 패턴
요약
멀티 에이전트 시스템의 핵심 과제인 실행 안전성 문제를 다룹니다. LLM의 확률적 특성과 백엔드 시스템의 결정론적 요구 사항 사이의 충돌을 분석하고, 직접 실행 방식의 위험성을 경고합니다.
핵심 포인트
- LLM의 확률적 출력과 시스템의 결정론적 요구 사이의 긴장 관계
- 직접 실행 방식은 상태 오염 및 조용한 실패를 유발할 위험이 있음
- 실행 안전성은 프롬프팅이 아닌 아키텍처 설계의 문제임
- 확장 가능한 에이전트 시스템을 위한 안전 경계 구축 필요
멀티 에이전트 (Multi-agent) 워크플로우에서 아직 해결되지 않은 가장 큰 문제는 추론 (Reasoning)이 아닙니다. 바로 실행 안전성 (Execution safety)입니다.
오늘날 LLM (Large Language Models)으로 시스템을 구축하는 대부분의 팀은 아직 이 문제에 직면하지 않았습니다. 아직 규모를 확장하지 않았기 때문입니다. 이 글은 곧 확장을 앞두고 있는 분들을 위한 것입니다.
핵심적인 긴장 관계 (The Core Tension)
LLM은 본질적으로 확률적 (Probabilistic)입니다. 모든 출력은 확률 분포 (Probability distribution)로부터 추출된 샘플입니다. 동일한 프롬프트 (Prompt)가 두 번 실행되었을 때 동일한 출력을 생성한다는 보장은 없습니다. 이것은 버그가 아닙니다. 언어 모델을 유용하게 만드는 근본적인 속성입니다.
반면, 프로덕션 백엔드 시스템 (Production backend systems)은 요구 사항상 결정론적 (Deterministic)이어야 합니다. 동일한 입력은 항상 동일한 상태 변화 (State change)를 일으켜야 하며, 사후에 재구성 가능한 감사 로그 (Audit log)를 통해 추적 가능하고 검증 가능해야 합니다.
에이전트 (Agent)를 로우 파이썬 (Raw Python), 개방형 도구 호출 (Open-ended tool calling), 또는 비정형 함수 디스패치 (Unstructured function dispatch)를 통해 실행 환경에 직접 연결하면, 두 세계 사이에 안전 경계(Safety boundary) 없이 연결하게 됩니다.
에이전트는 99번은 올바르게 추론합니다. 하지만 100번째에는 파라미터 (Parameter)를 환각 (Hallucinate)하거나, 컨텍스트 윈도우 (Context window)를 잘못 읽거나, 구조적으로는 유효하지만 의미론적으로는 틀린 지시 사항을 생성합니다. 전통적인 소프트웨어 시스템에서 이는 테스트 단계에서 잡아낼 수 있는 버그입니다. 하지만 직접적인 실행 권한을 가진 에이전트 시스템에서 이는 스택 트레이스 (Stack trace), 감사 로그, 혹은 깔끔한 에러 표면 (Error surface)도 남기지 않는 조용한 상태 오염 (State corruption)입니다.
이것은 프롬프팅 (Prompting)의 문제가 아닙니다. 모델 품질의 문제도 아닙니다. 이것은 아키텍처 (Architectural) 문제입니다.
세 가지 접근 방식 — 그리고 왜 두 가지는 규모 확장 시 실패하는가
접근 방식 1 — 직접 실행 (Direct Execution, 로우 도구 호출)
에이전트가 의도를 생성하고 함수 호출 (Function calls), 셸 명령 (Shell commands), 또는 파이썬 스크립트 (Python scripts)를 통해 이를 직접 실행합니다. 아키텍처는 다음과 같습니다:
사용자 의도 (User Intent) → LLM → 도구 호출 (Tool Call) → 시스템 실행 (System Execution)
이것이 대부분의 팀이 시작하는 지점입니다. 프로토타이핑이 빠르고, LangChain 또는 CrewAI와 연결하기 쉬우며, 데모에서는 인상적으로 작동합니다.
문제는 프로덕션(Production) 환경에서 드러납니다. 모델이 결정한 내용과 시스템이 실제로 수행한 내용 사이에 어떠한 계층(Layer)도 존재하지 않습니다. 실패는 런타임 실패(Runtime failures)로 나타나며, 이는 상태(State)가 이미 변경된 후에야 발견됩니다. 잘못된 인자(Invalid arguments)는 깔끔하게 실패하지 않습니다. 대신 시스템 경계(System boundary)에서 실패하며, 종종 조용히, 그리고 종종 부분적인 실행이 이루어진 후에 발생합니다.
2025년 멀티 에이전트 실패에 관한 연구 분류 체계(Taxonomy)에 따르면, AutoGen, ChatDev, CrewAI를 포함한 프레임워크 전반에서 **14가지의 고유한 실패 모드(Failure modes)**가 식별되었습니다. 이 연구의 핵심 결과는 냉혹했습니다. "기본 모델(Base model) 역량의 향상만으로는 전체 분류 체계를 해결하기에 불충분할 것입니다. 대신, 훌륭한 멀티 에이전트 시스템 설계에는 조직적 이해가 필요합니다. 정교한 개인들로 구성된 조직이라 할지라도 조직 구조에 결함이 있다면 파멸적인 실패를 겪을 수 있습니다."
실패의 원인은 모델에 있는 것이 아닙니다. 아키텍처(Architecture)에 있습니다.
또한 신뢰성이 복합적으로 저하되는 문제도 존재합니다. 체인(Chain) 내의 각 에이전트가 95%의 신뢰도를 가진다면, 세 개의 에이전트를 연결했을 때 전체 작업 성공률은 약 86%로 떨어집니다. 단계가 추가될수록 신뢰도는 기하급수적으로 하락합니다. 이는 개별 에이전트가 나빠서가 아니라, 구조적인 격리(Structural containment) 없이 실패가 체인을 따라 연쇄적으로 발생하기 때문입니다.
직접 실행(Direct execution) 방식에는 격리 계층이 없습니다. 이것이 바로 확장(Scale)할 수 없는 접근 방식입니다.
접근 방식 2 — 가드레일(Guardrails)을 활용한 자연어 파싱(Natural Language Parsing)
검증 계층(Validation layer)이 에이전트와 실행 환경 사이에 위치하여, 실행 전 출력값을 일련의 규칙에 따라 확인합니다.
사용자 의도(User Intent) → LLM → 출력(Output) → 가드레일 필터(Guardrail Filter) → 실행(Execution)
이 방식이 더 낫습니다. NeMo Guardrails, Guardrails-AI, AWS Bedrock Guardrails와 같은 프레임워크들이 이 영역에서 작동합니다. 이들은 경계에서 출력 검증(Output validation), 콘텐츠 필터링(Content filtering), 정책 집행(Policy enforcement)을 제공합니다.
하지만 에이전트가 생성할 수 있는 문법(Grammar)은 여전히 무제한적입니다. 모델은 자유 형식의 텍스트나 느슨하게 구조화된 JSON을 출력합니다. 그러면 가드레일은 해당 출력을 규칙 세트와 대조하여 검증하려고 시도합니다.
문제는 근본적입니다. 공간 자체를 제한하는 것이 아니라 무한한 공간을 필터링하고 있다는 점입니다. 모호하고 개방적인 출력에 대해 작성된 규칙 기반 검증 (Rule-based validation)에는 항상 예외 케이스 (Edge cases)가 존재할 수밖에 없습니다. 기술적으로는 유효하지만 의미론적으로 해로운 (Semantically harmful) 내용을 출력하는 에이전트는 필터링을 통과할 수 있습니다. 가드레일이 예상하지 못한 형식으로 출력하는 에이전트는 예측 불가능하게 실패할 수 있습니다.
LLM (Large Language Models)과 DSL (Domain-Specific Languages)에 관한 Microsoft의 연구에 따르면, 모델에 문법 파일 (Grammar files)과 형식 제약 조건 (Format constraints)을 제공하더라도 여전히 출력을 환각 (Hallucinate)하는 현상이 발견되었습니다. 즉, 형식은 올바르지만 의미적으로는 틀린 (Semantically wrong) 응답을 생성한다는 것입니다. 필터링은 이 중 일부를 잡아낼 수는 있지만, 필터링의 대상이 되는 요소가 형식적으로 정의되어 있지 않기 때문에 모든 것을 잡아낼 수는 없습니다.
이러한 접근 방식은 필요하지만 충분하지는 않습니다.
접근 방식 3 — LLM-to-DSL 컴파일러 패턴 (The LLM-to-DSL Compiler Pattern)
이는 안전성 보장 (Safety guarantee)의 단계를 런타임 동작 (Runtime behavior)에서 구조적 설계 (Structural design)로 이동시키는 아키텍처적 전환입니다.
사용자 의도 (User Intent) → LLM → DSL 출력 → 문법 검증기 (Grammar Validator) → 실행 엔진 (Execution Engine)
에이전트가 자유 형식의 코드나 자연어 명령을 생성하는 대신, 사용자 의도를 **도메인 특화 언어 (Domain-Specific Language, DSL)**로 컴파일합니다. DSL은 출력 공간이 엄격하게 제한된 견고하고 맞춤화된 문법입니다. 그런 다음 시스템은 단 하나의 명령이 시스템 상태 (System state)에 닿기 전에, 결정론적 검증 엔진 (Deterministic validation engine)을 통해 해당 DSL을 실행합니다.
우리는 수십 년 동안 로직을 엄격한 도메인으로 제한하기 위해 DSL을 사용해 왔습니다:
- SQL은 실수로 셸 명령 (Shell command)을 호출하는 것을 허용하지 않습니다.
- Terraform은 실수로 파일 시스템 (File system)에 쓰는 것을 허용하지 않습니다.
- CSS는 실수로 네트워크 요청 (Network request)을 보내는 것을 허용하지 않습니다.
문법은 무엇을 표현할 수 있는지를 정의합니다. 그 외의 모든 것은 구조적으로 불가능합니다. 이는 필터링되거나 차단되는 것이 아니라, 구조적으로 표현 자체가 불가능한 (Inexpressible by construction) 것입니다.
이 새로운 패러다임은 동일한 원칙을 AI 오케스트레이션 (AI orchestration)에 적용합니다.
LLM-to-DSL 패턴의 3단계
1단계 — 제약된 생성 (Constrained Generation)
에이전트는 범용 코드 (general-purpose code) 대신 사용자 의도를 DSL (Domain-Specific Language)로 번역합니다.
다음은 그 차이를 보여주는 최소한의 예시입니다. 데이터베이스를 쿼리하는 임무를 맡은 에이전트를 가정해 보겠습니다.
개방형 도구 호출 (Open-ended tool calling, 접근 방식 1):
# LLM이 이를 생성합니다. 무엇이든 허용됩니다.
import subprocess
result = subprocess.run(["psql", "-c", "DROP TABLE users;"], capture_output=True)
모델은 쿼리를 수행할 "의도"를 가졌으나, 파괴적인 작업을 환각 (hallucination) 했습니다. Python의 문법 (grammar)이 이를 허용했습니다.
DSL 제약 출력 (DSL-constrained output, 접근 방식 3):
QUERY users
WHERE status = "active"
LIMIT 100
...
이 문법에는 DROP 키워드가 포함되어 있지 않습니다. 표현 자체가 불가능합니다. 환각이 착륙할 표면이 없는 것입니다.
DSL은 AI의 추론 (reasoning)과 시스템의 실행 (execution) 사이의 계약 (contract)을 정의합니다. 모델이 말하는 것을 필터링하는 방식이 아니라, 모델이 말할 수 있는 것을 정의하는 방식입니다.
2단계 — 결정론적 검증 (Deterministic Validation)
백엔드 엔진은 정형 문법 (formal grammar)에 따라 DSL 출력을 파싱 (parse) 합니다. 문법이 제한되어 있기 때문에 파싱은 결정론적 (deterministic)입니다. 유효한 DSL은 통과하거나 실패할 뿐입니다. 모호함도, 부분적 실행도, 소리 없는 오류 (silent errors)도 없습니다.
검증 계층의 구조적 모습은 다음과 같습니다:
DSL 입력 → 어휘 분석기 (Lexer) → 토큰 스트림 (Token Stream) → 파서 (Parser) → 추상 구문 트리 (AST) → 의미론적 검증기 (Semantic Validator) → 실행 계획 (Execution Plan)
각 단계에서 실패는 명시적입니다:
- 어휘 분석기 (Lexer): 알 수 없는 토큰을 거부합니다.
- 파서 (Parser): 잘못된 구조를 거부합니다.
- 의미론적 검증기 (Semantic Validator): 문법은 맞지만 논리가 잘못된 경우를 거부합니다 (예: 스키마에 존재하지 않는 필드를 참조하는 경우).
결과적으로: 환각과 잘못된 논리는 소리 없는 런타임 오류 (runtime failures)를 발생시키지 않습니다. 실행이 시작되기 전, 컴파일 단계에서 실패합니다. 오류는 정밀하고, 원인을 추적할 수 있으며, 3단계 뒤에 오염된 상태로 발견되는 것이 아니라 문법 수준에서 로그로 남습니다.
이는 Rust의 소유권 모델 (ownership model)과 평행을 이룹니다. C 언어는 프로그래머를 신뢰했습니다. 단 한 번의 실수만으로도 결과는 치명적이었습니다. 가비지 컬렉션 (Garbage-collected) 언어들은 런타임 (runtime)을 신뢰했습니다. 안전성은 확보되었지만, 제어권을 상실했습니다. Rust는 컴파일러 자체에 정확성을 인코딩했습니다. 즉, 보장이 행동적 (behavioral)인 것이 아니라 구조적 (structural)인 것입니다. LLM-to-DSL 패턴은 에이전트 실행 (agentic execution)에 대해 동일한 역할을 수행합니다.
3단계 — 차분 가능한 실행 (Diffable Execution)
검증된 명령 세트는 사람이 읽을 수 있고, 구조화되어 있으며, 검토 가능합니다. 어떠한 상태 변경 (state change)이 실행되기 전에, 팀은 에이전트가 무엇을 제안하고 있는지 정확히 조사할 수 있습니다.
에이전트 제안 실행 계획
─────────────────────────────
QUERY orders
...
이것은 단순히 좋은 엔지니어링 관행이 아닙니다. 이것이 바로 인간 참여형 (human-in-the-loop) 워크플로우를 대규모로 운영 가능하게 만드는 핵심입니다. DSL 계층이 없다면, 에이전트 작업에 대한 인간의 검토는 가공되지 않은 코드나 자연어 출력을 읽는 것을 의미합니다. 이는 확장이 불가능하며 그 자체로 해석 오류를 유발합니다. DSL 계층이 있으면, 검토는 의미론적 의미 (semantic meaning)가 명시된, 구조화되고 경계가 지정된 명령 세트를 읽는 것을 의미합니다.
에이전트가 무엇을 하려는지 확인할 수 있습니다. 예상했던 내용과 차이점 (diff)을 비교할 수 있습니다. 실행 전에 거부할 수 있습니다. 이것이 실제 현장에서 말하는 "감사 가능성 (auditability)"의 진정한 의미입니다.
이것은 가설이 아닙니다 — 이미 프로덕션에서 사용 중입니다
2025년 말, PayPal은 이 패턴을 프로덕션 규모로 배포한 상세 연구 내용을 발표했습니다. 그들의 시스템은 에이전트 워크플로우 명세 (specification)를 구현 (implementation)으로부터 분리하는 선언적 DSL을 구현하여, 동일한 파이프라인 정의가 여러 백엔드 언어 (Java, Python, Go) 및 배포 환경에서 실행될 수 있도록 합니다.
매일 수백만 건의 상호작용을 처리하는 실제 이커머스 워크플로우에서의 결과는 다음과 같습니다:
- 명령형 (Imperative) 구현 방식 대비 개발 시간 60% 단축
- 배포 속도 3배 향상
- 500줄 이상의 명령형 코드 대신 50줄 미만의 DSL로 복잡한 워크플로우 표현 가능
- 100ms 미만의 오케스트레이션 (Orchestration) 오버헤드 — DSL 레이어가 유의미한 지연 시간(Latency)을 추가하지 않음
가장 눈에 띄는 발견은 선언적 (Declarative) 접근 방식이 엔지니어가 아닌 사람들도 에이전트의 동작을 안전하게 수정할 수 있게 했다는 점입니다. 문법 제약 (Grammar constraint)은 단순히 시스템을 더 안전하게 만들었을 뿐만 아니라, 시스템을 더 넓은 범위의 기여자들에게 접근 가능하게 (Accessible) 만들었습니다. 이는 제한된 문법이 실수로 구조적으로 위험한 변경을 가하는 것을 방지했기 때문입니다.
비즈니스 영향 (Business Implications)
기술적 아키텍처는 직접적인 비즈니스 결과로 이어집니다. 이러한 결과는 규모가 커질수록 복리로 작용합니다.
감사 가능성 (Auditability)이 컴플라이언스 자산이 됨
금융, 의료, 법률과 같은 규제 산업에서는 에이전트가 취하는 모든 행동이 추적 가능하고, 검토 가능하며, 되돌릴 수 있어야 합니다. DSL 기반의 제어 평면 (Control plane)은 실행 전 모든 제안된 상태 변경에 대해 구조화되고 사람이 읽을 수 있는 기록을 생성합니다. 이는 단순히 훌륭한 엔지니어링의 문제가 아닙니다. 많은 관할 구역에서 이는 배포 가능한 시스템과 배포 불가능한 시스템을 가르는 차이입니다.
GDPR의 설명 요구권 (Right to explanation), HIPAA의 감사 추적 (Audit trail) 요구 사항, 그리고 SOC 2의 액세스 제어 (Access control) 표준은 모두 자동화된 작업이 추적 가능하고 재구성 가능할 것을 요구합니다. 직접 실행 (Direct execution)을 통해 작동하는 에이전트는 설계상 이러한 요구 사항을 충족할 수 없습니다. 반면 DSL 제어 평면을 통해 작동하는 에이전트는 구조적으로 이를 충족합니다.
사고 비용의 극적인 감소
직접 실행을 통해 작동하는 에이전트가 상태 (State)를 손상시키면, 실패는 런타임 (Runtime)에 — 즉, 사후에 발견되며, 종종 어떤 명령이 원인이 되었는지에 대한 명확한 흔적 없이 발견됩니다. 복구를 위해서는 불완전할 수 있는 로그로부터 의도를 재구성해야 합니다.
DSL (Domain-Specific Language)을 통해 작동하는 에이전트가 잘못된 로직을 생성할 경우, 해당 실패는 실행 전 파싱 단계 (parse time)에서 문법 수준의 정확한 오류와 함께 포착됩니다. 폭발 반경 (blast radius)은 제로입니다. 변경된 상태도 없습니다. 탐지 평균 시간 (mean time to detection)은 수 시간에서 밀리초 단위로 급감합니다.
기록된 운영 환경의 실패 사례들이 이를 구체적으로 보여줍니다. 통제 불능의 상호작용 루프 (runaway interaction loop)에 빠진 두 에이전트는 탐지되기 전까지 11일 동안 작동하며 47,000달러의 API 비용을 발생시켰습니다. Ramp에서 그럴듯하지만 허위인 항목을 조작한 경비 보고 에이전트는 90일 동안 100만 달러 이상의 사기 송장을 생성했습니다. 이것들은 추론의 실패가 아닙니다. 실행 격리 (execution containment)의 실패입니다. 제한된 문법을 가진 DSL 제어 평면 (control plane)이 있었다면 두 패턴 모두 검증 단계에서 포착했을 것입니다. DSL 문법이 무제한 반복 (unbounded iteration)을 표현할 수 없다면 에이전트는 무한 루프에 진입할 수 없기 때문입니다.
인간의 감독이 운영적으로 실행 가능해짐
차분 가능한 실행 (Diffable execution)은 인간 검토자가 에이전트가 승인되기 전, 에이전트가 수행하려는 내용을 구조화되고 읽기 쉬운 형태로 정확히 검사할 수 있음을 의미합니다. 이는 인간 참여형 (human-in-the-loop) 아키텍처를 대규모로 실용화할 수 있게 합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기