에이전트를 '착하게' 만들 수는 없다. 프롬프트에 '조심해라', '신중하게 행동해라'를 아무리 써봤자 LLM은 본질적으로 도움이 되고 싶어 한다. 그 본능이 통제되지 않으면 에이전트는 결국 너무 많은 것을 하거나, 너무 많은 비용을 쓰거나, 너무 많은 권한을 사용한다. 문제는 '더 좋은 프롬프트'가 아니라 '더 단단한 경계'다.
최근 dev.to에 올라온 세 편의 글이 이 문제를 서로 다른 레이어에서 다루고 있다. 하나는 에이전트 설계에서 refusal list의 중요성, 다른 하나는 YOLO 모드 에이전트를 컨테이너 격리로 안전하게 운영하는 방법, 마지막은 MCP 도구 예산으로 토큰과 비용을 제어하는 구조다. 세 글을 따로 읽으면 각각의 팁으로 보이지만, 함께 보면 '실행 중인 에이전트를 제어하는 세 개의 레이어'로 수렴된다.
레이어 1: 행동 경계 — 무엇을 거부할 것인가
첫 번째 글의 핵심은 단순하다. 좋은 에이전트는 할 수 있는 것의 목록이 아니라, 하지 않을 것의 목록으로 정의된다. 저자가 처음 만든 코드 리뷰 에이전트는 '시니어 엔지니어처럼 철저하게 리뷰하라'는 프롬프트를 받고, 매번 23개 이상의 지적 사항을 뱉어냈다. 결과는? 아무도 읽지 않게 됐다. 무시되는 에이전트는 없는 것보다 나쁘다. 레이턴시와 비용을 쓰면서 '리뷰는 커버됐다'는 착각만 남긴다.
해결책은 refusal list였다. '자동 수정하지 마라', '네이밍이나 아키텍처 의견은 달지 마라', '설치되지 않은 툴은 pass로 처리하지 말고 skipped로 표시하라'처럼, 모델이 자연스럽게 흘러들어갈 문을 하나씩 닫는 것이다. 그리고 도구 스코핑은 이 거부 목록을 스키마 레벨에서 강제한다. tools: Bash, Read, Grep, Glob — Write와 Edit이 없으면 에이전트는 파일을 수정하고 싶어도 할 수 없다. 프롬프트가 약속이라면, 도구 스코핑은 물리적 제약이다.
레이어 2: 실행 경계 — 컨테이너가 최후의 방어선이다
두 번째 글은 더 과감한 전제에서 출발한다. 에이전트가 완전히 탈취됐다고 가정하자. 그래도 최악의 결과가 'PR 하나 열림'이어야 한다. 이걸 가능하게 하는 건 '조심하는 에이전트'가 아니라 컨테이너 격리 아키텍처다.
구체적인 구성은 이렇다. 에이전트 컨테이너에는 az, aws, gcloud 같은 클라우드 CLI가 없다. 클라우드 작업은 옆에서 돌아가는 별도의 MCP 서버가 토큰을 쥐고 대신 처리한다. 에이전트는 토큰을 볼 수 없으니 탈취당해도 읽기 권한 이상을 쓸 수 없다. 인터넷 접근은 내부 Docker 네트워크로 봉인되고, 유일한 출구는 프록시 컨테이너다. 프록시는 목적지 기반으로 필터링한다 — 요청 방식이 아니라 '어디로 가는가'를 본다. GET 요청에 시크릿을 쿼리스트링으로 실어 보내는 공격도 차단된다. 허용 목록(GitHub, 패키지 레지스트리 등)에 없으면 그냥 막힌다.
현재 알려진 한계도 솔직하게 언급된다. 프록시가 클라이언트가 선언한 호스트명을 신뢰하고 실제 TLS 목적지를 검증하지 않아, 컨테이너 내부 공격자가 다른 호스트로 라우팅할 수 있다. 저자는 이를 인지하고 있고, 다음 버전에서 실제 핸드셰이크 검증을 추가할 예정이라고 밝힌다. 완벽한 설계는 없지만, 어디가 뚫려 있는지 아는 것이 출발점이다.
레이어 3: 비용 경계 — 예산이 없으면 신뢰도 없다
세 번째 글은 MCP 도구 예산 설계를 다룬다. 핵심 통찰은 이것이다: 에이전트는 해킹당하지 않아도 비싸질 수 있다. 도구가 너무 많고, 권한이 모호하고, 지출 한도가 없으면 된다. 프로덕션 트래픽이 들어오면 모델은 잘못된 엔드포인트를 두 번 호출하고, 느린 워크플로우를 재시도하고, 아무도 모르는 사이 토큰 예산을 다 태운다.
해결책은 MCP 도구 예산 레이어다. 워크플로우별로 에이전트가 볼 수 있는 도구를 제한하고(tool visibility), 테넌트별 일일 지출 상한을 설정하고(daily_agent_budget_usd: 25), 위험도에 따라 도구를 분류한다. 읽기는 로깅과 함께 허용, 이메일 발송은 정책 확인 후 허용, 데이터 삭제나 PII 내보내기는 기본 비활성화 또는 사람 승인 필수. 흥미로운 패턴은 prepare_update와 apply_update의 분리다 — 에이전트가 변경안을 초안으로 작성하되, 실제 적용은 사람의 확인이 필요하다. 이것이 현실적인 human-in-the-loop 구현이다.
세 레이어를 함께 설계하라
이 세 가지 제어 경계는 독립적이지 않다. refusal list와 도구 스코핑은 에이전트의 의도 수준을 제한하고, 컨테이너 격리는 실행 수준을 제한하고, 토큰·도구 예산은 비용과 권한 수준을 제한한다. 하나만 있으면 다른 두 곳이 뚫린다.
테크 리드 입장에서 실용적인 시작점은 이렇다. 먼저 팀이 운영 중인 에이전트 각각에 대해 '이 에이전트가 절대 하면 안 되는 것 세 가지'를 명시적으로 문서화하라. 그것이 refusal list의 시작이다. 그다음 에이전트 컨테이너에 클라우드 CLI가 들어가 있는지 확인하라. 들어가 있다면 MCP 서버로 분리하는 작업이 이번 스프린트 과제다. 마지막으로 테넌트별 또는 워크플로우별 지출 상한이 코드에 명시되어 있는지 점검하라. 없다면 운영 중인 에이전트는 지금 이 순간에도 비용과 리스크를 무제한으로 생성 중이다.
에이전트를 도입했다고 해서 통제권을 넘긴 게 아니다. 통제권은 설계하는 것이고, 그 설계는 '무엇을 시킬 것인가'보다 '무엇을 막을 것인가'에서 시작된다.