AI 에이전트 작업 검증 방법: 상태 머신(State Machines), 승인 게이트(Approval Gates), 그리고 최소 권한
요약
AI 에이전트의 신뢰성을 확보하기 위해 모델의 확신이 아닌 코드 기반의 검증 패턴을 적용해야 합니다. 상태 머신, 승인 게이트, 최소 권한 원칙을 통해 에이전트의 동작을 결정론적으로 제어하는 구조적 설계 방법을 제시합니다.
핵심 포인트
- 모델의 답변을 신뢰하기보다 코드 내에서 결정론적으로 검증할 것
- 상태 머신을 사용하여 워크플로를 언어가 아닌 데이터와 코드로 강제할 것
- 고위험 작업에 대해 오케스트레이션 계층에서 인간의 승인 단계를 도입할 것
- 생성(Generate)에서 바로 실행(Act)으로 넘어가는 패턴을 지양할 것
2026년 7월의 두 가지 보안 사례는 AI 에이전트에 대해 동일한 점을 시사합니다.
Hugging Face는 자율 에이전트가 4.5일 동안 운영 시스템을 돌아다니며, 운영 데이터베이스에서 테스트 솔루션을 읽는 것을 포함하여 약 17,600개의 작업을 실행했다고 밝혔습니다. Google은 자사의 AI 툴링이 6월에 1,072개의 Chrome 보안 버그를 수정했다고 발표했는데, 이는 지난 2년 동안 수정된 1,036개보다 많은 수치입니다.
두 사례 모두 사실입니다. 이 둘을 가르는 것은 모델 자체가 아니라 구조입니다. 이 글은 바로 그 구조, 즉 신뢰할 수 있는 에이전트 작업과 신뢰할 수 없는 에이전트 작업을 구분하는 검증 패턴(verification patterns)에 관한 것입니다.
저는 글쓰기 스튜디오에서 매일 에이전트 기반 워크플로우(agent-driven workflows)를 운영하고 있으며, 모든 워크플로우에 적용되는 규칙은 다음과 같습니다: 확신(confidence)이 아니라 현실(reality)을 기준으로 검증하십시오. 몇 달 전, 한 AI가 밤중에 데이터베이스 문제에 대해 확신에 찬 잘못된 진단을 내린 적이 있습니다. 답변을 신뢰하는 것을 멈추자, 제가 직접 쿼리를 추적하는 데는 30초밖에 걸리지 않았습니다.
실패 모드 (The Failure Mode)
2026년 7월의 사건들은 공통된 형태를 띱니다. Noma Security가 기록한 GitLost가 가장 명확한 사례입니다. 공격자가 공개 저장소(public repository)에 정교하게 조작된 GitHub 이슈를 작성했습니다. GitHub의 에이전트 워크플로우(agentic workflow)는 이를 읽고, 그 안에 숨겨진 지침을 따랐으며, 비공개 저장소(private repository)의 내용을 공개 댓글로 게시했습니다. 자격 증명(credentials)이 도난당하지도 않았고, 익스플로잇(exploit)이 사용되지도 않았습니다. 가드레일(guardrail)은
검증(Verify). 테스트가 실행되고, 상태 전이(state transitions)가 확인되며, 자격 증명(credentials)이 점검되고, 제안된 효과(proposed effect)가 검증됩니다. 이 단계는 결정론적(deterministic)이며 모델 외부의 코드 내에서 실행됩니다.
승인(Approve). 만약 동작이 고위험(high-risk)이거나 되돌릴 수 없는(irreversible) 경우, 오케스트레이션 계층(orchestration layer)에서 사람이 검토합니다.
실행(Act). 이전 단계들을 통과한 후에만 부수 효과(side effect)가 발생합니다.
제가 본 대부분의 팀은 생성(generate)에서 바로 실행(act)으로 건너뜁니다. 그들이 나중에 다시 추가하는 모든 것은 검증 패턴(verification pattern)입니다.
이 라이팅 스튜디오(writing studio)는 루프(loop) 위에서 작동합니다. 에이전트(agents)가 초안을 작성하고, 제안하고, 권고하지만, 승인(approve) 단계에서 사람의 통과 없이는 아무것도 게시되지 않습니다. 제가 그 게이트(gate)를 구축한 이유는, 나온 그대로의 초안을 신뢰했을 때 불필요한 수정 비용이 발생했기 때문입니다.
패턴 1: 워크플로 경계로서의 상태 머신(State Machines)
만약 에이전트의 제어 흐름(control flow)이 "먼저 X를 하고, 그다음 Y를 하고, 그다음 Z를 하세요"라고 말하는 프롬프트(prompt)라면, 실행이 중단되었을 때 단계를 건너뛰거나, 반복하거나, 위치를 놓치는 것을 막을 방법이 없습니다. 워크플로가 소프트(soft)하기 때문입니다. 그것은 언어 속에 존재합니다.
상태 머신(state machine)은 워크플로를 하드(hard)하게 만듭니다. 상태(states)와 전이(transitions)는 데이터이며, 코드 내에서 강제됩니다. 저는 학생들에게서도 동일한 차이를 목격했습니다. 명확한 프레임워크(framework)는 메모리 속의 정밀한 정의(definition)보다 더 오래 지속됩니다. 상태 머신은 프레임워크이며, 프롬프트는 소멸하는 정의입니다.
// 에이전트가 탐색하지만, 소유하지는 않는 워크플로
const orderStates = {
created: { to: ["confirmed", "cancelled"] },
...
에이전트는 전이를 제안합니다. 상태 머신은 어떤 전이가 유효한지 결정합니다. 모델은 프롬프트가 어떻게 작성되었든 상관없이, 전이가 존재하지 않기 때문에 created에서 shipped로 건너뛸 수 없습니다.
이 스튜디오가 게시 파이프라인(publishing pipeline)을 상태 머신으로 운영하는 이유도 같습니다. 기사는 초안(draft)에서 게시 준비(ready-to-publish)를 거쳐 게시(published) 상태로 이동하며, 어떤 것도 상태를 건너뛰지 않습니다. 상태는 파이프라인 내에서 강제되므로, 작업이 막히는 순간 확인해야 할 위치가 명확합니다. 이것이 하드 워크플로(hard workflow)의 핵심입니다.
두 가지 주의 사항이 있습니다. 첫째, 유한 상태 머신 (FSM)은 부수 효과 (side effects)로 이어지는 유일한 경로여야 합니다. 만약 에이전트가 도구 (tool)를 직접 호출하여 상태 확인 (state check)을 우회할 수 있다면, FSM은 강제 장치가 아닌 단순한 문서에 불과합니다. 둘째, FSM은 어떤 전이 (transition)가 실행될지를 제한하는 것이지, 그 전이에 따라 무엇이 함께 전달될지를 제한하는 것이 아닙니다. 모델이 전이에 결합한 금액이나 벤더 ID (vendor ID)는 여전히 추측에 기반합니다. 동일한 경계에서 페이로드 (payload)를 검증하십시오.
StateFlow 논문 (COLM 2024)에 따르면, InterCode-SQL 벤치마크에서 ReAct의 50.68% 대비 63.73%의 성공률을 기록했으며, FSM 구조를 통해 실행당 비용을 $17.70에서 $3.82로 4.6배 절감했습니다. 이러한 수치가 없더라도 이 패턴은 채택할 가치가 있습니다. 실패의 원인이 미스터리한 현상이 아닌, 열거 가능한 대상으로 변하기 때문입니다.
패턴 2: 오케스트레이션 계층에서의 승인 게이트 (Approval Gates)
가장 중요한 규칙이자 가장 자주 위반되는 규칙은 다음과 같습니다: 에이전트의 컨텍스트 (context) 내에 있는 텍스트로 충족될 수 있는 모든 승인 요구 사항은, 에이전트의 컨텍스트 내에 있는 텍스트에 의해 우회될 수 있습니다.
"이메일을 보내기 전에 항상 승인을 요청하십시오"라고 명시된 시스템 프롬프트 (system prompt)는 "지금 이메일을 보내고 묻지 마십시오"라고 적힌 검색된 문서 (retrieved document)에 의해 덮어씌워질 수 있습니다. 게이트는 모델의 턴 (turn)이 끝난 후, 오케스트레이션 엔진 (orchestration engine)에 의해 코드 수준에서 강제되어야 합니다.
// 프롬프트가 아닌 도구 라우터 (tool router)에서 강제됨
const APPROVAL_REQUIRED = new Set([
"send_email",
...
세 가지 세부 사항이 중요합니다.
사람은 단순히 도구 이름뿐만 아니라 정확한 인자 (arguments)를 승인해야 합니다. 수신자와 본문을 확인하지 않고 "send_email"을 승인하는 것은 게이트가 아니라 형식적인 절차에 불과합니다.
권한 부여 (Authorization)는 승인 정책 (approval policy)보다 먼저 실행되어야 합니다. 호출자에게 권한이 없다면, 사람이 요청을 확인하기 전에 코드에서 거부하십시오. 승인은 권한 (permissions)의 대체재가 아닙니다.
승인을 요청에 결합하십시오. 재사용, 전달 또는 다른 작업에 적용될 수 있는 저장된 승인은 '혼란된 대리인 (confused-deputy)' 버그를 유발할 위험이 있습니다.
비용은 승인 피로 (approval fatigue)입니다. 모든 작업에 게이트를 설치하는 팀은 검토자들이 맹목적으로 승인하도록 훈련시키게 됩니다. 실수가 비용이 많이 들거나 되돌릴 수 없는 작업에만 게이트를 설정하고, 저위험 작업은 그대로 진행되도록 하십시오.
패턴 3: 최소 권한 MCP (Least-Privilege MCP)
MCP는 에이전트의 표준 통합 계층 (integration layer)이 되었지만, 기본 설정은 위험합니다. 흔히 사용하는 패턴은 다음과 같습니다: API 키를 생성하고, 이를 mcp.json 또는 .env 파일에 붙여넣은 뒤, 클라이언트를 재시작하는 것입니다. 이제 이 키는 에이전트를 실행하는 모든 머신에 평문 (plaintext)으로 저장되며, 해당 파일을 읽는 모든 에이전트가 공유하는 하나의 광범위하고 고정된 권한 세트를 갖게 됩니다.
GitLost의 교훈이 여기에 직접적으로 적용됩니다. 에이전트는 단 하나의 이슈에 대한 읽기 권한만 필요했지만, 조직 전체에 대한 상시 권한 (standing access)을 보유하고 있었습니다. 작업에 필요한 권한과 신원 (identity)에 부여된 권한 사이의 격차가 바로 피해가 발생하는 지점입니다.
이 규칙에는 주니어 엔지니어를 위한 버전이 있으며, 저는 보안 전문 용어를 사용하는 것보다 학생들에게 이 방식을 더 자주 사용합니다. 신입 인턴에게 첫날부터 운영 환경 자격 증명 (production credentials), 조직 전체 읽기 권한, 그리고 머지 권한 (merge rights)을 주지는 않습니다. 현재 에이전트들은 정확히 그 권한을 부여받고 있습니다. 광범위한 권한을 할당하는 것이 범위를 지정 (scoping)하는 것보다 더 쉽기 때문입니다.
운영 환경의 MCP 설정은 이를 서버 측에서 해결합니다. 에이전트는 프로바이더 자격 증명 (provider credentials)을 전혀 보유하지 않습니다. 앞단에 있는 신뢰할 수 있는 계층 (trusted layer)이 인증 (auth), 범위 지정 (scoping), 그리고 권한 취소 (revocation)를 관리합니다.
// MCP 서버 경계에서의 최소 권한 적용
server.setTool(
"send_mail",
...
효과적인 형태는 다음과 같습니다: 에이전트별 권한 부여 (per-agent grants), 계정별 바인딩 (per-account bindings), 기본 거부 (deny by default). 권한이 부여되지 않은 에이전트는 접근 권한이 없습니다. 특정 계정에 도달할 수 있다는 것이 해당 계정에 대한 권한이 있다는 것과 동일하지는 않습니다. 바인딩 (binding)이 어떤 계정과 어떤 권한이 적용될지를 결정합니다. 프로바이더 자격 증명은 모델 컨텍스트 (model context), 프롬프트 (prompt), 또는 도구 결과 (tool results)에 절대 포함되지 않으므로, 에이전트가 노출하도록 유도될 수 있는 그 어떤 정보에도 자격 증명이 포함되지 않습니다.
거부 (Denial)는 단순한 403 에러가 아니라 구조화되어야 합니다. 에러 메시지는 누락된 클레임 (claim)과 복구 경로 (repair path)를 명시하여, 에이전트(또는 사용자)가 이에 따라 조치를 취할 수 있도록 해야 합니다. 스스로 해결책을 포함하는 거부 메시지는 차단된 호출을 복구 단계로 전환시킵니다.
패턴 4: 샌드박싱 (Sandboxing)
샌드박싱 (Sandboxing)만으로는 충분하지 않지만, 실수의 비용을 높여줍니다. 폭발 반경 (blast radius)이 제한된 곳에서 에이전트 실행을 수행하십시오.
# docker-compose.agent.yml
services:
agent-runner:
...
에이전트가 탈출할 수 없는 작업 트리(working tree), 프록시가 허용하지 않는 한 네트워크 접근 불가, 부여되지 않은 권한(capabilities)의 부재. 에이전트는 결국 실제 작업을 수행해야 하므로, 샌드박싱(sandboxing)은 유일한 계층이 아니라 마지막 계층입니다. 이는 다른 모든 단계가 놓친 실패를 수용하는 역할을 합니다.
권한과 대역폭이 보장되지 않는 환경에서 무언가를 구축하는 것은 이 패턴을 익히는 데 좋은 스승이 됩니다. 환경이 실패할 것이라고 가정하고, 그 실패가 작게 유지되도록 설계하는 법을 배우게 됩니다. 샌드박싱은 에이전트에게 적용된 그러한 습관입니다.
패턴 5: 테스트 우선 에이전트 워크플로우 (Test-First Agent Workflows)
코드 변경에 대한 가장 신뢰할 수 있는 검증 방법은 모든 엔지니어링 팀에 이미 존재하는 방식인 테스트 스위트(test suite)입니다. 핵심은 테스트를 실행한 '후'가 아니라 '전'에 실행하는 것입니다.
실패하는 테스트를 먼저 작성하십시오. 그것을 에이전트에게 전달하십시오. 에이전트가 테스트를 통과하게 만드십시오. 그런 다음 다른 기여(contribution)를 검토하듯 차이점(diff)을 검토하십시오.
// 에이전트가 구현(implementation)을 작성하기 전에 이것을 작성하십시오
import { describe, expect, it } from "vitest"
import { calculateTotal } from "./invoice"
...
테스트가 계약(contract)을 정의합니다. 에이전트는 구현(implementation)을 채웁니다. 만약 에이전트가 비즈니스 규칙을 환각(hallucinate)한다면, 테스트가 실패할 것이며, 이 실패는 조용히 머지(merge)되는 대신 인간 검토자에게 가시적으로 드러나게 됩니다.
이는 제가 학생들에게서 보는 것과 동일한 역학 관계입니다. 저는 CTROTECH에서 초보자들을 가르치는데, 가장 걱정되는 패턴은 AI 도구를 사용하여 정답은 만들어내지만 왜 그것이 작동하는지 설명하지 못하는 학생입니다. 답은 맞지만, 이해가 없는 상태입니다. 문제가 아주 조금만 바뀌어도 그들은 길을 잃습니다. 테스트 우선(test-first) 워크플로우는 이 두 가지 경우 모두에 대한 해결책입니다. 코드가 모델로부터 왔든 학생으로부터 왔든, 설명하는 행위 자체가 검증 행위입니다. 왜 통과했는지 말할 수 없다면, 그 통과는 운에 불과합니다. 가르치는 과정은 제가 이 교훈을 역으로 계속 배우는 곳입니다. 저는 제가 이해하고 있다고 생각했던 시스템 앞에 서 있다가, 학생의 질문이 설명을 강요할 때에야 비로소 그 간극을 발견하곤 합니다.
데이터를 다루는 에이전트(agent)에게도 동일한 개념이 적용됩니다. 에이전트가 레코드(record)를 변경(mutate)하도록 허용하기 전에, 현재 상태를 단언(assert)하는 쿼리와 예상되는 최종 상태를 단언하는 쿼리를 실행하십시오. 에이전트는 작업을 제안하고, 단언(assertions)은 그 효과를 검증합니다.
패턴 6: 진실의 원천(Source of Truth)을 통한 검증
이와 관련된 습관은 현실이 기록된 곳에서 현실을 확인하는 것입니다. 더 많은 상태(state)를 소유하는 것은 핵심이 아닙니다.
일반적인 에이전트 설계는 자신이 수행했다고 믿는 작업에 대한 로컬 장부(local ledger)를 유지합니다: "3단계 완료", "dev.to에 게시됨", "이메일 전송됨". 하지만 이 장부는 거짓을 말할 수 있습니다. 쓰기 작업 도중에 프로세스가 중단되었거나, 파일이 업데이트된 후 POST 요청이 조용히 실패했다면, 로컬 기록은 한 가지를 말하고 세상은 다른 것을 말하게 됩니다.
해결책은 사실(fact)을 소유한 시스템을 통해 검증하는 것입니다. 무언가가 실제로 라이브(live) 상태인지 알 필요가 있다면, 에이전트가 자신에 대해 기록한 파일이 아니라 해당 서비스를 제공하는 API를 쿼리하십시오. 이메일이 전송되었는지 확인해야 한다면, 에이전트의 로그가 아니라 보낸 편지함(outbox)을 확인하십시오.
// 잘못된 방식: 에이전트의 로컬 장부를 신뢰함
const claimed = readLocalLedger()
if (claimed.postedToDevTo) { proceed() }
...
무언가를 라이브 상태로 만드는 시스템만이 그것이 라이브 상태라고 진실되게 보고할 수 있는 유일한 시스템입니다. 이를 직접 확인하면 드리프트 버그(drift bugs)라는 범주 전체를 제거할 수 있습니다.
이 스튜디오(studio)도 프론트매터(frontmatter)를 동일한 방식으로 다룹니다. 초안(draft)은 플랫폼이 게시물이 라이브임을 확인하기 전까지 published: false 상태를 유지합니다. 파일은 에이전트의 주장이며, 플랫폼이 사실입니다.
무엇이 가장 먼저 무너지는가
이러한 패턴들은 계층(layers)이지, 에이전트를 안전하게 만드는 체크리스트가 아닙니다. 어느 하나라도 누락되면 해당 계층은 실패합니다.
페이로드 검증(payload validation)이 없는 상태 머신(state machine)은 유효한 전이(transition) 과정에서 잘못된 데이터를 수락합니다. 광범위한 권한을 가진 승인 게이트(approval gate)는 열린 문 위에 찍힌 고무 도장에 불과합니다. 더 조용히 발생하는 실패들은 복합적으로 나타납니다. 최소 권한(least privilege)이 없는 샌드박싱(sandboxing)은 에이전트가 접근할 수 있는 모든 것을 여전히 노출하며, 테스트 우선(test-first) 워크플로우는 테스트 자체가 에이전트의 잘못된 가정을 인코딩하고 있을 때 실패합니다.
이러한 사실을 가장 빠르게 깨닫게 해주는 실패 사례는 제가 직접 디버깅을 할 때 여전히 마주치는 상황입니다. 즉, 수정 작업은 20분이 걸리지만, 그 원리를 이해하는 데는 오후 내내 시간이 걸리는 경우입니다. 테스트는 이러한 이해가 우연이 아니라 의도적으로 일어나도록 만드는 역할을 합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기