로그는 일어나지 않은 일을 기록할 수 없다
요약
AI 에이전트 시스템에서 발생하는 '누락(Omission)' 문제는 로그에 기록되지 않기 때문에 탐지가 매우 어렵습니다. 단순히 기록이 없는 상태는 발생하지 않은 것인지, 아직 발생하지 않은 것인지 모호하기 때문입니다. 이를 해결하기 위해 미결 상태에 만료 기한을 부여하고 종결 이벤트를 강제로 생성하는 설계가 필요합니다.
핵심 포인트
- 로그에 남지 않는 '누락'은 검증 계층에서 가장 다루기 어려운 실패 유형임
- 기록의 부재는 '발생하지 않음', '미발생', '기록 누락'을 구분할 수 없게 만듦
- 침묵이 지속되면 시스템은 이를 정상적인 상태로 오인할 위험이 있음
- 해결책으로 미결 상태에 만료 기한을 부여하고 종결 이벤트를 기록해야 함
AI 에이전트(AI agent)를 중심으로 구축된 모든 검증 계층(verification layer)은 동일한 종류의 핸들, 즉 아티팩트(artifact)를 붙잡으려는 경향이 있습니다.
로그 엔트리(log entry). 검토자 서명(reviewer signature). 도구 결과(tool result). 구조화된 출력 블록(structured output block). 조정자(reconciler)는 하나의 아티팩트를 다른 아티팩트와 비교하여 시스템이 여전히 가이드라인(rails) 내에 있는지 결정합니다.
이는 잔여물을 남기는 실패 사례에는 효과적입니다.
위조된 도구 결과는 거부될 수 있습니다. 일치하지 않는 호출 ID(call ID)는 플래그(flag)가 지정될 수 있습니다. 잘못된 형식의 JSON 블록은 격리(quarantine)될 수 있습니다. 잘못된 페이로드(payload)에 대한 서명은 검증에 실패할 수 있습니다. 이것들은 모두 시스템이 조사할 수 있는 무언가를 생성하기 때문에 다루기 쉬운 실패들입니다.
더 고약한 부류는 아티팩트를 전혀 생성하지 않습니다.
누락(Omission)은 다루기 어렵습니다. 왜냐하면 추가 전용 로그(append-only log)는 여러 상태를 동일한 가시적 상태로 만들기 때문입니다: 즉, 일어나지 않았다, 아직 일어나지 않았다, 그리고 일어났지만 기록되지 않았다. 이 세 가지는 모두 '부재(absence)'로 나타납니다. 로그에는 아무것도 들어있지 않습니다. 감사 쿼리(audit query)는 아무것도 반환하지 않습니다. 탐지기(detector)는 일치시킬 문자열도, 비교할 ID도, 거부할 블록도 없습니다.
부재는 기본적으로 모호합니다.
침묵은 시간이 흐를수록 악화된다
증명 원장(attestation ledger)부터 시작해 봅시다.
에이전트가 작업을 수행합니다: 파일을 편집하거나, 메시지를 보내거나, 티켓을 열거나, 배포를 대기열에 추가합니다. 검토자들은 사후에 해당 작업들을 증명(attest)할 것으로 기대됩니다. 원장은 작업 기록을 저장한 다음, 나중에 검토자 서명이나 승인 이벤트(approval event)를 저장합니다.
서류상으로는 이것이 깔끔합니다: 서명은 쿼리(query)가 가능하며, 각 증명 페이로드(attestation payload)는 해당 작업 해시(action hash)와 대조하여 검증할 수 있습니다.
이제 증명이 누락되었다는 것이 무엇을 의미하는지 물어봅시다.
아마도 검토자가 구두로 작업을 거부하고 아무것도 클릭하지 않았을 수도 있습니다. 아마도 검토자가 아직 확인하지 않았을 수도 있습니다. 아마도 작업이 검토자에게 전달되어야 했으나, 라우팅 규칙(routing rule)이 이를 건너뛰었을 수도 있습니다. 아마도 조직이 서명되지 않은 기록은 아무도 호출(paged)되지 않기 때문에 정상적인 것이라고 조용히 학습했을 수도 있습니다.
원장은 이를 알려줄 수 없습니다.
아무도 증명(attest)하지 않은 기록은 검토자가 아직 확인하지 못한 기록과 바이트 단위로 구별할 수 없습니다. 규모가 커지면, 침묵은 조용히 동의로 변합니다. 대시보드는 여전히 건강한 추가 전용(append-only) 이력을 보여줍니다. 존재하는 서명들은 깨끗하게 검증됩니다. 감사 추적(audit trail)은 포함된 기록들에 대해 무결성을 유지합니다.
누락된 상태가 피해를 입히고 있는 것입니다.
해결책은 침묵에 만료 기한을 부여하는 것입니다. 증명되지 않은 작업은 쿼리(query)가 가능하고 경고(alert)를 보낼 수 있는 양의 상태(positive state)로 노화되어야 합니다. 대기(Pending) 상태는 정의된 검토 창(review window) 내에서만 허용됩니다. 그 이후에는 시스템이 REVIEW_UNRESOLVED, REVIEW_EXPIRED, 또는 REVIEW_REPUDIATED와 같은 종결 이벤트(terminal event)를 추가(append)해야 합니다.
이는 로그에 대한 독자의 관점을 변화시킵니다. 쿼리는 더 이상 승인된 기록만을 요청하지 않습니다. 현재 검토 상태가 approved, repudiated, 또는 unresolved 중 하나인 작업들을 요청합니다. 나쁜 사례에도 이름이 생기는 것입니다.
이것은 미적인 문제가 아닙니다. unresolved라고 명명된 상태는 릴리스 게이트(release gate)를 차단할 수 있습니다. 큐(queue)의 소유자에게 페이지(page)를 보낼 수 있습니다. 대기 상태가 해롭지 않은 중립적인 값인 척하지 않고도 이를 집계할 수 있습니다.
침묵에는 만료 날짜가 필요합니다.
클레임(Claims)에는 출처(provenance)가 필요하다
두 번째 실패는 산문(prose)에서 발생하기 때문에 모습이 다릅니다.
에이전트가
더 똑똑한 위조 출력 탐지기(forged-output detector)도 이 문제를 해결할 수는 없습니다. 문제는 잘 형성된 주장(well-formed claim)에 대한 정의입니다.
세계 상태(world state)에 대한 단언(assertion)이 다운스트림 동작(downstream action)에 영향을 미칠 수 있다면, 반드시 관찰(observation)을 인용해야 합니다. 그 관찰은 도구 결과 ID(tool result ID), 파일 스냅샷 해시(file snapshot hash), 데이터베이스 읽기 이벤트(database read event), 또는 명확한 생성자(producer)를 가진 다른 타입화된 아티팩트(typed artifact)일 수 있습니다. 그러한 인용(citation)이 없다면, 해당 메시지는 운영 목적상 잘못 형성된(malformed) 것입니다.
강제 지점(enforcement point)은 "파일이 비어 있었다"가 사실인지 이해할 필요가 없습니다. 단지 해당 주장이 사용 가능한 참조(reference)를 포함하고 있는지 여부만 알면 됩니다.
단순한 형태면 충분합니다:
{
"claim": "deploy succeeded",
"subject": "service.api",
...
산문(prose)은 여전히 존재할 수 있습니다. 사람들은 산문을 좋아하니까요. 하지만 부수 효과(side effect)를 제어하는 모든 것은 구조화된 주장(structured claim)에 의존해야 하며, 구조화된 주장은 observation_id가 누락되었거나 잘못된 타입의 관찰을 가리킬 때 '실패 시 차단(fail closed)'되어야 합니다.
이는 검증 불가능한 의미론적(semantics) 문제를 인용 누락(missing-citation) 문제로 전환합니다. 인용 누락은 확인(checkable)이 가능합니다.
그 경계가 바로 에이전트 안전(agent safety) 작업이 더욱 날카로워지는 지점입니다. 모델의 텍스트로부터 에이전트가 "정말로 확인했는지" 추론하려고 하지 마세요. 외부 상태에 대한 주장이 그것을 뒷받침하는 관찰(observation)을 명시하지 않는 한, 그 주장이 유효하게 계산되는 것을 불가능하게 만드세요.
인용이 있더라도 주장은 틀릴 수 있습니다. 인용된 도구에 버그가 있을 수 있습니다. 외부 시스템이 거짓말을 할 수도 있습니다. 그것들은 실제적인 문제입니다. 적어도 그것들은 연결된 아티팩트(artifacts)가 있는 문제들입니다.
인용되지 않은 주장은 지식인 척하는 부정적 공간(negative space)일 뿐입니다.
효과 이전의 의도 (Intent before effect)
세 번째 실패는 오래된 것이며, 에이전트 시스템은 이를 더 쉽게 발생하게 만듭니다.
작업자(worker)가 이메일을 보내고, 풀 리퀘스트(pull request)를 열고, 카드로 결제하고, 댓글을 게시하거나, 배포(deploy)를 트리거합니다. 그러고 나서 결과 이벤트(result event)를 추가하기 전에 작업이 종료됩니다.
재생(replay) 시, 로그는 부수 효과가 이미 발생했는지 여부를 알려줄 수 없습니다. 로그에는 결과가 보이지 않기 때문입니다. 맹목적인 재시도(blind retry)는 동작을 두 번 수행할 위험이 있고, 맹목적인 건너뛰기(blind skip)는 동작을 누락시킬 위험이 있습니다.
이벤트 소싱 (Event Sourcing)의 직관적인 버전은 "작업 후에 결과를 추가(append)하라"고 말합니다. 하지만 이는 외부 부수 효과 (external side effects)를 처리하기에는 너무 늦습니다. 위험한 간극은 효과 (effect)와 로그 기록 (log write) 사이에 존재합니다.
해결책은 두 개의 이벤트로 분리하는 것입니다.
먼저 idempotency_key (멱등성 키), 대상, 작업, 그리고 나중에 조정 (reconcile)할 수 있는 충분한 파라미터를 포함하는 INTENT (의도)를 추가합니다. 그 다음 부수 효과를 수행합니다. 마지막으로 외부 참조 또는 에러를 포함한 OUTCOME (결과)을 추가합니다.
이제 로그는 불편한 중간 상태를 나타낼 수 있습니다:
INTENT가 존재하고,OUTCOME이 존재함: 작업이 기록된 최종 상태에 도달함.INTENT는 존재하지만,OUTCOME이 누락됨: 조정 (reconciliation)이 필요함.INTENT가 없음: 아무것도 시도되지 않았어야 하며, 어떠한 외부 흔적이라도 있다면 이는 프로토콜을 벗어난 것임.
그 중간 상태가 바로 핵심입니다. '결과 없는 의도 (Intent-without-outcome)'는 구체적인 작업의 단위를 명명하며, 던져야 할 명확한 질문을 정의합니다.
조정자 (reconciler)는 외부 시스템에 "이 idempotency_key를 가진 작업이 있습니까?"라고 물을 수 있습니다. 만약 있다면, 관찰된 결과를 추가합니다. 없다면, 동일한 키를 사용하여 재시도합니다. 만약 외부 시스템이 키를 통해 답변할 수 없다면, 수동 해결이나 도메인 특화된 보상 작업 (compensating action)으로 격상합니다.
여기에는 정직한 한계가 있습니다. 이 방식은 다운스트림 (downstream) 시스템이 멱등성 키 (idempotency key)를 준수하거나, 이를 통해 조정할 수 있는 충분한 쿼리 표면 (query surface)을 노출할 때만 작동합니다. 만약 대상 시스템이 모든 재시도를 새로운 명령으로 취급하고 안정적인 조회 경로를 제공하지 않는다면, 아무리 로그 규율을 지켜도 당신을 완전히 구할 수는 없습니다.
그 경계가 바로 진짜 설계 문제입니다.
에이전트 시스템 (agent systems)의 경우, 도구 호출 (tool calls)이 외부 상태를 변경할 때 프로세스가 완료되기 전에 종료되어 작업자가 아무것도 기록하지 못하는 상황에서 이 문제가 뼈아프게 다가옵니다. 재생 시스템 (replay system)은 부재 (absence)를 목격합니다. 부재는 증거가 아닙니다.
INTENT 이벤트는 부재에 윤곽을 부여합니다. 이는 시스템이 계획 단계에서 시도된 변경 (attempted mutation) 단계로 넘어간 지점을 표시합니다. 이것이 없다면, 로그는 미래의 코드에게 빈 공간으로부터 역사를 추론하라고 요구하게 됩니다.
알 수 없는 상태는 창고가 될 수 없다
검증되지 않은 주장을 unknown (알 수 없음)으로 표시하는 대시보드가 성공을 가정하는 대시보드보다 낫습니다. 당분간은 말이죠.
에이전트가 저장소(repository) 변경 사항을 검토하고 테스트 통과, 종속성 스캔(dependency scan) 깨끗함, 마이그레이션 생성됨, 롤백 경로 존재와 같은 사실(facts)을 출력한다고 가정해 봅시다. 대시보드는 각 사실이 관찰(observation)을 인용하지 않는 한 녹색(green)을 표시하기를 거부합니다. 누락된 관찰은 unknown으로 표시됩니다.
이것이 정직한 방식입니다. 이는 잘못된 확신을 방지합니다. 하지만 unknown 상태가 결코 해결되지 않는다면 대시보드의 가치는 빠르게 하락합니다.
첫 주에는 unknown이
마감 기한(Deadlines)은 침묵을 종료된 검토 상태(terminal review state)로 바꿉니다. 클레임 스키마(Claim schemas)는 누락된 출처(provenance)를 잘못된 형식의 메시지(malformed message)로 바꿉니다. 의도 이벤트(Intent events)는 "아마 실행되었을 것"이라는 모호함을 "N단계에서 의도가 기록되었으나, 결과(outcome)가 누락됨"으로 바꿉니다. 화해 마감 기한(Reconciliation deadlines)은 누적된 미확인 상태(unknown)를 할당된 작업으로 바꿉니다.
설계 원칙은 대부분의 로깅 가이드라인보다 더 가혹합니다. 시스템이 성공적으로 생성하는 모든 아티팩트(artifact)에 대해, 해당 아티팩트가 누락되었을 때 로그의 독자가 무엇을 보게 될지 자문하십시오.
만약 그 대답이 "아무것도 보이지 않는다"라면, 당신은 최악의 장애가 발생할 바로 그 지점에 사각지대(blind spot)를 가지고 있는 것입니다.
이는 감사(audit) 시스템에도 적용됩니다. 감사인은 체인의 모든 해시(hash)를 검증할 수 있지만, 전체 작업의 3분의 1이 기록을 생성하지 못했다는 사실은 놓칠 수 있습니다. 레드 팀(Red team)은 위조된 도구 출력(tool outputs)이 탐지되는지 확인할 수 있지만, 인용되지 않은 산문(uncited prose)이 증거로 수용되는 사실은 놓칠 수 있습니다. 기존 기록에 대한 무결성(Integrity)이 집합의 완전성(completeness)을 증명하지는 않습니다.
완전성은 누락(omission)이 숨어 있는 곳입니다.
어려운 점은 장애가 발생하기 전에 그 부재(absence)가 표현되어야 한다는 것입니다. 장애가 발생한 후에는 누구나 로그의 빈 공간을 가리키며 기록이 있었어야 했다고 말할 수 있습니다. 그것은 값싼 사후 확신(hindsight)일 뿐입니다. 시스템은 실행되는 동안 그 빈 공간이 의미가 있다는 것을 알아야 합니다.
따라서 부정적인 상태(negative states)를 일급 레코드(first-class records)로 설계하십시오. 이름을 부여하십시오. 소유자(owner)를 지정하십시오. 쿼리(query)에 포함시키십시오. 실패 게이트(fail gates)로 만드십시오.
그렇지 않으면 로그는 아무것도 말하지 않을 것이며, 아무것도 읽히지 않은 채 가장 편리한 대로 해석될 것입니다.
가장 중요한 것이 누락되었을 때, 당신의 시스템은 무엇을 기록합니까?
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기