[에이전트] 6. 승인 : 실행 전에 막고, 전부 기록한다 — 감사 가능한 권한 모드

폐쇄망 사내 에이전트 6편 - 승인

작성자

카테고리:

* 이 게시물은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

(폐쇄망 사내 에이전트 ⑥) 승인

1편에서 말했듯 이 물건의 정체는 ‘에이전트이기 이전에 감사 가능한 실행기’다. 규제가 엄격한 환경에서는 “누가 언제 무엇을 허락해서 실행됐는가”에 답하지 못하는 도구는 반입 자체가 안 된다. 이번 편은 그 답을 만드는 승인·감사 체계, 그리고 거기에 클로드 코드식 권한 모드를 얹은 커밋이다.

감사의 핵심은 ‘허락의 주체’를 구분하는 것

모든 도구 실행은 SQLite 감사 DB에 남는다. 중요한 건 남긴다는 사실이 아니라 승인 상태의 해상도다. 에어의 승인 상태는 여섯 가지다.

상태뜻
APPROVED사람이 화면에서 직접 허락
AUTO_APPROVED권한 모드(auto 등)가 대신 허락
RULE_APPROVED저장된 승인 규칙이 허락 (7편)
REJECTED사람이 거부
SKIPPED모드가 승인 없이 거른 것(plan의 거부 등)
NOT_REQUIRED애초에 승인 불필요(읽기 등)

전부 ‘실행됨/안 됨’이 아니라 누가 그 결정을 했는가를 남긴다. 사람이 누른 APPROVED와 모드가 통과시킨 AUTO_APPROVED가 감사 DB에서 구분되지 않으면, 자동화를 켜는 순간 감사가 무의미해진다. 이 구분이 이 프로젝트에서 무엇으로도 무너뜨리면 안 되는 불변식으로 못박혀 있다.

권한 모드 — 편의를 얹되 원칙은 하나

매번 묻는 게 안전하지만, 신뢰가 쌓이면 피로가 된다. 그래서 클로드 코드의 permission mode를 이 구조 위에 옮겼다.

commit c4864ac  (2026-08-15)
feat: 권한 모드 — 감사 가능한 승인 게이트에 클로드 코드식 모드를 얹는다

  원칙은 하나다: 모드는 승인 절차를 조절할 뿐,
  감사는 모드와 무관하게 항상 남는다.
모드동작
plan읽기 전용 — 쓰기·실행·네트워크는 승인조차 없이 거부
default정책이 APPROVAL이면 매번 묻는다
accept-edits파일 편집만 자동 승인 — 삭제·셸·네트워크는 여전히 묻는다
auto승인 전부 자동 통과 — 단 정책 DENY는 여전히 차단

표의 마지막 줄이 핵심이다. auto 모드도 정책 DENY는 못 뒤집는다. 모드는 ‘승인을 접는’ 장치지 ‘정책을 이기는’ 장치가 아니다. 그리고 DESTRUCTIVE(삭제 등) 등급은 accept-edits에서도 묻는다 — 파괴적 동작은 자동화 대상이 아니라는 선을 코드로 그었다.

거부 메시지가 모델을 조종한다

plan 모드의 디테일 하나가 재밌다. 쓰기 요청을 거부할 때 그냥 “거부됨”이 아니라, 거부 사유에 “계획을 한국어로 답하라”를 실어 보낸다. 그러면 모델이 실행을 포기하고 계획 모드로 방향을 튼다. 도구 결과·오류 메시지는 모델이 읽는 컨텍스트라는 것 — 도구 정의 편에서 “에러도 모델이 읽게 쓰라”고 했던 그 원칙이 여기선 거부를 조향 신호로 쓰는 데까지 갔다. 실기 검증에서 plan 모드의 모델이 실제로 “진행하려면 모드를 바꾸라”는 계획 답변으로 전환하는 것까지 확인하고 커밋했다.

코드로 — 상태와 모드의 실제 정의

6종 상태와 4종 모드는 개념도가 아니라 코드다. 감사 레코드의 enum 주석부터 —

// packages/api/src/ToolExecutionRecord.ts
export enum ApprovalStatus {
    NOT_REQUIRED = "NOT_REQUIRED",
    APPROVED = "APPROVED",
    REJECTED = "REJECTED",
    /** 승인이 필요했지만 사전 검증에서 걸러져 묻지 않았다. */
    SKIPPED = "SKIPPED",
    /**
     * 권한 모드(accept-edits·auto)가 묻지 않고 통과시켰다.
     * 사람이 누른 APPROVED와 반드시 구분한다 —
     * "누가 허락했는가"가 감사의 핵심이다.
     */
    AUTO_APPROVED = "AUTO_APPROVED", …
// packages/api/src/PermissionMode.ts — 설명까지 코드가 정본
const MODE_DESCRIPTIONS = {
    "plan":         "읽기 전용 — 쓰기·실행·네트워크는 거부하고 계획만 세운다",
    "default":      "정책대로 매번 승인을 묻는다",
    "accept-edits": "파일 편집은 자동 승인, 삭제·셸·네트워크는 승인",
    "auto":         "승인 전부 자동 통과(감사에 AUTO_APPROVED로 기록, 정책 DENY는 유지)",
};

모드 설명 문자열을 상수 하나로 모아 CLI 배너·모드 전환 안내·진단(doctor)이 같은 문장을 공유하게 한 것도 작은 디테일이다 — 문서와 실물이 어긋나는 흔한 사고를 원천에서 막는다.

적용해 보기 — 감사 스키마 설계 질문 4개

자기 에이전트에 감사를 넣는다면, 테이블을 만들기 전에 이 네 질문에 답해보자.

  1. 실행 거부·차단도 남는가? — 실행된 것만 남기면 “막힌 시도”라는 가장 중요한 신호가 사라진다.
  2. 허락의 주체(사람/모드/규칙)를 구분하는가? — 자동화를 켜는 순간 이 구분이 감사의 전부가 된다.
  3. 기록이 어떤 경로로도 우회되지 않는가? — 실행 코드가 한 지점(2편)이어야 보장된다.
  4. 기록을 사람이 조회할 수단이 있는가? — 남기기만 하고 못 보면 없는 것과 같다.

✅ 확인

  • auto 같은 편의 모드를 켜도 정책 차단이 그대로 사는지 테스트로 고정했다.
  • 파괴적 동작이 어떤 모드에서도 자동 통과되지 않음을 확인했다.
  • 거부 메시지가 모델에게 다음 행동을 알려주는지(plan → “계획으로 답하라”) 확인했다.

한 가지 더 — plan의 거부는 연속 실패 카운터에 세지 않는다. 모드가 막은 것을 모델의 실패로 세면, 정상 동작이 종료 조건을 당겨버리기 때문이다. 상태의 해상도가 이런 데서 값을 한다.

자주 묻는 질문 (FAQ)

자동 승인 모드가 있으면 감사가 의미 없어지지 않나요?

자동 통과는 AUTO_APPROVED로 기록돼 사람이 누른 APPROVED와 구분됩니다. ‘무엇이 실행됐나’뿐 아니라 ‘누가 허락했나’가 남으므로, 자동화를 써도 책임 추적이 가능합니다. 이 구분이 무너지면 그때 감사가 무의미해집니다.

권한 모드와 정책은 뭐가 다른가요?

정책은 ‘무엇이 가능한가'(DENY는 절대 차단), 모드는 ‘가능한 것에 승인을 물을 것인가’입니다. 모드가 아무리 관대해도 정책 DENY를 되살릴 수 없게 층을 나눠야, 편의 기능이 안전선을 침식하지 않습니다.

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다