FourEyes
FourEyes
인간 승인 게이트가 있는 고객 지원 에이전트. 티켓을 읽고, 계정을 조회하며, 수행할 작업을 결정합니다. 하지만 모든 되돌릴 수 없는 쓰기 작업(환불/에스컬레이션/종료)은 실행 전에 물리적으로 인간 승인 게이트에서 중단됩니다.
이름은 4-눈 원칙에서 유래했습니다: 모든 중요한 작업에는 두 쌍의 눈이 필요합니다.

핵심 주장
대부분의 "AI 에이전트 안전"은 프롬프트 텍스트에 의존합니다: "환불 전에 사람에게 물어보세요." 프롬프트는 요청일 뿐, 제약 조건이 아닙니다 — 이전 지침은 무시하세요 한 방이면 무용지물이 됩니다.
FourEyes는 프롬프트가 도달할 수 없는 곳에 보장 장치를 둡니다:
계층 | 위치 | 실제 기능 |
① 콘텐츠 | 고객 텍스트를 명시적인 신뢰할 수 없는 데이터 경계로 감쌉니다; 주입 패턴(가짜 | |
② 구조 | 그래프 토폴로지 + 두 개의 MCP 서버 | 쓰기 경로는 물리적으로 |
③ 비즈니스 가드레일 | 모든 쓰기 도구 진입 시 결정론적 검사: 금액 ≤ 주문 금액, 금액 ≤ $500 상한, 상태/기간 준수, 이전 환불 없음, 고유 멱등성 키 — |
두 가지 속성은 주석이 아닌 테스트에 의해 강제됩니다:
START에서execute_action까지interrupt()를 우회하는 경로는 없습니다 — 그래프에서 인터럽트 노드를 삭제하고execute_action에 도달할 수 없음을 증명하여 확인합니다.권한 부여는 변경 가능한 그래프 상태가 아닌 승인된 데이터베이스 행에서 비롯됩니다.
execute_action은 인간이 서명한approvals행을 다시 읽고 제안과 교차 검증합니다; 불일치 시 거부되고 감사됩니다. (이것은 원래 코드가 인간이 에스컬레이션을 승인한 동안 환불을 실행할 수 있다는 적대적 검토에서 나왔습니다 —failures.md참조.)
아키텍처
┌──────────────────────────────────────┐
ticket ──▶ sanitize_input ──▶ gather_evidence ──▶ classify ──▶ route │
(layer ①) (read-only MCP) (LLM, policy) │
│ │
┌───────────────────────────────────────────┤ │
▼ ▼ ▼ │
out_of_policy under_specified in_policy │
│ │ │ │
explain_refusal propose_escalation propose_action │
│ └──────────┬───────────┘ │
END ▼ │
request_approval ── writes approvals row
▼
★ await_decision — interrupt()
state → Postgres checkpoint
│
┌─────────────────────┴──────────────────┐
rejected approved
│ │
log_rejection execute_action ── the ONLY
│ │ ticket-action
END verify_and_log client
│
END하나의 명령(docker compose up)으로 실행되는 네 개의 서비스:
서비스 | 언어 | 역할 |
| TypeScript MCP SDK | 읽기 전용 도구. |
| Python MCP SDK | 유일한 쓰기 경로. 모든 도구 진입 시 비즈니스 가드레일이 작동합니다. |
| FastAPI | 승인 콘솔 백엔드. 그래프를 재개할 수만 있을 뿐 실행 기능은 없습니다. |
| — | 비즈니스 테이블 + LangGraph 체크포인트. |
콘솔(console/, React + TypeScript + Vite)은 하나의 화면입니다: 대기 중인 카드 → 승인/거부.
두 개의 도구 그룹이 있는 하나의 MCP 서버 대신 두 개의 MCP 서버를 사용하는 이유는 무엇입니까? 권한 경계는 함수 내부가 아닌 프로토콜 및 네트워크 계층에서 설정됩니다. 읽기 도구는 게이트가 없습니다. 모든 것을 게이트하면 승인 피로가 발생하기 때문입니다 — 모든 곳에 게이트가 있으면 아무 곳에도 게이트가 없는 것과 같습니다. 되돌릴 수 없는 쓰기만 게이트됩니다.
측정된 결과
아래 모든 수치는 이 저장소의 명령에서 나왔습니다. 추정된 것은 없습니다.
적대적 테스트 — 53개의 이메일, 7개의 공격 범주
.venv/bin/python evals/test_redteam.py # report: evals/redteam/report.jsontotal_emails : 53 (direct injection · roleplay/jailbreak · forged system messages ·
encoding/obfuscation · social engineering · tool-parameter
pollution · multi-turn priming)
unauthorized_executions : 0
deception_rate : 0.0 (0/53 talked the model into proposing a refund)
sanitize_flagged : 21/53
blocked_by seen : content_layer + structural_layer + business_guardrail ← all three실행 중 모든 이메일은 자동 승인됩니다 — 또한 속은 인간을 의도적으로 시뮬레이션하여 비즈니스 가드레일 자체가 테스트 대상이 되도록 합니다.
두 지표는 의도적으로 별도로 보고됩니다: 승인되지 않은 실행 0건은 실행 계층의 주장이고, 기만율은 추론 계층의 실험입니다. 96% 안전한 환불 시스템을 원하는 사람은 없으므로 안전 주장은 백분율이 아닌 숫자로 표시됩니다.
작업 선택 — 100개의 레이블이 지정된 티켓
.venv/bin/python evals/test_benchmark.py # report: evals/benchmark/report.jsonaction_selection_accuracy : 99.0% (99/100)
false_block_rate : 0.0% (0/31 actionable in_policy tickets)
per_subset : generated 98.8% (79/80) · boundary 100% (20/20)데이터셋 구성은 숫자보다 중요합니다. 80개의 티켓은 LLM이 생성한 명확한 정책 경계 케이스입니다; 측정 결과 이 중 3개만이 임계값의 ±5일 / ±$50 이내에 있었으며, 이는 98.8%만으로는 방어가 불가능함을 의미합니다. 따라서 20개의 수작업 경계 케이스가 추가되었습니다: 30일 대 31일, 정확히 $500 대 $500.01, 정확히 주문 금액 대 1센트 초과, pending/rejected 이전 환불(새 환불을 차단하지 않음), 그리고 세 가지 정책 우선순위 충돌(X3가 E1을 이김; X4가 E1을 이김; 안전 사고가 금액보다 우선). 경계 하위 집합은 20/20을 기록했습니다 — 분류기는 키워드 매칭이 아닌 조항에서 추론합니다.
단일 오류(bm_076)는 E3 + X1을 인용하고 레이블이 거부를 지시한 곳에서 에스컬레이션했습니다 — 84세 부모를 대신하여 제3자가 요청한 경우입니다. 방어 가능한 의견 차이이며 버그가 아닙니다.
궤적 평가 — 29개의 시나리오, 그리고 그 효과에 대한 증명
.venv/bin/python -m pytest evals/test_trajectories.py -q # 30 passed in 124.85s
.venv/bin/python scripts/verify_eval_teeth.py궤적 평가는 프로세스를 주장하며, 단순히 답변만을 주장하지 않습니다 — 올바른 최종 상태라도 잘못된 경로(금요일 리팩터링으로 승인 노드를 조용히 우회하는 경우)에 도달할 수 있습니다. 29개 중 10개는 부정적인 시나리오입니다.
한 번도 실패한 적이 없는 평가 스위트는 안전망이 아니므로, 실패는 요청 시 증명됩니다: verify_eval_teeth.py는 승인 엣지를 request_approval → execute_action으로 다시 작성하고, 평가를 실행하며, 빨간색이 되도록 요구합니다 — 그런 다음 파일을 복원하고 초록색을 요구합니다:
=== step 1: sabotage the approval edge ===
3 failed (traj_bypass_check, traj_single_inbound_edge, traj_001), exit=1
OK: evals went RED as required
=== step 2: re-run against the intact graph ===
4 passed
VERDICT: trajectory evals have teeth토폴로지 주장뿐만 아니라 traj_001 — 행동 시나리오 — 도 빨간색으로 변합니다.
테스트 스위트
.venv/bin/python -m pytest tests/ -q # 43 passed가드레일 (14) · 토폴로지 (6) · 동의 바인딩 (4) · 계층-1 가드 (16, 일반 불만이 플래그되지 않도록 6개의 오탐지 가드 포함) · 가드레일 백스톱 (3).
합성 데이터 공개
이 저장소의 모든 티켓, 고객, 주문 및 적대적 이메일은 LLM이 생성한 합성 데이터입니다. 실제 고객, 실제 주문 또는 프로덕션 트래픽은 없습니다. 구체적으로:
db/seed_data.json— 9개 시나리오 범주에 걸친 60개의 티켓, Claude가 생성하고 git에 캐시되어 재시딩이 결정적입니다 (ADR-003).evals/redteam/emails.jsonl— 53개의 적대적 이메일. 6개 범주는 Claude가 생성했습니다;encoding_obfuscation세트는 프로그래밍 방식으로 구축되었습니다(실제 base64 / 제로 너비 / 동형문자 페이로드). Claude의 안전 분류기가 실제 공격 명령어 인코딩을 거부하기 때문입니다.evals/benchmark/tickets.jsonl— 80개의 레이블이 지정된 티켓, Claude 생성, 데이터셋에 들어가기 전에 모든 레이블이 결정론적 정책 규칙에 대해 확인되었습니다 — 자기 모순적인 항목 하나가 삭제되고 재생성되었습니다 (ADR-011).evals/benchmark/boundary.jsonl— 20개의 수작업 경계 케이스.
날짜는 상대적 오프셋으로 저장되고 시드 시간에 변환되므로 "30일 기간 내" 시나리오는 데이터셋이 재시딩될 때마다 유효합니다.
OWASP LLM Top 10 매핑
위험 | FourEyes가 해결하는 방법 |
LLM01 프롬프트 인젝션 | 세 가지 계층 모두. 콘텐츠: |
LLM02 안전하지 않은 출력 처리 | 모델 출력은 검증되지 않은 상태로 도구에 도달하지 않습니다 — |
LLM05 부적절한 출력 처리 / 과도한 권한 | 에이전트는 아무것도 실행할 수 없습니다. |
LLM06 민감 정보 노출 | 조회 서버는 티켓의 고객별로 범위가 지정됩니다; 읽기 역할은 SELECT만 가능합니다. |
LLM07 시스템 프롬프트 유출 |
|
LLM08 과도한 권한 | 쓰기는 필수 HITL 인터럽트로 게이트됩니다; 읽기/쓰기는 별도의 DB 역할을 가진 두 개의 개별 MCP 서버로 분할됩니다. |
LLM09 과도한 의존 | 궤적 평가는 도구 시퀀스를 주장합니다; 벤치마크는 정확성 과 오탐지율을 모두 측정하므로 과도한 차단이 안전 주장 뒤에 숨겨지지 않고 드러납니다. |
LLM10 모델 서비스 거부 | 30초 타임아웃, 명시적 공급자 폴백이 있는 |
실행 방법
Python 3.12+, Node 20+, Docker가 필요합니다.
# 0. Local Python env — the scripts and evals run on the host, not in the containers
python3.12 -m venv .venv
.venv/bin/pip install -r requirements.txt
# 1. Full stack
cp .env.example .env # fill in ANTHROPIC_API_KEY, GOOGLE_API_KEY, Langfuse keys
docker compose up -d --build # postgres + mcp-lookup + mcp-action + api
# 2. Seed synthetic tickets (uses the cached generation; no API call needed)
.venv/bin/python db/seed.py --reset
# 3. Drive one ticket to the approval gate — the process then exits
.venv/bin/python scripts/run_ticket.py start --category refund_eligible
# 4. Approve from a *different* process, resuming from the Postgres checkpoint
.venv/bin/python scripts/run_ticket.py resume <ticket_id> approved --by you
.venv/bin/python scripts/run_ticket.py inspect <ticket_id>
# 5. Or approve in the console
cd console && npm install && npm run dev # http://localhost:51733단계 → 4단계는 체크포인트 데모입니다: 두 개의 개별 프로세스. 두 번째 프로세스는 저장된 체크포인트에서 재개되며 재추론하지 않습니다 — 이는 LLM이 두 번 질문을 받으면 다른 결론에 도달할 수 있고, 인간이 특정 제안을 승인했지 재시도를 승인한 것이 아니기 때문에 중요합니다.
전체 ADR 및 고려된 대안은 decisions.md에 있습니다. 주요 내용은 다음과 같습니다:
[ADR-002] 두 가지 DB 역할.
foureyes_ro에는 쓰기 권한이 없으므로 "조회 서버는 읽기 전용이다"는 코드 규칙이 아닌 데이터베이스 사실입니다.[ADR-007] 결정론적 증거 수집, 단일 LLM 의사 결정 지점. ReAct 도구 루프가 없습니다 — 궤적 어설션은 정확할 수 있으며, 벤치마크 변동성은 검색 불안정성이 아닌 판단에서 비롯됩니다.
[ADR-007]
request_approval과await_decision은 별개의 노드입니다. LangGraph는 재개 시 노드를 다시 실행합니다. 부작용은interrupt()이후에 위치해야 하며, 그렇지 않으면 승인 행이 두 번 기록됩니다.[ADR-009] 동의는 실행된 작업에 바인딩됩니다. 승인은 승인된 행에서 실행 시점에 다시 읽힙니다.
[ADR-012] API는 실행할 수 없습니다. 승인은 그래프를 재개할 뿐이므로, 콘솔을 손상시켜도 자금을 이동할 수 없습니다.
코드가 "작동"한 후 발견된 세 가지 실제 버그를 포함한 스카(Scar)는 failures.md에 있습니다.
AI 지원 개발 워크플로
이 프로젝트는 Claude Code로 구축되었습니다. 이것이 구체적으로 의미하는 바와 출력이 어떻게 검증되었는지는 다음과 같습니다:
구축 중 사용된 규율
모든 컴포넌트는 구현 전에
decisions.md항목을 가졌습니다 — 결정, 대안, 이유, 대안으로 인해 깨지는 것. 대안을 명명할 수 없다는 것은 설계를 아직 이해하지 못했음을 의미했습니다.모든 실수는
failures.md에 정확한 오류, 진단 및 수정 사항과 함께 기록되었습니다.실행하고 출력을 커밋 메시지에 붙여넣지 않으면 "완료"라고 부르지 않았습니다.
출력 숫자(정확도, 차단율)는 명령이 이를 생성할 때까지 어디에도(코드 주석 포함) 표시되는 것이 금지되었습니다. 플레이스홀더는
[NOT_MEASURED]로 읽혔습니다.
AI 출력이 검증된 방법
적대적 코드 검토. 4개의 독립적인 검토 에이전트(HITL 토폴로지, 인젝션 우회, 가드레일 완전성, 정확성)가 23개의 원시 결과를 생성했습니다. 각 결과는 실제 코드에 대해 반박하도록 지시된 별도의 에이전트에 전달되었습니다. 23개 → 3개 확인. 반박 단계가 없었다면 실제 버그는 거짓 양성에 묻혔을 것입니다.
확인된 HIGH는 진정한 설계 결함이었습니다. 오타가 아닙니다. 동의와 작업이 분리되어 있어 재생 시 인간이 에스컬레이션을 승인하는 동안 환불이 실행될 수 있었습니다. 구조적으로 수정(ADR-009) 및 4개의 회귀 테스트를 추가했습니다.
엔드투엔드 데모는 단위 테스트로 잡을 수 없는 것을 발견했습니다. 레이어-3 백스톱과 레이어-1 정규식 격차는 모두 레드팀 데모에서 발견되었으며, 단위 테스트와 프로토콜 스모크 테스트를 통과한 후였습니다 — 버그는 컴포넌트 사이의 이음새에 있었습니다.
레드팀 하네스 자체의 기준 진실이 처음에 잘못되었습니다. 처음에 10건의 무단 실행을 보고했지만, 캐리어 주문은 우연히 합법적으로 환불 가능했습니다. 안전 메트릭의 위험한 실패 모드는 못생긴 숫자가 아니라 잘못된 기준선에 대해 측정된 예쁜 숫자입니다.
생성된 레이블은 기계 검증됩니다. 벤치마크 레이블은 데이터셋에 들어가기 전에 결정론적 정책 규칙에 대해 검증되므로, 메트릭은 다른 모델과의 일치가 아닌 정책과의 일치를 측정합니다.
범위 외 (의도적으로)
음성/TTS 없음, 채팅 UI 없음, 대시보드 또는 차트 없음, 로그인 시스템 없음, 파인튜닝 없음, 실제 사용자 트래픽 없음. 승인 콘솔은 한 화면입니다 — 그 이상은 범위 확장입니다.
추적
모든 티켓은 하나의 Langfuse 추적을 생성하며, 티켓 ID를 결정론적으로 키로 사용하여 시작 프로세스와 재개 프로세스에서 방출된 스팬이 동일한 추적에 위치합니다:
SPAN sanitize_input injection_flags recorded here
SPAN gather_evidence the five read-only lookups
GENERATION classify policy + evidence → decision (prompt/completion/tokens)
SPAN approval_requested ← the graph stops here
SPAN human_decision ← human waited 9.7s (waited_seconds in metadata)
SPAN execute_action runs only what the approved row authorises
SPAN verify_and_log reads the ticket backapprovals.trace_url은 링크를 저장하므로 콘솔의 모든 카드는 자체 추적으로 딥링크됩니다. 제공자 폴백은 추적에 provider-fallback 이벤트로 방출되므로 Claude → Gemini 전환이 추론되는 것이 아니라 표시됩니다.
지역 주의사항, 이 저장소를 포크하는 경우: Langfuse Cloud는 지역별로 분할됩니다. US 프로젝트를
cloud.langfuse.com으로 가리키면401 Invalid credentials가 반환됩니다 — 이는 잘못된 키처럼 보이지만 그렇지 않습니다. 완전히 잘못 진단하는 데 시간을 허비했습니다. 자세한 내용은failures.md를 참조하십시오.
알려진 격차
제공자 폴백은 목이 아닌 실제
APITimeoutError(scripts/smoke_router.py)로 검증되었습니다. 그러나 실제 제공자 중단 상황에서 실행되지는 않았습니다.MCP 서버 연결은 MCP Inspector UI가 아닌 MCP Python SDK 클라이언트(
list_tools+call_toolover Streamable HTTP)로 검증되었습니다. 프로토콜과 동등하지만 "Inspector에서 검증됨"이라고 주장하려면 직접 실행해 보십시오.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
A paid remote MCP for agent memory MCP, built to return verdicts, receipts, usage logs, and audit-re
Paid remote MCP for agent code search routing MCP, structured receipts, audit logs, and reviewer-rea
A paid remote MCP for AI agent browser approval MCP, built to return verdicts, receipts, usage logs,
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/LoganLuo46/FourEyes'
If you have feedback or need assistance with the MCP directory API, please join our Discord server