AI 동반자를 위한 4단계 승인 게이트 구축: 절대로 거절할 수 없는 휴리스틱
요약
본 글은 AI 동반자(AI Companion)의 승인 시스템을 재설계하는 방법을 다룹니다. 기존의 이진적이고 취약했던 게이트를 4단계(Tier 0~3)로 세분화하여, 특히 '파괴적인 행동'에 대한 검증 단계를 강화했습니다. 이를 통해 AI가 사용자의 명시적 승인 없이 민감한 작업을 수행하는 것을 원천적으로 차단합니다.
핵심 포인트
- AI 동반자 시스템은 4단계(Tier 0~3)의 세분화된 게이트를 구축해야 합니다.
- 특히 '보내기', '삭제하기' 같은 파괴적 동사는 항상 사용자 질문을 거쳐야 합니다 (Tier 2).
- 시스템 보호 수준을 낮추는 모든 변경 사항은 반드시 사용자의 명시적인 승인을 받아야 합니다.
- 오류 예산(Error Budget) 관점에서, 오탐지보다 잘못된 행동 전송이 훨씬 위험합니다.
Gmail을 데스크톱 AI 동반자에 연결하는 순간, 저는 제 승인 시스템에 구멍이 있다는 것을 깨달았습니다. 이전 규칙은 이진적이었습니다. readOnlyHint: true로 표시된 도구는 게이트를 건너뛰었고, 나머지는 모두 질문을 받았으며, 신뢰할 수 있는(trusted) 서버는 아예 게이트를 건너뛰었습니다. Composio는 신뢰할 만했기 때문에, 연결된 Gmail은 아무런 프롬프트 없이 메일을 보낼 수 있었습니다. '신뢰한다'는 것은 '연결이 검증되었다'는 의미였지만, 게이트는 이를 '그 행동들이 검토되지 않고 실행될 수 있다'고 취급했습니다. 이것이 구멍이었고, 이를 수정하려면 승인 시스템을 처음부터 재설계해야 했습니다.
Ankita는 제가 만든 오픈 소스(MIT)의 로컬 우선 데스크톱 동반자입니다. 작은 동반자이지만 훨씬 더 많은 것이 가능합니다. 이 프로그램은 실제 기기에서 도구들을 실행합니다: 브라우저, MCP 서버, 셸 명령어. 따라서 승인 게이트는 선택 사항이 아니라 전체 신뢰 모델 그 자체입니다. 제가 구축한 내용을 소개합니다.
두 단계가 아닌 네 가지 단계
src/integrations/mcp-tiers.mjs 파일은 이진 규칙을 네 가지 명시적인 단계로 대체합니다:
- Tier 0 — auto-allow: 읽기 전용 도구. 비용이 들지 않습니다.
- Tier 1 — ask once: 분류되지 않은 도구의 기본값입니다.
- Tier 2 — always ask: 파괴적인 동사(destructive verbs) — 보내기, 삭제하기, 게시하기, 결제하기.
- Tier 3 — deny: 명시적인 블랙리스트 항목을 통해서만 도달할 수 있습니다.
해결 순서는 엄격하고 검사 가능합니다: 블랙리스트 → 개별 도구 규칙 → 개별 앱 규칙 → 앱 기본값 → 휴리스틱. 핵심 속성은 다음과 같습니다: Tier 3은 오직 블랙리스트를 통해서만 도달 가능하다는 것입니다. 이는 휴리스틱이 절대 조용히 거부할 수 없다는 것을 의미합니다. 프롬프트를 에스컬레이션 할 수는 있지만, 거절을 내릴 수는 없습니다.
이는 의도적인 비대칭성입니다. 파괴적 동사 매처는 의도적으로 거칠게 작성되었습니다:
export const DESTRUCTIVE_VERBS = /(send|delete|publish|pay|transfer|remove|destroy|revoke)/i;
부분 문자열 매칭(Substring matching)은 단어 경계(word boundaries)를 고려하지 않기 때문에 payment와 resend 모두 일치합니다. 코드의 주석에 명확히 나와 있듯이: 오탐지(false positive)는 추가 프롬프트 비용만 발생하며, 질문하는 것은 이미 기본값이므로 단어 경계를 사용해서 오탐지(false negative)를 감수할 이유가 없습니다. 잘못된 질문은 사소한 불편함일 뿐이지만, 잘못 전송된 이메일은 되돌릴 수 없습니다. 오류 예산(error budget) 전체는 너무 많이 질문하는 데 쓰입니다.
게이트가 스스로 정책을 세우다 (The gate polices itself)
제가 가장 자랑스러워하는 부분은 lowersProtection()입니다:
/** Tier changes that lower protection are themselves approval-worthy. */
lowersProtection(serverId, previousTier, nextTier) { ... }
이와 관련된 보조 기능은 "Gmail에서 계속 물어보는데, 자동 허용으로 설정할 수 있나요?" 같은 티어 변경을 제안할 수 있지만, 보호 수준을 낮추는 모든 변경 자체는 승인받을 가치가 있는(approval-worthy) 이벤트입니다. 에이전트(agent)가 스스로의 목줄을 조용히 느슨하게 할 수는 없습니다. 느슨해지는 것은 사용자의 눈이 필요하지만, 조여지는 것은 절대 그렇지 않습니다.
오래된 코드에는 언급할 만한 관련 함정이 있습니다: MCP 서버의 trusted 플래그입니다. mcp-manager.mjs에 있는 tierFor()는 다음과 같은 주석을 가지고 있습니다: "trusted는 의도적으로 참조되지 않습니다. 이는 서버 연결이 Ankita에 의해 검증되었다는 의미일 뿐, 그 행동들이 검토 없이 실행될 수 있다는 의미는 아닙니다." 연결에 대한 신뢰가 그것이 취할 수 있는 모든 행동에 대한 신뢰를 의미하지는 않습니다. 모든 호출은 여전히 자체 티어를 해결합니다.
메타 도구는 실제 동작을 숨길 수 없다 (Meta-tools can't hide the real action)
Composio는 COMPOSIO_MULTI_EXECUTE_TOOL과 같은 메타 도구를 노출하며, 이 도구의 _인수(arguments)_가 실제 동작을 명시합니다. 도구 이름으로 분류하면 그것을 "하나의 도구"라고 부르게 되어 Gmail 전송이 포함되어 있다는 사실을 놓치게 됩니다. 따라서 enclosedActions()는 인수를 구문 분석하여 각 슬러그(GMAIL_SEND_EMAIL → { app: "gmail", action: "send email" })로 분할하며, 이때 중요한 티어는 가장 낮은(worst) 포함된 티어입니다. 그리고 사용자가 보는 것은 결코 메타 도구 이름이 아닙니다 — describeCall()은 계획이 명시적으로 COMPOSIO_MULTI_EXECUTE_TOOL이 사용자에게 아무것도 알려주지 않는 반면, "gmail: send email"은 무엇을 승인하는지 알려주기 때문에 "gmail: send email"로 렌더링합니다.
메일은 기본 도구 세트에서 가장 후회할 만한 행동으로 특별 대우를 받습니다: 새로운 Composio 연결은 일반적인 기본값을 상속받는 대신 Gmail이 "항상 질문" 상태로 고정됩니다. 읽기는 무해하게 유지됩니다.
"왜 그런 메일을 보냈지?"에 답할 수 있어야 함
모든 결정은 recordDecision()을 거치며, 이 함수는 허용/질문/거부된 횟수를 서버별로 기록합니다. 예상치 못한 일이 발생했을 때 가장 먼저 하는 질문은 "게이트가 무엇을 결정했고, 왜 그랬는지"이며, 감사 추적(audit trail)이 어깨를 으쓱하는 대신 그 답을 제시합니다.
이 모든 것은 테스트에 의해 고정되어 있습니다: 티어 해상도 스위트(tier resolution suite)는 14/14가 녹색으로 표시되며, 최악의 격리된 티어 규칙, 블록리스트가 모든 것을 능가하는 경로, 읽기 전용 디스커버리 단축을 다룹니다.
제가 계속 돌아오는 설계 결정은 다음과 같습니다: 우리는 의도적으로 휴리스틱(heuristic)을 일방향으로 만들었습니다. 프롬프트 방향으로는 에스컬레이션할 수 있지만, 거부(denial)는 절대 발행하지 않습니다. 커버리지보다 검토 가능성(Reviewability)에 중점을 둔 것입니다. 거부 권한이 있는 휴리스틱은 아무도 검토하지 않았고 아무도 감사 추적을 통해 빠져나갈 수 없는 거절 사례를 조용히 포착할 수는 있겠지만, 그 대가는 너무 큽니다.
저는 과연 적절한 경계가 어디인지 진심으로 확신이 서지 않습니다. 만약 본인 에이전트를 위한 승인 게이트를 구축한다면, 휴리스틱이 거부하는 것을 허용하시겠습니까? 그리고 그것을 신뢰하기 전에 무엇을 요구하시겠습니까?
코드는 원하든 엿보시든 모두 리포에 있습니다: https://github.com/akyourowngames/A.N.K.I.T.A — src/integrations/mcp-tiers.mjs는 286줄이며, 공유 파일 도우미 외에는 의존성이 없습니다. 여러분이 다르게 경계를 그을 수 있는 방법을 듣고 싶습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기