
AI 에이전트 가드레일을 구축했더니 관찰자가 자기 자신을 관찰하고 있다는 사실을 발견했다
요약
AI 코딩 에이전트의 비용 폭주와 범위 이탈을 방지하기 위해 실행 계약(execution contract)을 기반으로 동작을 예측하고 모니터링하는 'Preflight' 프로토타입을 소개합니다. OpenTelemetry와 SigNoz를 활용하여 에이전트의 실제 동작이 사전에 정의된 기대치 내에 있는지 측정하는 메커니즘을 다룹니다.
핵심 포인트
- 에이전트 작업 전 모델 티어와 예상 파일 점유율을 예측하는 실행 계약 생성
- In-process interception과 External watch 두 가지 런타임 모드 제공
- OpenTelemetry를 통한 에이전트 동작의 관찰 가능성(Observability) 확보
- 가드레일 구축 시 가드 자체의 메타데이터가 비용에 포함될 수 있음을 주의
반응형 AI 비용 모니터링에는 어색한 한계가 있습니다. 차트에 폭주하는 에이전트가 나타날 때쯤이면, 토큰은 이미 다 써버린 상태라는 점입니다. 저는 SigNoz 해커톤을 위해 다른 아이디어를 테스트해보고 싶었습니다. AI 코딩 에이전트가 작업을 시작하기 _전_에 작은 실행 계약 (execution contract)을 만들고, 그 실제 동작이 해당 계약 내에 머무는지 측정하는 것입니다.
그것이 바로 Preflight가 되었습니다. 코딩 작업이 주어지면, Preflight는 모델 티어 (model tier), 노력 수준 (effort level), 그리고 예상 파일 점유율 (expected file footprint)을 예측합니다. 이러한 예측은 관찰 가능한 기대치 (observable expectations)가 됩니다. 에이전트가 파일을 작성할 때, Preflight는 범위 이탈 (scope drift)을 측정하고, OpenTelemetry를 방출하며, 그 결과를 SigNoz에 나타냅니다. 이것은 의도적으로 프로토타입으로 만들어진 것이지, 범용 보안 샌드박스 (security sandbox)가 아닙니다. 유용한 점은 예측, 관찰된 행동, 그리고 예측 실패 사례를 모두 검사할 수 있다는 것입니다.
가장 가치 있는 교훈은 순탄한 과정에서 얻은 것이 아니었습니다. 저의 외부 관찰자 (external observer)가 자신의 툴링 메타데이터 (tooling metadata)를 에이전트의 작업으로 카운트하고 있다는 사실을 발견했을 때 얻었습니다.
에이전트보다 먼저 계약이 존재한다
'로컬 변수 하나 이름 바꾸기'와 같은 작업이 '결제, 보고, 마이그레이션, 테스트 및 배포 문서 전반에 걸쳐 테넌트 인식 권한 부여 (tenant-aware authorization) 도입하기'와 동일한 모델 예산이나 저장소 전체 범위 (repository-wide scope)를 자동으로 할당받아서는 안 됩니다. Preflight는 먼저 작업을 분류하고 간결한 계약을 생성합니다:
| 계약 필드 (Contract field) | 예시 |
|---|---|
| 티어 (Tier) | small / medium / large |
| ... |
저는 이 작은 JSON 결정을 위해 250 completion tokens로 제한된 저렴한 gpt-5.6-luna 구조화된 분류 호출 (structured classification call)을 사용합니다. 이는 의도적인 것입니다. 가드 (guard) 역시 다른 에이전트들에게 권장하는 '적절할 때 저렴하게 예측하라'는 원칙을 스스로 실천해야 하기 때문입니다.
그 후 Preflight는 두 가지 정직한 런타임 모드 (runtime modes)를 가집니다:
- **In-process interception (프로세스 내 가로채기)**는 Preflight의 내장 에이전트가 실행되는 동안 그 동작을 평가합니다. 드리프트 (drift)가 높아지면 실행을 일시 중단하고 사람이 확인한 수정 사항 (remediation)을 제안할 수 있습니다.
- **External watch (외부 감시)**는 다른 코딩 CLI를 실행하기 전에 예측하고, 선택된 워크스페이스 내의 쓰기 및 삭제 작업을 재귀적으로 관찰하며, 프로세스가 종료된 후 드리프트 (drift)를 계산합니다. 이 모드는 사후적 (post-hoc)이며, 자식 프로세스 (child process)를 선제적으로 차단할 수는 없습니다.
이러한 차이는 중요합니다. 파일 시스템 와처 (filesystem watcher)는 의미론적 안전성 (semantic safety)을 증명할 수 없으며, 수명이 짧은 파일을 볼 수 없거나, 감시 중인 워크스페이스 외부의 경로를 보호할 수 없습니다. 이 모드들을 동일하다고 부르는 것은 데모를 실제보다 더 강력해 보이게 만들 뿐입니다.
SigNoz가 보는 것
저는 스팬 이름 (span names)에 작업 텍스트나 모델 이름을 넣는 대신, 안정적인 OpenTelemetry 스팬 이름들을 내보냈습니다:
preflight.predict
-> preflight.execute
-> preflight.action.evaluate
...
속성 (Attributes)에는 세션 ID, 예측된 티어 (tier), 런타임 모드 (runtime mode), 작업 대상, 판결 (verdict), 그리고 드리프트 점수 (drift score)와 같이 변하는 세부 정보가 담깁니다. 메트릭 (metrics)은 분류 비용 (classifier cost), 항상 큰 기준선 (baseline) 대비 모델링된 절감액 (modeled savings), 티어 분포, 작업 횟수, 그리고 드리프트를 다룹니다. '모델링된 절감액'은 명시적으로 비교 가정을 의미하며, 과금에 대한 주장(billing claim)이 아닙니다.

