에이전트는 끝났다고 말했다. 그런데 데이터베이스는 고개를 저었다
말은 매끄러운데 저장이 안 되어 있다면요. Microsoft가 다룬 에이전트의 거짓 완료 문제를 따라가며 호출과 커밋의 차이, 읽기로 확인하는 습관, 멱등성과 원자성, 그리고 운영 가드레일을 정리합니다.
원문 논문 보기
Microsoft가 Hugging Face 블로그에 공개한 글 The Agent Said It Was Done. The Database Disagreed.를 이번 글에서 다룹니다. 데이터베이스 쓰기를 맡은 AI 에이전트가 실제로는 저장이 끝나지 않았는데도 유창한 완료 메시지를 내놓을 수 있다는 지적에서 출발하는데, 원문 링크는 제목 아래에 걸어 두었습니다.
왜 이 실패담이 오래 남았을까
안녕하세요, 패트릭입니다.
여러 사람이 함께 일하다 보면 누군가 "다 했어요"라고 말해서 모두가 안심했는데, 나중에 확인해보니 정작 파일이 저장되지 않은 경우가 생깁니다. 악의가 있어서 그런 것도 아니고, 그 사람은 정말 다 했다고 믿었던 것이죠. 그렇다면 AI 에이전트가 데이터베이스 앞에서 "저장 완료했습니다"라고 말할 때도 비슷한 일이 생길까요.
제가 이 글에서 가장 흥미롭게 본 부분은 바로 그 장면이었습니다. 에이전트는 문장을 참 잘 만들기 때문에, "완료했습니다"라는 말이 나오면 사람도 다음 단계의 에이전트도 그대로 믿고 넘어가기 쉽습니다. 그런데 데이터베이스는 말을 듣지 않고, 거기에는 행이 있거나 없거나 둘 중 하나만 있습니다. 이 간극이 왜 생기는지, 그리고 어떻게 메워야 하는지를 따라가 보겠습니다.

말이 앞서고 저장이 늦는 순간
이런 상황을 하나 상상해 보시죠. 월요일 오전 아홉 시, 고객이 환불을 요청했고 에이전트가 환불 기록을 남기라는 지시를 받았습니다. 에이전트는 도구를 호출했고 화면에는 "환불 기록을 저장했습니다"라고 떴으며, 상담원은 고객에게 문자를 보내고 고객은 안심하고 전화를 끊었습니다. 그런데 오후에 정산 담당자가 조회해보니 해당 행이 없었습니다. 누구의 잘못일까요.
여기서는 조금 조심해서 읽을 필요가 있습니다. 에이전트가 거짓말을 하려고 한 게 아닐 수도 있기 때문입니다. 도구 호출이 중간에 실패했거나 시간이 초과됐거나 아예 실행되지 않았는데도, 응답 문장만 매끄럽게 생성됐을 가능성이 있습니다. 언어 모델은 다음 문장을 자연스럽게 이어 붙이는 데에는 능숙하지만, 그 문장이 바깥 세계의 상태와 일치하는지는 스스로 알 수 없으니까요. 그래서 말과 저장 사이에 틈이 생깁니다.
그렇다면 이 틈은 왜 이렇게 자주 눈에 띄지 않을까요. 대화를 보면 모든 게 정상처럼 보이기 때문입니다. 로그에는 에이전트의 자신감 있는 답변이 남는 반면, 실패한 쓰기는 조용히 사라집니다. 사람은 문장의 유창함을 신뢰의 신호로 읽는 습성이 있어서 "저장했습니다"라는 말을 들으면 굳이 다시 묻지 않습니다. 저는 이 지점이 에이전트 실패 중에서 유독 다루기 까다로운 대목이라고 봅니다. 오류가 시끄럽게 나면 고치기라도 하는데, 이 오류는 조용히 통과하니까요.
호출과 커밋은 다른 사건이다
호출과 커밋 사이에는 선 하나가 있습니다. 에이전트가 데이터베이스 도구를 불렀다는 사실과 트랜잭션이 실제로 커밋됐다는 사실은 서로 다른 사건인데, 호출은 요청이 나갔다는 뜻이고 커밋은 저장소가 그 요청을 받아들여 상태를 바꿨다는 뜻입니다. 그런데 에이전트의 문장 속에서는 이 둘이 자주 하나로 뭉개집니다.
이 차이는 생각보다 중요합니다. 호출과 커밋 사이에는 네트워크 문제, 시간 초과, 권한 부족, 제약 조건 위반, 재시도 과정의 꼬임 같은 변수가 끼어들 수 있는데, 에이전트는 그 중간 과정을 직접 보지 못하기 때문입니다. 도구에서 돌아온 출력이 모호하거나 비어 있으면 모델은 빈자리를 그럴듯한 문장으로 메우는 쪽을 택하기 쉽고, 그 결과 실제로는 아무것도 바뀌지 않았는데 말만 완성되는 셈이 됩니다.
그래서 저는 여기서 숫자보다 그 뒤의 구조를 보고 싶었습니다. 내놓을 성적표는 없지만 분명한 구조적 교훈이 있기 때문입니다. 저장이 됐다는 주장은 오직 저장소 쪽 근거로만 확인할 수 있고, 에이전트의 자기 보고는 어디까지나 주장으로 다뤄야 합니다. 증거는 저장소 쪽에서 와야 하죠. 이 구분이 서 있어야 뒤에 나오는 방법들이 자연스럽게 이해됩니다.

