Skip to main content

Claude Code 세션 정책 엔진 — 에이전트 폭주(재귀 스폰·예산 소진·반복 루프)를 실행 전에 차단

Project description

fusegate

tests PyPI

Claude Code 세션에 다는 정책 엔진. 재귀 스폰, 예산 소진, 반복 루프 같은 에이전트 폭주를 실행되기 전에 차단한다.

이름 그대로다: 퓨즈(fuse)를 단 관문(gate). 과부하가 나면 집 전체가 타기 전에 두꺼비집의 퓨즈가 먼저 끊어진다 — 그 퓨즈를 에이전트 실행 관문에 단다. 세션이 정책 상한을 넘는 순간, 해당 실행만 그 자리에서 끊긴다.

왜 만들었나

2026년 6월, Claude Code에서 서브에이전트가 재귀로 50단계까지 스스로를 복제하며 5분 만에 약 400만 토큰을 태운 사례가 보고됐다(anthropics/claude-code#68619, 아직 OPEN). 같은 시기 AI 에이전트가 프로덕션 DB를 삭제한 사건들도 잇따랐다 — Cursor 에이전트가 Railway API 호출로 DB와 백업을 수 초 만에 지운 건과, Replit 에이전트가 코드 프리즈 중 무승인 파괴를 일으켜 CEO가 공개 사과한 건은 서로 다른 사건이다(언론이 혼동해 보도한 전례가 있어 구분해 적는다 — AI Incident DB #1152, #1469).

피해자들의 결론은 한결같다: 프롬프트에 적은 규칙은 제안일 뿐이다. 강한 제약이 필요하면 시스템 수준의 경계가 필요하다. 모니터링 도구는 많지만 전부 표시 전용이다 — fusegate는 표시하지 않고 강제한다.

무엇을 하나

fusegate init 한 번으로 프로젝트에 훅 4종이 걸리고, 이후 모든 툴 실행 직전(PreToolUse)에 정책을 평가한다.

규칙 내용 스코프
depth 재귀 스폰 깊이 상한 (기본 1 — 아래 실측 근거) 세션
concurrent_session 세션 내 동시 활성 서브에이전트 수 상한 세션
concurrent_global 프로젝트 전역(모든 세션 합산) 동시 수 상한 전역
budget_session 세션 토큰 예산 — 추정 기반(트랜스크립트 파싱) 세션
budget_subagent 서브에이전트 단위 토큰 예산(추정 기반) 에이전트
repeat 동일 툴 호출의 연속 반복 상한 세션
  • enforce / warn 모드 — 규칙별로 고를 수 있다. enforce는 차단하고, warn은 차단 없이 모델이 관측 가능한 경고를 주입해 자가 교정을 유도한다.
  • 예산 위반은 확장만 차단 — Agent 스폰만 막고 Bash·편집 등은 통과시켜, 진행 중 작업의 마무리(커밋·정리)를 잃지 않게 한다.
  • 차단 메시지가 재시도를 막는다 — 차단 시 모델에게 "재시도하거나 다른 방법으로 우회하지 말 것, 정지하고 사용자에게 보고할 것"을 지시한다. 거부→재스폰 패턴(#68619의 악화 경로)을 막는 실측된 문구다.
  • fail-open — 엔진이 어떤 식으로 고장 나도(정책 문법 오류, DB 손상, 버그) 세션은 절대 막히지 않는다. 통과시키고, 경고하고, 기록한다.
  • 위반 텔레메트리 — 모든 판정이 violations 테이블과 events.jsonl에 남는다(차단, 경고, 킬스위치 통과, 엔진 장애 통과를 구분).
  • 관측fusegate status(터미널)와 fusegate dashboard(127.0.0.1 전용, 완전 읽기 전용 로컬 대시보드)로 위반, 활성 에이전트, 예산 추정을 조회한다.
  • 킬스위치FUSEGATE_DISABLE=1 하나뿐이다. 끄는 행위는 항상 명시적이고 기록된다. 몰래 우회하는 경로는 만들지 않았다.

대시보드 화면

위반 목록 타임라인 예산
위반 목록 뷰 타임라인 뷰 예산 뷰

네이티브 상한과 뭐가 다른가

Claude Code에는 이미 전역 env var 상한이 있다: CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS, CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH, CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION. 쓰고 있다면 계속 쓰면 된다 — fusegate는 이 변수들을 건드리지 않고, 둘 다 설정돼 있으면 보수적인 쪽이 먼저 걸린다.

fusegate의 가치는 상한의 유무가 아니라 정책 계층이다: 전역 고정 노브가 아닌 프로젝트별 정책 파일, 규칙별 enforce/warn 선택, 예산과 반복 같은 상한 밖 규칙, 그리고 무엇이 언제 왜 차단됐는지의 위반 텔레메트리 — 벤더가 버그를 고치고 상한을 늘려도 프로젝트마다 다른 "정책"은 남는다.

실측: 무엇이 이 폭주를 실제로 끊나

재귀 스폰 폭주(#68619 서사)를 가드 없음 / depth=2 / depth=1 세 조건으로 대조 재현했다. n=1 실측이며 모든 수치는 추정치다 — 재현 스크립트와 원본 증거는 measurements/에 있다(안전 상한 내장).

스폰 수 차단 중단 지점 토큰(추정)* 비용(추정, CLI 보고)
가드 없음 4 0 자연 종료(안전 상한 내 관측) 783,109 $0.19
depth=2 enforce 5 0 자연 종료 — 차단 실패 1,040,701 $0.25
depth=1 enforce 1 1 정책 차단 310,464 $0.14

* 토큰 수치는 stream-json usage 4개 카테고리 합산이라 캐시 재사용(cache_read)이 포함된 누적 usage다. 신규 처리량이나 절감률 계산의 근거로 읽지 말 것 — 비용 감각은 CLI 보고 USD가 더 정확하다(두 추정 소스의 차이는 measurements/ 문서 참조).

이 실측의 핵심 발견은 차단 성공이 아니라 depth=2의 차단 실패다. 부모-자식 페어링 필드가 훅 페이로드에 없어 깊이를 동시 활성 수로 근사하는데, 부모가 먼저 종료되는 순차 사슬은 이 근사에 걸리지 않는다. 재귀 재스폰을 실제로 막는 설정은 depth=1뿐이었고, 기본값이 1인 이유다. 이 한계는 문서에 공개돼 있다(docs/tracking/findings.md).

설치와 사용

pip install fusegate         # PyPI: https://pypi.org/project/fusegate/
cd <보호할 프로젝트>
fusegate init                # .fusegate/ 생성 + .claude/settings.json 훅 병합(백업, 멱등)

정책은 .fusegate/policy.toml 하나로 조정한다(편집 즉시 적용):

[fusegate]
mode = "enforce"             # 전역 기본: "enforce" | "warn"

[rules.depth]
limit = 1                    # 규칙 테이블을 지우면 그 규칙은 비활성
[rules.budget_session]
limit_tokens = 2_000_000     # 추정 기반
# 모든 규칙에 mode = "warn" 오버라이드 가능

일시 해제: FUSEGATE_DISABLE=1. 위반 확인: .fusegate/events.jsonl.

정직하게 밝혀두는 한계

  • 예산은 추정이다. 트랜스크립트에 기록된 usage를 파싱한 누적치라 실제 청구와 다를 수 있고, 아직 기록되지 않은 사용량만큼 뒤처질 수 있다. 과금 명세로 쓰지 말 것.
  • 깊이는 근사다. 훅 페이로드에 부모-자식을 잇는 필드가 없어 동시 활성 수로 근사한다. limit=1은 정확히 동작하지만("메인의 직속 스폰만 허용"), 2 이상은 순차 사슬형 폭주를 놓칠 수 있다.
  • CLI 2.1.215 실측 스냅샷 기반이다. 훅 계약(이벤트, 필드명, 차단 지점)은 버전업 시 재검증이 필요하다.
  • fail-open이 철학이다. 게이트가 죽으면 보호도 사라진다 — 대신 세션은 절대 막히지 않고, 장애는 반드시 기록·경고로 드러난다.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fusegate-0.1.1.tar.gz (288.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fusegate-0.1.1-py3-none-any.whl (44.1 kB view details)

Uploaded Python 3

File details

Details for the file fusegate-0.1.1.tar.gz.

File metadata

  • Download URL: fusegate-0.1.1.tar.gz
  • Upload date:
  • Size: 288.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for fusegate-0.1.1.tar.gz
Algorithm Hash digest
SHA256 0a6110b6ff66631a03be046d1a002cf6edc5527b7d387555516d23466bd16e56
MD5 76a3f98d672f558878137de1bea86d34
BLAKE2b-256 4d6cbf1c2ff1bb3142a9cddc727f98106971e232f85e998518134ee4e7453ac8

See more details on using hashes here.

Provenance

The following attestation bundles were made for fusegate-0.1.1.tar.gz:

Publisher: publish.yml on calintzy/fusegate

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file fusegate-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: fusegate-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 44.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for fusegate-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a556f538c7c64308eb5e1f17934ecae6238fa40b024b544d9d3c3dfcf900df8e
MD5 bd07bf7cde6454a530390fdead3f79c3
BLAKE2b-256 5ddec422d40197789ae5994a948debc5933ff17e3e4423da7729ecf5fdfd5165

See more details on using hashes here.

Provenance

The following attestation bundles were made for fusegate-0.1.1-py3-none-any.whl:

Publisher: publish.yml on calintzy/fusegate

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page