저는 여러 SigNoz 기능을 통해 이러한 신호들을 사용했습니다:
- 분류 비용, 드리프트, 라우팅 티어, 정책 판결, 세션 비용, 그리고 모델링된 절감액을 위한 Preflight 대시보드;
- 범위 드리프트 (scope drift), 비용 급증, 그리고 하드 상한선 (hard ceiling)에 대한 세 가지 경고 알림 규칙;
preflight.predict -> preflight.execute -> preflight.drift.check -> preflight.complete구조를 가진 트레이스 퍼널 (trace funnel);- 실제로 높은 드리프트를 보이는 티어를 찾기 위한 쿼리 빌더 (Query Builder) 집계 (aggregation).
대시보드 쿼리는 단순한 스크린샷이 아니었습니다. 저장된 v5 쿼리에서 저는 avg(preflight.drift_score)를 계산하고, preflight.predicted_tier별로 그룹화(group by)한 뒤, HAVING avg_drift_score > 0.5를 적용했습니다. 현재 데이터에서는 평균 드리프트(drift)가 0.542인 중간 티어(medium tier)만이 나타났습니다. 이것은 합성 지표(synthetic metric)가 아닌 실제 쿼리 결과입니다.

3개 세션의 라이브 배치(live batch)가 예측(predict), 실행(execute), 드리프트 체크(drift check), 완료(complete)라는 모든 안정적인 퍼널(funnel) 단계에 도달했습니다. 해당 배치의 분류 비용(classification cost)은 $0.001852였습니다. 또한 이를 구축하는 과정에서 SigNoz 통합과 관련된 두 가지 실질적인 세부 사항을 발견했습니다. 대시보드 그룹화(group-by) 시 이전의 groupBy.name 형태가 아닌 groupBy.key가 필요하며, v5 집계(aggregation) 별칭(alias)은 인라인 SQL 스타일의 별칭 지정 대신 별칭 필드(alias field)를 통해 제공해야 한다는 점입니다. 이러한 세부 사항들은 대시보드가 실제로 데이터에 연결되었을 때만 나타나는 것들입니다.
버그: 나의 관찰자가 자기 자신을 관찰하고 있었다
외부 감시(external-watch) 증명을 위해, 저는 설치된 Codex CLI를 래핑(wrap)하여 정확히 하나의 마크다운(Markdown) 파일을 생성하도록 요청했습니다. 첫 번째 원시(raw) 결과는 성공처럼 보였습니다. 감시자(watcher)는 변경된 파일을 감지했고 드리프트(drift)가 상승했습니다.
하지만 요청한 파일은 생성되지 않았습니다.
원인은 Foresight라는 이름의 전역 설치된 Codex 플러그인이었습니다. 이 플러그인은 임시 작업 공간(temporary workspace) 내부에 자체적인 .foresight 계약(contract) 및 히스토리(history) 파일을 작성했습니다. 저의 감시자는 외부 코딩 에이전트가 사용자 코드를 편집한 것처럼, 이러한 내부 파일들을 성실하게 카운트했습니다.
그것은 유효하지 않은 증거였습니다. 저는 실패한 시도들을 저장소(repository)에 남겨두었지만, 이를 성공적인 실행이라고 부르지는 않았습니다.
해결책은 의도적으로 좁게 설정되었습니다:
_INTERNAL_WATCH_DIRECTORIES = frozenset({".foresight"})
relative_path = path.relative_to(self.workspace)
...
또한 저는 증명 실행기(proof runner)가 실패 시 차단(fail closed)되도록 만들었습니다. 실행은 외부 CLI가 0을 반환하고, 요청된 정확한 출력 파일이 존재하며, 그 내용이 예상된 텍스트와 정확히 일치할 때만 수락됩니다. 모든 재실행(rerun)은 새로운 워크스페이스(workspace)를 할당받습니다. 이것은 광범위한 숨김 파일 제외 방식이 아닙니다. 검증된 하나의 플러그인 메타데이터 디렉터리를 필터링하며, 일반적인 프로젝트 파일들은 관찰 가능한 상태로 남겨둡니다.
수정 후, 커밋된 결과에는 두 번의 실제 Codex CLI 실행이 포함되어 있습니다. 각 실행은 요청된 사용자 파일을 정확히 하나씩 생성했고, 각각 종료 코드(exit code) 0을 반환했으며, 3개 파일 계약(contract) 대비 0.33의 드리프트(drift)를 가진 하나의 관찰된 파일 쓰기를 수행했습니다. 두 번의 실제 Luna 예측은 각각 203개의 입력 토큰(input tokens)과 50 및 54개의 출력 토큰(output tokens)을 사용했으며, 총 비용은 $0.001030이 들었습니다.