읽어서 확인하는 습관이 필요한 이유
그렇다면 이 틈을 어떻게 메울까요. 이 글에서 제안하는 출발점은 의외로 단순해서, 쓰고 나서 바로 읽어서 확인하라는 것입니다. 같은 에이전트나 바로 뒤따르는 검증 단계가 기대한 행이나 상태 변화가 정말로 있는지를 데이터베이스에 다시 물어보라는 뜻인데, 말하자면 영수증을 받고 나서 금고 문을 직접 열어보는 셈입니다. 저는 이 비유가 괜히 나온 게 아니라고 봅니다. 말은 쉽게 나오지만 저장은 눈에 보여야 믿을 수 있으니까요.
이 방법은 얼핏 보면 너무 당연해 보여서 오히려 놓치기 쉽습니다. 그런데 운영의 눈으로 보면 이 당연한 한 줄이 막아주는 사고가 꽤 많습니다. 실패한 쓰기는 읽는 순간 바로 드러나고, 시간 초과로 애매해진 쓰기도 행이 있는지 없는지로 가려지기 때문입니다. 특히 여러 단계가 이어지는 작업에서는 앞 단계의 저장이 흔들리면 뒤 단계가 전부 어긋나기 때문에, 일찍 확인할수록 수습 비용이 훨씬 싸집니다. 그래서 저는 이 확인을 선택이 아니라 기본값으로 두는 편이 맞다고 봅니다.
물론 여기서 곧바로 다음 질문이 생깁니다. 읽어서 확인하면 모든 게 해결될까요. 그렇지는 않습니다. 읽는 시점과 쓰는 시점이 어긋나면 잠깐 보이지 않는 데이터가 있을 수 있고, 복제 지연이 있는 환경에서는 확인 읽기가 잠시 옛 상태를 보여줄 수도 있기 때문입니다. 그래서 읽기 확인은 만능 열쇠라기보다 매번 묻는 기본 습관에 가깝다고 보는 편이 정확합니다. 매번 묻는 것이 귀찮아 보여도 저장 여부를 말로 단정하기 전에 한 번 더 들여다보는 태도가 에이전트를 훨씬 믿을 만하게 만드니까요.
중복과 반쪽짜리 쓰기를 막는 법
쓰기 확인만큼 중요한 것이 쓰기 자체를 안전하게 만드는 일입니다. 이 글에서 함께 다루는 장치가 바로 멱등성 키, 원자적 트랜잭션, 그리고 적용된 행 수 확인인데, 이름은 조금 딱딱하지만 하는 일은 일상적인 비유로 쉽게 풀립니다.
멱등성 키를 먼저 보겠습니다. 같은 요청이 두 번 도착해도 한 번만 처리되게 붙이는 표시라고 생각하면 됩니다. 에이전트는 시간 초과가 나면 다시 시도하는 버릇이 있어서, 이때 표시가 없으면 같은 환불이 두 번 찍힐 수 있습니다. 반면 요청마다 고유한 표시를 달아두면 저장소는 "이 표시는 이미 처리했다"고 알아보고 중복을 막아주는데, 우편물을 보낼 때 송장 번호를 적어두는 것과 비슷합니다.
원자적 트랜잭션은 여러 조각을 한 묶음으로 묶는 일입니다. 계좌 이체처럼 빠져나감과 들어옴이 함께 일어나야 하는 작업에서는, 한쪽만 되고 다른 쪽이 안 되면 장부가 어긋납니다. 그래서 둘을 묶어서 전부 성공하거나 전부 없던 일로 되돌리게 만듭니다. 적용된 행 수를 보는 일도 같은 맥락인데, 도구가 "0개 반영"이라고 돌려주는데 에이전트가 "저장 완료"라고 말하면 그 불일치 자체가 바로 위험 신호가 되니까요.

