매일 새로 시작하는 AI 에이전트가 같은 실수를 반복하는 이유와 해결법

제가 돌리는 자동화 에이전트의 산출물에서 거슬리는 표현 버릇을 발견해서, 고치라고 지시했고, 고쳐졌습니다. 다음 날 아침 새 산출물에는 똑같은 버릇이 태연히 다시 들어 있었습니다. 지능의 문제가 아니었습니다. 이 에이전트는 매일 아침 새 세션으로 뜨면서, 어제 무엇을 지적받았는지 전혀 모른 채 시작하고 있었습니다.
매일 정해진 시각에 LLM 세션을 하나 띄워 일을 시키고 닫는, 크론(cron) 스케줄러에 걸어두는 흔한 구성입니다. 이런 구성은 이전 실행의 상태를 따로 넘겨주지 않는 한 기억이 없습니다(대화 저장소나 외부 메모리를 붙였다면 사정이 다릅니다). 파고들어 보니 표현 버릇만이 아니었습니다. 한 세션은 작업을 끝내고도 완료 기록을 남기지 않았고, 다음 세션이 같은 작업을 다시 실행하기 직전이었습니다. 결론부터 말하면, 저는 이 반복을 모델 교체가 아니라 파일 몇 개와 프롬프트 몇 줄로 끊었습니다. 기억을 모델 안이 아니라 실행 바깥에 둔 겁니다.
새 세션은 매번 첫 시도다
세션 하나 안에서 모델은 “그 방법은 아까 실패했다”를 압니다. 한 번의 실행에서 모델이 읽을 수 있는 범위, 그러니까 컨텍스트 윈도우 안에 그 기록이 있으니까요. 문제는 다음 실행입니다. 새 세션은 새 컨텍스트로 시작하고, 어제의 교정은 어디에도 실려 있지 않습니다. 매번 똑같은 프롬프트만 받고 출발하니 어제와 같은 판단에 도착합니다. 엄밀히 말하면 실수를 반복하는 것도 아닙니다. 매일 처음 해보는 셈입니다. 엔지니어 니르 예히엘의 요약이 정확합니다. “에이전트는 자기가 틀렸었다는 걸 기억하지 못한다(they don’t remember being wrong).”
실행 바깥에 기억을 두는 네 가지 장치
그래서 고치는 곳도 모델이 아니라 모델을 감싼 층입니다. 제가 쓰는 장치는 네 가지고, 실행 흐름에 따라 정리하면 이렇습니다.
1. 실패 기록은 래퍼 스크립트가 남깁니다. 세션을 띄우는 셸 스크립트가 종료 코드를 받아, 성공이면 완료 기록을, 실패면 실패 시각과 종료 코드, 마지막 출력을 로그 파일에 적습니다. 이 일을 에이전트 자신에게 맡기면 안 됩니다. 프로세스 강제 종료나 API 단절처럼 정작 중요한 실패의 순간에, 에이전트는 아무것도 쓸 수 없기 때문입니다.
2. 다음 세션은 프롬프트로 어제를 전달받습니다. 세션 시작 프롬프트에 “작업 전에 운영 로그부터 읽어라”를 넣고, 1번의 기록이 그 로그에 쌓이게 합니다. 모델이 기억하게 된 건 아닙니다. 어제가 적힌 종이를 읽고 시작할 뿐인데, 실행 단위에서 보면 결과는 기억과 같습니다.
3. 재시도에는 이어받기 지시를 줍니다. 실패 후 다시 뜨는 세션에는 “직전 시도가 중단됐을 수 있다. 처음부터 다시 하지 말고 어디까지 됐는지 확인해 이어서 하라”를 덧붙입니다. 이게 없으면 재시도가 끝난 작업을 처음부터 반복하거나, 반쯤 된 작업 위에 중복을 쌓습니다. 다만 프롬프트는 지시일 뿐 실행을 보장하지 않습니다. 발행이나 결제처럼 중복이 치명적인 작업은 스크립트가 완료 여부를 확인해서 재실행 자체를 막아야 합니다.
4. 반복되는 교정은 지침 파일로 승격합니다. 세션마다 읽는 지침 파일을 두고, 같은 교정을 두 번째 하게 되는 순간 그 내용을 파일에 올립니다. 어디에 적느냐가 중요합니다. 모든 작업에 해당하는 규칙과 특정 작업에만 해당하는 규칙은 각각 둘 파일이 다르고, 아무 데나 적힌 규칙은 다음 세션이 필요한 순간에 찾지 못합니다. 승격한 규칙이 실제로 지켜지는지도 다음 실행 로그로 한 번 확인합니다. 잘못 일반화된 규칙이 영구화되는 게 이 방식의 대표적인 부작용이라서요.
제 표현 버릇 사건은 4번으로 끝났습니다. 지침 파일에 그 버릇을 금지 항목으로 올린 뒤로 재발이 멎었습니다. 완료 기록을 안 남겨 생길 뻔했던 중복 작업은 1번과 2번이 막았고요. 물론 네트워크 장애처럼 상태와 무관한 실패는 이 장치들의 몫이 아니라 별도 재시도 정책의 몫입니다. 그래도 어떤 원인이든, 두 번째 실행이 어제를 읽고 시작한다는 사실은 복구를 훨씬 수월하게 만듭니다.
기록이 있어도 흘려 읽는 문제
여기까지 하고도 남는 문제가 하나 있습니다. 기록을 줘도 모델이 흘려 읽는 경우입니다. 올해 초 트벤테 대학교의 베리 헤리츠(Berry Gerrits)가 공개한 프리프린트(심사 전 공개 논문)에 재미있는 관찰이 있습니다. 최신 LLM들에게 1977년작 텍스트 어드벤처 게임 조크(Zork)를 시켰더니 평균 점수가 만점의 10%에도 못 미쳤는데, 눈에 띄는 건 점수보다 행동이었습니다. 대화 기록에 자기 실패가 그대로 남아 있는데도, 모델들이 실패한 행동을 되풀이한 겁니다. 이 연구 하나로 원인을 확정할 수는 없습니다. 다만 기록이 있어도 반드시 활용되는 건 아니라는 관찰은 제 로그와도 일치했습니다. 긴 문맥에서는 가운데 놓인 정보보다 앞과 끝의 정보를 잘 찾는다는 보고도 있고요.
그래서 저는 실패 전달을 이야기처럼 길게 쓰지 않습니다. 프롬프트 앞머리에 짧은 지시형으로 둡니다. “어제 X 단계에서 연결 오류로 중단됐다. 오늘은 X의 완료 여부부터 확인하라.” 제 환경에서는 긴 로그를 통째로 붙일 때보다 이쪽이 지시 누락이 눈에 띄게 적었습니다.
에이전트는 여전히 매일 아침 처음 뜹니다. 달라진 건 그 처음이 빈손이 아니라는 것뿐입니다. 기억은 파일이고, 반성은 다음 세션의 프롬프트입니다.
여백 — 본문에 안 들어간 요령 하나. 래퍼가 실패를 기록하는 것과 별개로, 에이전트에게는 작업 중간중간 “지금 어느 단계까지 마쳤다”를 파일에 적게 해보세요. 죽는 순간의 기록은 래퍼 몫이지만, 어디까지 갔는지의 기록은 살아 있는 동안의 에이전트가 제일 잘 압니다. 둘을 합치면 재시도 세션이 복원할 수 있는 지도가 됩니다.
참고 — AI도 못 깨는 게임이 있다 (ZDNet Korea), Why Your AI Agent Keeps Making the Same Mistakes (Nir Yechiel)
댓글