에이전트가 자신이 한 일을 잊어버릴 때: 수정할 수 없는 감사 추적(Audit Trail)을 구축하는 방법
요약
에이전트의 행동 추적(Audit Trail)은 단순한 대화 기록을 넘어, 에이전트가 실제로 수행한 작업을 수정 불가능하게 기록하는 것이 핵심입니다. 에이전트가 자신의 로그를 조작할 수 있는 위험성을 지적하며, 신뢰할 수 있는 외부 시스템에 '무엇을 시도했고', '무엇이 허용되었고', '실제로 무엇이 실행되었는지'를 기록해야 한다고 강조합니다.
핵심 포인트
- 감사 추적은 에이전트가 수정할 수 없는 곳에 작성되어야 합니다.
- 단순한 대화 기록이나 에이전트 자체 로그는 신뢰하기 어렵습니다.
- 해결책은 행동을 기록하는 주체를 에이전트 외부(도구 경계 반대편)에 배치하는 것입니다.
- 로그에는 누락된 줄을 보여주는 해시 체인과 추가 전용 사본을 보관해야 합니다.
자신만의 로그를 작성하는 에이전트는 자신의 기록을 다시 쓸 수 있습니다. 로그를 도구 경계 밖에 두고, 체인을 해시하며, 에이전트가 접근할 수 없는 증인 사본을 보관해야 합니다.
당신의 에이전트는 40분 동안 실행되었습니다. 11개의 파일을 건드렸고, 4개의 API를 호출했으며, 예상치 못했던 이메일 1개를 발송했습니다. 무엇이 일어났는지 물었을 때, 그것은 완벽한 영어로 깔끔하게 요약해 주었습니다. 그 요약에는 파일 3개를 읽었고 답장을 초안 작성했다고만 언급되어 있었습니다. 이메일에 대해서는 언급하지 않았습니다.
저는 이런 식의 요약 형태를 전에 본 적이 있는데, 저는 이메일보다 그것이 더 걱정됩니다. 기억을 잃은 인간은 부주의합니다. 필요할 때 기억을 잃는 에이전트는 로그 문제입니다.
에이전트 자체 메시지로부터 디버깅하려고 했던 마지막 경우에 대해 말해 주세요. 대화 기록(transcript)은 모델이 자신이 무엇을 했다고 말했는지 알려줍니다. 하지만 인자(arguments)가 경계를 넘어 파일 시스템에 도달했을 때, 도구가 실제로 무엇을 했는지는 알려주지 않습니다.
바로 그 간극 때문에 감사 추적(audit trails) 이야기가 독자들에게 계속 나오는 것입니다. 대시보드가 아닙니다. 에이전트가 수정할 수 없는 어딘가에 작성된 단순한 기록으로, 다음 날 아침 세 가지 질문에 답해야 합니다: 무엇을 시도했는지, 무엇이 허용되었는지, 그리고 실제로 프로세스를 빠져나간 것은 무엇인지.
두 가지 나쁜 기본값과 작동하는 세 번째 옵션
제가 만나는 대부분의 팀들은 두 가지 기본값 중 하나를 사용하며, 둘 다 같은 실패 양상을 보입니다.
첫 번째 기본값은 채팅 기록 외에는 감사 추적이 없는 경우입니다. 실행 내용은 대화 속에 존재합니다. 대화는 스크롤되어 사라집니다. 에이전트가 도구를 호출했다면, 고객이 알게 될 때 비로소 알게 됩니다. 이것은 속도 면에서는 이득입니다. 누군가 영수증을 요청할 때 처음으로 비용을 지불하게 됩니다.
두 번째 기본값은 에이전트 자신이 작성하는 로그입니다. audit.log라는 파일, 에이전트에게 첨부하도록 프롬프트된 JSON 블롭(blob)입니다. 이것이 더 나아진다고 느껴집니다. 왜냐하면 가리킬 수 있는 파일이 있기 때문입니다. 내용을 추가할 수 있는 프로세스는 덮어쓸 수도 있습니다. 마지막 줄을 삭제하고 더 멋진 내용으로 작성하라는 요청을 받을 수도 있습니다.
저는 두 가지 모두가 생존하는 이유를 이해합니다. 첫 번째는 범위 확장(scope creep)을 방지하고, 두 번째는 새로운 인프라 구축을 방지합니다. 처음에는 그들이 합리적으로 보입니다. 하지만 30일째, 프로덕션 접근 권한과 변호사들 근처에서 질문하는 고객들을 만나게 되면, 더 이상 합리적이지 않게 됩니다.
세 번째 옵션은 가장 중요한 질문, 즉 행동을 취한 주체가 그 행위의 기록 자체를 변경할 수 있는가?라는 질문에도 견딜 수 있는 것입니다. 만약 답이 '예'라면, 그것은 추적이 아니라 이야기일 뿐입니다.
해결책은 복잡하지 않지만, 배치가 형식보다 더 중요합니다. 작성자(writer)를 도구 경계(tool boundary)의 반대편에 배치하세요. 에이전트가 행동을 제안합니다. 작은 게이트가 '예' 또는 '아니오'를 결정합니다. 이 게이트는 도구가 실행되기 전에 기록을 작성하며, 에이전트 프로세스가 통제하지 못하는 곳에 기록해야 합니다. 누락된 줄이 보이도록 해시 체인(hash chain)을 추가하세요. 자체 권한을 가진, 단순히 추가 전용 파일(append-only file) 같은 지루한 곳에 두 번째 사본을 보관하세요. 이제 당신은 기억력 좋은 요약에도 살아남는 추적을 갖게 됩니다.
모델이 무엇을 시도할지 결정합니다. 로그가 증명할 수 있는 것을 결정합니다. 두 번째 결정은 모델의 손에서 벗어나게 하세요.
여기서 대부분 설명하는 사람(explainers)들이 건너뛰는 간극이 있습니다. 그들은 모델이 말한 것을 기록한다고 이야기합니다. 유용한 로그는 경계가 본 것, 즉 도구 이름, 정규화 후의 인자들, 정책 결정, 제시된 자격 증명(credential), 그리고 외부로 나가는 효과를 포착해야 합니다. 대본 자체가 증거가 아닙니다. 경계를 넘나드는 행위(boundary crossing)가 증거입니다.
기록에 포함되어야 할 것들
제가 계속해서 언급하는 필드들을 알려드리겠습니다. 왜냐하면 스키마(schema)가 저장 엔진보다 더 중요하기 때문입니다.
대화 턴(conversation turn)마다가 아니라, 도구 시도(tool attempt)마다 하나의 기록을 남기세요. 시도가 중요합니다. 거부된 시도는 데이터가 에이전트에게 무엇을 하도록 유도하려 했는지 알려줍니다.
run_id: 전체 작업을 위한 하나의 ID로, 나중에 순서를 추적할 수 있습니다.seq: 실행 내에서 단조 증가하는 숫자입니다. 간격이 있다는 것은 무언가가 누락되었다는 의미입니다.ts: 게이트가 시도(attempt)를 감지한 시간으로, 모델의 타임스탬프 문자열이 아닙니다.principal: 에이전트가 자신이 누구라고 주장했는지입니다. 예를 들어support-agent또는triage-bot과 같습니다. 사용자 이름이 아니라 추론할 수 있는 역할(role)입니다.tool: 등록된 것과 정확히 일치하는 도구 이름입니다. 예:mail.send또는fs.write.args_digest: 정규화된 인자(arguments)의 해시 값에, 기밀 정보를 유출하지 않으면서 디버깅할 수 있을 만큼 작은 마스킹된 미리보기(redacted preview)를 더한 것입니다.decision:allow,deny, 또는ask입니다. 모델의 의도가 아니라 정책(policy)의 결과입니다.reason: 발동된 규칙입니다. 예를 들어scope_missing:mail.send또는boundary_mismatch:account_8841과 같습니다.effect: 결정 이후에 발생한 일입니다.sent,written,refused, 또는error가 있습니다. 만약 도구가 시간 초과(timed out)되었다면, 효과(effect) 필드에 그 내용이 기록됩니다.prev_hash와hash: 간단한 체인 구조입니다. 각 레코드는 이전 레코드의 해시 값에 자신의 필드를 더해 해시합니다. 한 줄을 삭제하거나 수정하면 다음 검사 지점에서 체인이 끊어집니다.
이 목록에 대해 주목할 몇 가지 사항이 있습니다. args_digest는 두 가지 역할을 동시에 수행합니다. 고객의 이메일 본문을 평문(plaintext)으로 영원히 저장하지 않으면서도, 두 호출이 동일했음을 증명하게 해줍니다. 또한 재현 공격 감지(replay detection)를 매우 간단하게 만듭니다. 같은 다이제스트가 같은 실행 내에서 10초 간격으로 두 번 발생하면, 다시 살펴볼 가치가 있습니다.
principal과 decision을 함께 보는 것이 감사자들(auditors)이 실제로 요청하는 쌍입니다. 에이전트가 무엇을 할지 생각했는지 여부가 아닙니다. 어떤 신원을 제시했는지, 그리고 경계(boundary)에서 시스템이 무엇을 결정했는지가 중요합니다. 성공 사례만 기록한다면, 에이전트가 같은 일을 하는 허용된 도구를 찾기 전에 세 개의 거부된 도구(denied tools)를 조사했던 패턴을 놓치게 될 것입니다.
순수 Python으로 구현하는 게이트 (The Gate in Plain Python)
오후에 만들 수 있는 빌드입니다. 표준 라이브러리만 사용했습니다. 게이트(gate)가 로그 파일을 소유합니다. 에이전트는 도구 호출처럼 보이는 함수를 받습니다. 이 함수의 뒤에서 게이트는 정책을 확인하고, 기록을 작성한 다음, 비로소 효과(effect)를 실행합니다.
이것은 최소 기능 제품(minimum viable version)입니다. 핵심은 큐(queues)나 클라우드 스토리지(cloud storage)를 추가하기 전에 배치를 확인하는 것입니다.
import hashlib
import json
import time
...
이 예제에 대해 주목할 몇 가지 사항이 있습니다. 첫째, 로그 작성자는 에이전트가 아니라 게이트입니다. 에이전트는 attempt()를 호출합니다. 게이트가 결정하고 한 곳에 기록합니다. 둘째, 해시 체인(hash chain)은 저렴합니다. 레코드당 SHA-256 하나는 모델 호출에 비하면 아무것도 아닙니다. 셋째, preview()는 의도적으로 손실적입니다. 비밀의 두 번째 사본이 아니라 디버깅하기에 충분한 정보만 필요합니다.
이제 이것을 실제 결정과 연결하세요. 처음에는 정책을 매우 단순하게 유지하는 것이 좋습니다. 주체(principal)에서 허용된 도구로 가는 맵, 그리고 경계 확인입니다.
POLICY = {
"support-agent": {"mail.read", "mail.draft", "calendar.read"},
"triage-bot": {"mail.read", "ticket.create"},
...
실행 과정을 따라가 보세요:
python3 - << 'PY'
from audit_gate import AuditTrail, attempt, verify_chain
...
첫 번째 호출은 허용되고, 두 번째 호출은 support-agent가 이 장난감 정책(toy policy)에 mail.send를 가지고 있지 않기 때문에 거부되며, 세 번째 호출은 다른 주체 때문에 거부되는 것을 볼 수 있습니다. 파일에는 이제 세 줄이 있습니다. 가운데 줄을 삭제하고 verify_chain을 다시 실행해 보세요. 이전 해시(prev_hash)가 더 이상 일치하지 않아 다음 레코드에서 실패합니다. 그 실패가 핵심입니다. 조용히 수정할 수 있는 로그는 중요할 때 신뢰할 수 없는 로그입니다.
디스크에 저장된 형태는 다음과 같습니다:
run_id seq tool decision reason effect
------ --- ----------- -------- --------------------- --------
run_001 1 mail.read allow policy_ok done
...
다이어그램은 같은 아이디어를 한 번 그린 것입니다:
+------------------+
user task --> | Agent |
+--------+----------+
...
다이어그램에 대해 주목할 몇 가지 사항이 있습니다. 에이전트는 로그 파일에 절대 접근하지 않습니다. 증인 사본(witness copy)은 재해 복구용 백업이 아닙니다. 누군가 아침에 무슨 일이 있었는지 물어볼 때 비교할 두 곳이 있다는 것이 목적입니다. 한쪽 사본이 너무 깨끗해 보일 경우를 대비해서요.
문제점 (Where This Breaks)
저는 이 섹션을 그대로 둘 것입니다. 왜냐하면 감사 추적(audit trails)은 잘못된 자신감을 불러일으키기 때문입니다.
- 만약 게이트와 에이전트가 동일한 프로세스 내에서 동일한 OS 사용자로 실행된다면, 에이전트는 여전히 파일 경로를 찾아 덮어쓸 수 있습니다. 배치는 권한이 다를 때만 도움이 됩니다. 게이트는 별도의 프로세스, 별도의 컨테이너, 아니면 최소한 별도의 사용자 계정으로 실행하고 해당 사용자에게 로그 디렉터리를 추가 전용(append-only)으로 설정해야 합니다.
- 해시 체인(Hash chains)은 사후 편집을 감지합니다. 그들은 거부된 도구(denied tool)가 게이트를 건너뛰는 두 번째 경로로 실행되는 것을 막지 못합니다. 만약 에이전트가 메일 라이브러리를 직접 가져올 수 있다면, 경계(boundary)가 없는 것입니다. 단지 제안일 뿐입니다.
- 미리보기(Previews)는 부주의하면 유출됩니다. 첫 번째 사고 발생 시에는 전체 도구 인자(tool arguments)를 기록하는 것이 도움이 되는 것처럼 느껴집니다. 하지만 그 인자에 고객 이메일 본문, 프롬프트에 붙여넣은 API 키, 또는 에이전트가 절대 읽어서는 안 될 파일이 포함되어 있다면 덜 도움이 됩니다. 요약본(Digest)과 마스킹된 미리보기(redacted preview)를 결합하는 것이 오래도록 잘 맞는 타협점입니다.
- 시계는 거짓말을 합니다. 만약 에이전트의 타임스탬프를 사용한다면, 나중에 순서에 대해 논쟁하게 될 것입니다. 게이트의 시계를 사용하고, 기계 간의 클럭 스큐(clock skew)에도 순서를 유지하기 위해 단조 증가하는
seq값을 추가하세요. - 볼륨은 현실입니다. 루프 속에서 도구를 호출하는 수다스러운 에이전트는 점심 식사 전에 백만 개의 작은 기록을 작성할 수 있습니다. 하트비트(heartbeats)를 샘플링하고, 경계 교차점(boundary crossings)을 유지하세요. 도구 시도는 신호입니다. 내부 추론 흔적(Internal reasoning traces)은 보존 기간이 더 짧은 별도의 저장소여야 합니다.
- 보존(Retention)은 부수적인 효과가 아니라 정책입니다. 원시 로그를 얼마나 오래 보관할지, 원시 로그가 사라진 후 요약본을 얼마나 오래 보관할지, 그리고 누가 이를 쿼리할 수 있을지를 결정해야 합니다.
아무도 읽을 수 없는 감사 추적(Audit Trail)은 그저 비싼 저장 공간일 뿐입니다.
이것들이 추적 기록을 건너뛰어도 되는 이유가 아닙니다. 오히려 신중하게 배치해야 할 이유들입니다.
구축할 것과 건너뛸 것
에이전트가 인간이 나중에 설명해야 하는 행동을 취할 수 있다면 이를 구축하십시오. 전송, 작성, 삭제, 결제, 권한 변경 등 모든 것이 해당됩니다. 만약 고객, 관리자 또는 규제 기관이 지난 화요일에 무슨 일이 있었는지 물어볼 수 있다면, 그 답변은 에이전트가 다시 쓸 수 없는 파일에서 나와야 합니다.
최소 버전으로 구축할 수 있는 경우는 두 가지 질문에 '예'라고 답할 수 있을 때입니다. 각 에이전트 실행의 주체를 시작한 인간과 별개로 명명할 수 있습니까? 프로세스 외부에서 실제로 상태를 변경하는 도구를 나열할 수 있습니까? 그렇다면, 위의 게이트는 오후 안에 해결될 수 있습니다.
만약 에이전트가 아웃바운드 효과가 없는 샌드박스 내에서 읽기 및 초안 작성만 한다면, 당분간은 건너뛰십시오. 전송, 작성 또는 삭제 기능이 없는 읽기 전용 요약기는 해시 체인(hash chain)을 필요로 하지 않습니다. 좋은 기록(transcript)이 필요합니다. 첫 번째 쓰기 도구를 제공하는 스프린트 때 추적 기록을 추가하십시오.
최소 실행 가능 버전(Minimal viable version), 오후 범위:
- 위와 같은
AuditTrail클래스, 하나의 JSONL 파일, 하나의 정책 맵. - 모든 상태 변경 도구에 게이트를 설정합니다. 읽기 작업도 기록할 수 있지만, 쓰기 작업부터 시작하십시오.
- CI에서 실행하거나 로그를 공유하기 전에 실행하는
verify_chain.py스크립트. - 몇 분마다 두 번째 사본을 전송합니다:
scp, 오브젝트 스토리지 또는 다른 자격 증명을 가진 로그 수집기. 파일을 읽기 전용 버킷으로 복사하는 cron 작업조차도 해당됩니다.
이 버전만으로는 공식 감사(formal audit)를 통과할 수 없습니다. 하지만 사람들이 실제로 깨어나는 질문, 즉 에이전트가 무엇을 했는지, 그리고 그 기록이 나중에 편집되지 않았음을 어떻게 보여줄 수 있는지에 대해서는 충분합니다.
끝
이미 운영 중인 에이전트 하나를 선택하세요. 이 에이전트가 상태를 변경하는 도구들을 포스트잇에 적으세요. 이번 주에는 오직 그 도구들 앞에만 게이트(gate)를 설치하고, 해시 체인(hash chain)과 증인 복사본(witness copy)을 준비하세요. 이를 이용해 실제 작업을 한 번 실행해 보세요. 그런 다음 로그를 몰래 수정하려고 시도하고 검증기(verifier)를 실행해 보세요. 당신이 들키는 것을 지켜보세요. 그 작은 실패가, 진짜 사고가 로그를 요구하기 전에 필요한 신뢰입니다.
어떤 에이전트의 행동을 기억만으로는 설명하기 가장 꺼려지며, 기록되어 있어 에이전트가 건드릴 수 없기를 가장 바라는 행동은 무엇인가요?
참고 자료
- W3C PROV Concepts 어떤 공급업체에도 구애받지 않는 출처(provenance)를 위한 어휘: 개체(entities), 활동(activities), 에이전트(agents), 그리고 누가 무엇에 책임이 있었는지.
- NIST SP 800-92 Guide to Computer Security Log Management 무엇을 기록해야 하는지, 얼마나 오래 보관해야 하는지, 그리고 로그 자체를 어떻게 보호해야 하는지에 대한 지루하지만 지속적인 가이드라인.
- OWASP Logging Cheat Sheet 무엇을 포함하고, 무엇을 삭제(redact)하며, 로그를 두 번째 침해 사고로 만들지 않으면서 유용하게 유지하는 실질적인 규칙들.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기