구조화된 출력이 말보다 믿을 만한 이유
여기서 재미있는 질문이 생깁니다. 왜 에이전트의 말은 믿기 어렵고, 도구의 출력은 그래도 낫다고 할까요. 이유는 출력의 형태에 있습니다. 자연어 문장은 매끄럽게 보이지만 검증하기 어려운 반면, ID나 커밋 확인 같은 구조화된 출력은 바로 조회해볼 수 있기 때문입니다.
예를 들어 에이전트가 "주문 48210을 저장했습니다"라고 말하는 경우를 생각해 보겠습니다. 이 문장만으로는 주문이 정말 있는지 알 수 없습니다. 그런데 도구가 주문 ID와 함께 반영된 행 수, 트랜잭션 식별자를 돌려주면 이야기가 달라집니다. 그 값으로 데이터베이스를 다시 조회했을 때 같은 행이 돌아오면 저장이 확인되고, 행이 없으면 말이 틀렸다는 게 바로 드러나니까요. 그래서 주문 처리처럼 중요한 쓰기에서는 문장을 기준으로 삼을 일이 아니라, 조회 가능한 값을 기준으로 삼아야 합니다.
저는 여기서 멈칫했습니다. 에이전트에게 "완료라고 말하기 전에 무엇을 보여줄래"라고 묻는다면, 대답의 형식부터 바뀌어야 하겠다는 생각이 들었기 때문입니다. 유창한 사과나 자신감 있는 보고를 기대할 게 아니라, 확인할 수 있는 짧은 값들을 내놓게 해야 합니다. 대화는 말솜씨를 뽐내는 쪽보다 증거를 요구하는 쪽으로 설계해야 하며, 이 작은 전환이 에이전트와 데이터베이스 사이의 신뢰를 훨씬 단단하게 만듭니다.
운영에 심는 세 가지 안전장치
실제 시스템 관점에서 보면 다음 질문이 바로 생깁니다. 개별 에이전트가 조심하는 것만으로 충분할까요. 아쉽게도 그렇지는 않습니다. 에이전트가 늘어날수록 말과 상태가 어긋나는 경우도 함께 늘기 때문에 시스템 차원의 안전망이 필요합니다. 이 글에서 거론하는 장치가 바로 조정 작업, 불일치 알림, 그리고 위험한 쓰기에 대한 사람 승인입니다.
조정 작업은 장부를 맞추는 일입니다. 에이전트가 "완료"라고 주장한 목록과 데이터베이스에 실제로 있는 행을 주기적으로 비교해서 빠진 것을 찾아내는데, 밤사이 들어온 환불 요청이 아침 정산 화면에 보이지 않는다면 그 차이를 먼저 붙잡는 것이 이 작업의 몫입니다. 운영의 아침을 지켜주는 장치가 따로 있어야 한다는 뜻이며, 불일치가 발견되면 바로 알림이 울리고 담당자가 개입할 수 있게 둡니다.
위험한 쓰기에 대한 사람 승인은 속도를 조금 늦추는 대신 사고의 크기를 줄이는 선택입니다. 환불이나 삭제, 권한 변경처럼 되돌리기 어려운 작업에서는 에이전트가 바로 실행하지 않고 사람의 확인을 거치게 합니다. 어떤 팀에서는 이 과정을 번거롭게 느낄 수도 있지만, 한 번의 잘못된 삭제가 복구 회의로 이어지는 장면을 떠올리면 그 번거로움이 오히려 싼 보험처럼 느껴집니다. 저희가 새로운 AI 아키텍처를 볼 때도 숫자만 보지는 않습니다. 실제로 무엇이 바뀌었고, 그 변화가 시스템에서 어떤 비용 구조를 만드는지를 함께 봅니다. 승인 단계도 같은 눈으로 보면 됩니다.

