
Elliot 소개: LLM이 구동하는 로봇 시뮬레이션과 상태 머신을 통한 신뢰성 확보
요약
LLM 기반 에이전트의 불확실성을 해결하기 위해 유한 상태 머신(FSM)을 결합한 로봇 시뮬레이션 프로젝트 Elliot을 소개합니다. LLM은 동작을 제안하고, 상태 머신은 실제 상태를 바탕으로 이를 검증 및 거부하여 에이전트의 신뢰성을 확보합니다.
핵심 포인트
- LLM의 환각 및 잘못된 상태 판단 문제 해결
- 유한 상태 머신(FSM)을 통한 결정론적 동작 검증
- Theodosia 라이브러리를 활용한 제안 및 거부 메커니즘
- 모델의 판단력과 시스템의 규칙성을 결합한 하이브리드 구조
Elliot이 운전합니다. 상태 머신이 거부합니다.
저는 언어 모델(LLM)에게 핸들을 넘겨주었습니다. 그리고 그 모델이 시도하는 모든 움직임을 거부할 수 있는 권한을 상태 머신(state machine)에 주었습니다.
30초 만에 실행 가능. API 키나 모델 없이, 완전히 오프라인으로 (결정론적 내비게이터가 운전합니다):
git clone https://github.com/msradam/elliot && cd elliot
uv venv && uv pip install -e .
python run.py --offline
이것이 2D 시뮬레이션 속의 로봇, Elliot입니다. LLM이 그를 구동하고 있습니다. 이 모델은 모든 단계를 자체적인 말로 서술하며, 준비되었다고 생각하는 순간 다음 단계로 나아갑니다. 빨간색 선들은 상태 머신이 '안 돼'라고 알려주는 부분입니다.
요약(TL;DR): 모델이 다음 움직임을 제안하면, 유한 상태 머신(finite state machine)이 실제 상태와 비교하여 이를 검증하고 나머지는 거부합니다. 이 거부 기능은 제가 만든 작고 오픈 소스인 라이브러리 Theodosia를 통해 이루어지며, 이는 단 한 줄의 연결 코드입니다. 로봇은 제가 이것을 설명하는 데 찾은 가장 재미있는 방법일 뿐입니다.
문제점: LLM은 자신만만할 뿐, 정확하지 않다
여러 단계 이상의 작업을 수행하는 에이전트(agent)를 구축해 본다면 실패 모드를 알게 됩니다. 모델은 아직 완료하지 않은 단계를 끝냈다고 결정합니다. 건너뛰어 버립니다. 파일이 작성되었고, 주문이 배치되었으며, 배포가 완료되었다고 주장합니다.
제 경우, 결제 호출(payment call)에서 오류를 반환하는 대신 시간 초과(timed out)가 발생하자 모델은 이를 '아니다'가 아닌 '예'로 읽고 다음 단계로 넘어가, 존재하지 않는 주문에 대한 확인 이메일을 발송했습니다.
이것은 고의적으로 거짓말을 하는 것이 아닙니다. 그것에게는 진실된 근거(ground truth)가 없고, 오직 자신만의 믿음만 있으며, 그 믿음은 현실보다 상위에 위치합니다.
일반적인 해결책은 프롬프트를 더 강하게 만드는 것입니다.
만약 워크플로우(workflow) 자체가 명시적인 상태(states)와 명시적인 전이(transitions)를 가진 유한 상태 머신(finite state machine)이고, 모델은 오직 다음 전이를 '제안(propose)'할 수만 있다면 어떨까요? 다른 무언가가 실제 상태에 비추어 그 제안을 검증하고, 이를 실행하거나 거부한 뒤 실제로 허용 가능한 동작(moves)들을 다시 돌려주는 방식 말입니다.
이렇게 하면 필요한 곳(그래프와 게이트)에는 결정론(determinism)을 얻고, 원하는 곳(어떤 동작이 허용되는지, 그리고 그 이유는 무엇인지)에는 모델의 판단력을 얻을 수 있습니다. 모델은 운전(drive)을 할 뿐, 도로를 다시 그릴 수는 없습니다.
Elliot 소개
Elliot는 그 아이디어를 시뮬레이션 상에 구현한 것이며, 약간은 연극적입니다.
- 그의 규칙북은 Apache Burr (상태 머신 라이브러리)로 구축된 유한 상태 머신(finite state machine)입니다. 부팅(boot), 정찰(recon), 침투(exploit), 유출(exfil), 흔적 삭제(ghost)의 다섯 단계와 그 사이의 허용된 동작들로 구성됩니다. 규칙북은 아무것도 결정하지 않습니다. 오직 어떤 동작이 존재하는지만 말해줍니다. (네, 그 Elliot의 이름을 따왔습니다. 아시는 분은 아시겠죠.)
- 정신(mind)은 LLM입니다. LLM은 상태를 읽고, 상황을 서술하며, litellm을 통해 어떤 허용된 동작을 수행할지 선택하므로 모델을 교체(swappable)할 수 있습니다.
- 감각(senses)은 상태(state)입니다. 2D 라이다(lidar), 위치, 충돌 플래그(collision flag), 도착 플래그(arrival flag) 등이 있으며, 이 모든 것은 헤드리스(headless)로 실행되는 ir-sim (2D 로봇 시뮬레이터)에서 제공됩니다.
- 실제 조향(steering)은 일반적인 컨트롤러(controller)가 수행합니다. 조향은 모터 작업이며, 모델은 모터 작업에 서투르기 때문입니다.
해당 스크린샷의 콘솔을 보세요. 모델은 RECON(정찰) 단계에 있으며 타겟에 접근하고 있습니다. 모델은 의욕이 앞서서 계속 EXPLOIT(침투)을 시도합니다. 그러면 머신은 계속해서 다음과 같이 응답합니다.
REFUSED
recon에서exploit으로의 시도 거부됨; 자격 미달. 허용됨:recon
대상(target)이 실제로 감지 범위(sensing range) 내에 들어오기 전까지는 EXPLOIT 단계로 이동할 수 없습니다. 시뮬레이터 자체의 도착 플래그(arrival flag)가 발생하기 전까지는 EXFIL 단계로 이동할 수 없습니다. 진정으로 홈(home)으로 돌아오기 전까지는 GHOST 상태가 될 수 없습니다. 모든 게이트(gate)는 모델이 주장할 수 있는 것이 아니라, 세상이 제공해야 하는 사실(fact)입니다. 모델이 너무 일찍 도달하면, 거부 메시지와 함께 허용된 동작 목록을 받게 되며, 대신 현재 있는 단계(phase)에서 작업을 수행합니다.
그 거부는 오류가 아닙니다. 그것이 핵심입니다. 그것이 바로 모델에게 운전대를 맡겨도 안전하게 만드는 요소입니다.
기계는 세상이 획득하지 못한 그 어떤 것도 거부합니다.
거부를 수행하는 주체: Theodosia
여기가 제가 실제로 여러분께 보여드리고 싶은 부분입니다.
Theodosia는 Apache Burr 상태 머신(state machine)을 가져와 MCP 서버로 마운트(mount)합니다. 단 한 번의 호출로 가능합니다:
import theodosia
server = theodosia.mount(burr_app, name="elliot")
이제 모든 MCP 클라이언트(Claude, 사용자 정의 에이전트, 스크립트 등)는 작고 일정한 툴 인터페이스(tool surface)를 보게 됩니다. 주로 하나의 step 툴로 구성됩니다. 클라이언트는 step(action)을 호출합니다. Theodosia는 현재 상태에서 실제로 도달 가능한 전이(transitions)를 기준으로 해당 동작을 검사합니다. 동작이 유효하면 실행됩니다. 유효하지 않으면 클라이언트는 거부 메시지와 함께 무엇을 할 수 있는지 안내받습니다:
{
"error": "invalid_transition",
"requested": "exfil",
...
그래프가 곧 계약(contract)입니다.
게이트는 실제 상태(real state)에 대한 조건이며, 이를 일반적인 Burr 문법으로 작성합니다:
from burr.core import when
builder.with_transitions(
...
제가 의존했던, Theodosia가 제공하는 두 가지 기능이 더 있습니다:
- 상태는 서버에 존재합니다 (State lives on the server). 모델은 상태를 보유하지 않으며 상태를 이탈(drift)시킬 수도 없습니다. 모델은 제안할 뿐이며, 서버가 신뢰할 수 있는 단일 원천 (source of truth)입니다.
- 모든 단계가 기록됩니다. Theodosia는 모든 행동과 모든 거부(refusal)에 대해 해시 체인 방식의 원장 (hash-chained ledger)을 유지합니다. 각 항목은 이전 항목의 해시를 포함하므로, 단 하나의 단계라도 수정되거나 누락되면 체인이 깨집니다. 세션을 재생하면 아무것도 몰래 변경되지 않았음을 검증할 수 있습니다. 결제, 배포, 지원 작업과 같이 감사가 필요한 모든 사항에서 이 부분이 핵심입니다.
Burr 그래프가 여러분이 작성하는 유일한 것입니다. Theodosia는 이를 에이전트가 구동할 수는 있지만 망가뜨릴 수는 없는 무언가로 바꾸어 주는 단 한 줄의 코드입니다.
이것은 사실 로봇에 관한 것이 아닙니다
Elliot이 로봇인 이유는 로봇이 관찰하기 즐겁고 이해하기 쉽기 때문입니다. 이 패턴은 모델이 주도권을 가져야 하지만, 모델 스스로 자신의 진행 상황을 보고하는 것을 신뢰해서는 안 되는 모든 LLM 기반 워크플로 (LLM-driven workflow)를 위한 것입니다:
- "결제 완료 (payment captured)"가 주장되는 것이 아니라 반드시 사실이어야 하는 결제 흐름 (checkout flow)
- 마지막 단계가 통과될 때까지 다음 단계를 실행할 수 없는 배포 파이프라인 (deploy pipeline)
- 다단계 양식 (multi-step form), 지원 런북 (support runbook), 에이전트 작업 그래프 (agent task graph)
이것들은 가설이 아닙니다. 프로덕션급 버전은 Theodosia를 기반으로 구축된 온콜 장애 분류 (on-call incident-triage) 에이전트인 Leavitt입니다. 이 에이전트는 Grafana 메트릭과 로그, k6 부하, 배포 컨텍스트를 읽고 이들을 상관 분석하여, 모델의 확신(confidence)이 아닌 증거에 의해 처분이 제한되는 분류 보고서를 작성합니다.
이 에이전트는 오직 읽기만 합니다. 프로덕션 환경을 대상으로 지정해 두고 자리를 떠나도 됩니다. Microsoft Research의 AIOpsLab 벤치마크에서 이 강제 계층 (enforcement layer)은 정확도 측면에서 비용이 전혀 들지 않습니다. 단지 확신에 찬 잘못된 보고서를 "저하됨 (degraded)" 또는 "결론을 내릴 수 없음 (inconclusive)"으로 바꿀 뿐입니다.
상태 머신 (state machine)으로 그리십시오. 실제 상태에 조건을 부여하십시오. Theodosia로 마운트하십시오. 모델은 레일 안에서 똑똑해질 수 있으며, 그 레일은 움직이지 않습니다.
왜 LangGraph나 LangChain가 아닌가요?
LangGraph와 LangChain는 인프로세스 오케스트레이션 (in-process orchestration) 레이어입니다. 여러분은 노드를 구성하고, 상태를 유지하며, 자신의 프로그램 내부에서 루프를 실행합니다. 이 도구들은 그래프를 구축하고, 도구를 연결하며, 체인을 통해 메모리와 검색 (retrieval)을 스레딩하는 작업에 매우 능숙합니다. 만약 에이전트의 로직을 조립하기 위한 유연한 프레임워크를 원한다면, 그것이 바로 이 도구들의 용도입니다.
Theodosia는 다른 문제를 해결하며, 바로 이 지점에서 저는 차별점을 강조하고자 합니다. 에이전트와 동일한 프로세스 내에서 실행되는 가드레일 (guardrail)은 에이전트가 우회할 수 있는 가드레일이 됩니다. Theodosia는 여러분이 오케스트레이션하는 프레임워크가 아니라, 서버 수준에서 강제되는 계약 (contract)입니다. 이는 일반적인 상태 머신 (state machine)을 MCP 서버로 마운트하며, 서버가 어떤 전이 (transition)가 허용되는지 결정하고 나머지는 거부합니다. 상태는 모델의 손이 닿지 않는 서버 측에 존재하며, 모든 단계는 변조 방지 기능이 있는 원장 (tamper-evident ledger)에 기록됩니다. 강제 실행 로직이 클라이언트에 있지 않기 때문에, LangGraph, LangChain, 또는 아무것도 사용하지 않고 작성된 에이전트 뒤에도 Theodosia를 배치할 수 있습니다.
직접 시도해 보세요
두 단계만 거치면 되며, 두 번째 단계는 단 한 줄입니다:
pip install theodosia
import theodosia
server = theodosia.mount(your_burr_app) # 이제 불법적인 움직임을 거부하는 MCP 서버가 됩니다
Theodosia 리포지토리에 전체 가이드가 있습니다.
자신의 그래프를 연결하기 전에 이것이 무언가를 구동하는 모습을 보고 싶다면, 이 포스트 상단에 있는 오프라인 명령어가 바로 실행 가능한 데모인 Elliot입니다.
msradam / theodosia
AI 에이전트를 레일 위에 올리세요: Burr 상태 머신을 MCP 서버로 마운트하여 에이전트가 허용된 다음 단계만 수행할 수 있도록 하며, 모든 단계를 기록하고 재생할 수 있게 합니다.
Theodosia
Theodosia
Theodosia는 Burr Application을 MCP 서버로 마운트합니다. 모든 Burr 액션(action)은 단일 step(action, inputs) 도구를 통해 접근할 수 있습니다. 서버는 각 액션이 실행되기 전에 그래프를 기준으로 도달 가능성(reachability)을 확인하며, 순서에 맞지 않는 호출은 허용된 다음 동작(legal next moves)을 안내하며 거부하고, 모든 시도를 기록합니다.
설치 (Install)
Python 3.11, 3.12 또는 3.13이 필요합니다 (Burr은 아직 3.14를 지원하지 않습니다). Python 3.14를 새로 설치한 환경에서는 "no version that satisfies the requirement theodosia"라는 메시지가 나타날 것입니다. 먼저 3.11~3.13 버전의 가상 환경(venv)을 생성하세요.
uv venv --python 3.13 # 또는: python3.13 -m venv .venv
uv pip install theodosia # 또는: pip install theodosia
선택적 추가 기능(Optional extras): theodosia[observability], theodosia[ui], theodosia[claude], theodosia[mellea], theodosia[all].
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기