완벽해 보이는 점수 대신 증거를
저는 평가 이야기가 단순히 기분 좋은 단일 백분율보다 더 유용하기를 원했습니다. 초기 18개 작업(task)으로 진행된 라이브 벤치마크(live benchmark)에서는 티어(tier)와 영향 범위(blast-radius) 레이블 모두에서 61.11%의 정확도를 보였습니다. 이는 실제 약점을 드러냈습니다. 짧은 요청들이 인증(authentication), 검증(validation), 회귀 커버리지(regression coverage), 그리고 내구성 있는 운영 작업(durable operational work)의 비중을 낮게 평가했다는 점입니다.
저는 하나의 범위 루브릭(scope-rubric) 보정(calibration)을 적용하고 동일한 코퍼스(corpus)를 다시 실행했습니다. 결과는 18/18, 즉 100%였지만, 저는 이를 독립적인 검증이 아닌 동일 코퍼스 보정 결과라고 명확히 표시했습니다. 그 다음, 실행하기 전에 별도의 8개 작업으로 구성된 홀드아웃(held-out) 세트를 고정했습니다. 해당 홀드아웃 실행 결과는 티어와 영향 범위 모두 8/8이었습니다. 이는 고무적인 결과이지, 보편적인 보증은 아닙니다.
저장소(repository)에는 v1, v2, 홀드아웃 데이터, 원시 토큰 수(raw token counts), 비용, 실패한 외부 시도, 그리고 성공한 외부 결과가 보관됩니다. 흥미로운 엔지니어링 결과는 '모델이 절대 틀리지 않는다'가 아닙니다. 실수, 드리프트(drift) 이벤트, 또는 고장 난 관찰자가 발생하더라도 다른 사람이 조사할 수 있는 흔적을 남긴다는 점입니다.
과거의 나에게 해주고 싶은 말
- 지출하기 전에 예측하십시오 (Predict before spending). 저렴한 사전 점검 (preflight) 결정이 훨씬 더 비용이 많이 드는 에이전트 실행 (agent run)을 제한할 수 있습니다.
- 런타임 상태를 정확하게 명시하십시오 (State runtime guarantees precisely). 프로세스 내 가로채기 (In-process interception)와 사후 외부 감시 (post-hoc external watch)는 서로 다른 이유로 유용합니다.
- 실패한 증거를 증거로 취급하십시오 (Treat failed evidence as evidence). Foresight 메타데이터 버그가 프로젝트를 더 강력하게 만들 수 있었던 이유는 제가 잘못된 실행 (bad run)을 보관하고, 이를 설명하며, 테스트를 강화했기 때문입니다.
- 관찰 가능성 (Observability)을 반증 가능하게 만드십시오 (Make observability falsifiable). 대시보드 (Dashboards)는 커밋된 로우 데이터 (raw data), 안정적인 스팬 (stable spans), 그리고 명시적인 경계 (explicit boundaries)와 연결될 때 더 가치 있습니다.
Preflight는 에이전트의 범위 (scope)와 비용이 사후 처리 작업이 되기 전에 이를 가시화하려는 저의 시도입니다. 전체 저장소 (repository), Foundry 배포 파일, SigNoz 대시보드 증거, 그리고 벤치마크 결과물 (benchmark artifacts)은 AmanM006/Preflight에서 확인할 수 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기