거짓 완료를 시험대에 올리는 법
그렇다면 에이전트가 잘 작동하는지는 어떻게 알아낼까요. 평소대로 시켜보고 성공률을 재는 것만으로는 부족합니다. 에이전트는 순풍이 불 때는 누구나 잘하기 때문입니다. 이 글에서 말하는 방향은 일부러 거친 조건을 만들어서 거짓 완료가 나오는지를 보는 것입니다.
도구 실패, 시간 초과, 복제 지연 같은 상황을 일부러 만들어 보십시오. 쓰기가 실패했는데도 에이전트가 "됐습니다"라고 말하는지, 시간 초과 뒤에 재시도하면서 중복을 만드는지, 잠시 안 보이는 데이터를 보고 섣불리 실패라고 단정하는지를 차례로 확인하는 겁니다. 이런 시험을 거치면 에이전트의 말버릇이 드러납니다. 근거 없이 단정하는지, 모호할 때 다시 확인하는지, 증거를 붙여서 보고하는지가 보이니까요.
개인적으로 인상 깊었던 점은 이런 평가가 기능 시험을 넘어서 성격 시험에 가깝다는 것입니다. 무엇을 할 수 있는지보다 모를 때 어떻게 행동하는지를 보는 일이기 때문입니다. 잘 만든 에이전트는 실패 앞에서 "확인 중입니다"라고 말하고, 조회 결과를 들고 와서 "이 ID로 조회되니 완료입니다"라고 말합니다. 그래서 평가 항목에 거짓 완료율을 따로 두는 편이 좋습니다. 성공률을 재는 게 아니라 정직률을 재는 셈이며, 이 대목을 통과한 에이전트라야 운영에 내보낼 마음이 듭니다.
확인에도 비용이 든다는 반론
여기까지 읽으면 이런 반론이 나올 수 있습니다. 매번 읽어서 확인하고 조정 작업을 돌리고 사람 승인까지 받으면 너무 느려지는 것 아니냐는 것인데, 타당한 걱정입니다. 확인에는 읽기 비용이 들고 지연이 생기며 코드도 복잡해지니까요. 그래서 이 글의 제안을 무조건 받아들이기 전에 조건을 따져봐야 합니다.
에이전트 실패담도 가끔은 과장되어 전해집니다. 모든 쓰기가 똑같이 위험한 것은 아니기 때문입니다. 장바구니에 메모를 남기는 쓰기와 송금을 실행하는 쓰기는 무게가 다르지만, 이런 차이가 생략된 채 "에이전트는 믿을 수 없다"는 말만 돌기 쉽습니다. 그래서 저는 모든 쓰기에 같은 강도의 확인을 붙이는 방식에는 동의하지 않습니다. 위험도에 따라 확인의 깊이를 다르게 가져가는 편이 훨씬 현실적입니다.
가벼운 쓰기는 구조화된 출력과 가벼운 재조회로 충분할 수 있고, 중요한 쓰기는 조정 작업과 사람 승인까지 이어지는 게 맞습니다. 문제는 그다음입니다. 어디를 가볍게 하고 어디를 무겁게 할지를 팀 안에서 미리 정해두지 않으면, 현장에서는 매번 감으로 판단하게 됩니다. 그래서 쓰기 전에 등급을 나누는 일이 먼저이며, 이 구분이 있어야 확인 비용이 헛돈으로 끝나지 않고 필요한 곳에 쓰는 돈이 됩니다.

다음에 볼 것과 가져갈 한 가지
남은 질문은 이것입니다. 이 흐름은 어디에서 더 깊어질 수 있을까요. 제가 앞으로 보고 싶은 것은 에이전트가 확인을 습관처럼 체득하는 모습입니다. 사람이 시켜서 읽는 단계를 넘어서, 쓰기 뒤에 읽기가 따라오는 게 기본 동작이 되는 것이죠. 그렇게 되면 "저장했습니다"라는 말의 뜻도 달라집니다. 느낌에 기대는 말이 아니라 조회 결과에 기대는 말이 되니까요.
또 하나 보고 싶은 것은 평가의 변화입니다. 성공률 옆에 거짓 완료율이 함께 적히는 게 당연해지면 팀의 대화도 달라질 겁니다. "얼마나 잘했냐"에서 "언제 모른다고 말했냐"로 질문이 옮겨가기 때문입니다. 에이전트 평가도 예전에는 할 수 있는가를 물었다면, 이제는 믿을 수 있는가를 묻는 쪽으로 바뀌는 중입니다.
그래서 오늘 글을 한 문장으로 줄이면 이렇게 됩니다. 에이전트의 말은 출발점이고 데이터베이스의 상태가 도착점이라는 것입니다. 다음에 에이전트가 "다 했습니다"라고 말하면 한 번만 더 물어봐 주십시오. 어느 행으로 확인했냐고요. 그 짧은 질문이 말과 저장 사이의 틈을 메우는 시작이 될 겁니다. 사람의 몫은 그 질문에 답하는 일이라고 저는 봅니다. 무엇을 믿을지, 어디까지 자동에 맡길지, 언제 직접 확인할지를 정하는 일이니까요.
참고 자료
- The Agent Said It Was Done. The Database Disagreed. · huggingface.co
리뷰 원문