최근 들어 너무나 자주 목격하게 되는 익숙한 장면이 하나 있다.
AI 에이전트가 똑같은 실수를 세 번째 반복하는 것을 발견한다. 당신은 교정 사항을 기억 파일에 저장하라고 명령한다. 에이전트는 메모리 파일에 규칙을 한 줄 적어 넣는다. 실제로 디스크의 파일이 업데이트되는 것을 두 눈으로 확인한다. 에이전트 역시 당당하게 확인해 준다: “저장했습니다! 다음부터는 꼭 기억하겠습니다.”
그리고 다음 세션. 녀석은 어김없이 똑같은 실수를 저지른다.
당신이 착각한 것이 아니다. 에이전트는 실제로 파일에 글을 적었다. 그 파일은 지금도 디스크에 온전히 존재한다. 그리고 녀석은 그 메모를 완벽하게 무시할 것이다. 이는 LLM 에이전트 시스템에서 가장 흔하게 일어나는 실패 유형(Failure Mode #1)이다. 에이전트가 “저장하지 않아서” 생기는 문제가 아니다. ’저장하는 것’과 ’학습하는 것’은 완전히 다른 차원의 동작이기 때문에 발생하는 문제다.
기억의 착각
사람들은 흔히 생각한다: 에이전트가 어딘가에 적어두었다면, 배운 것이라고.
그러나 현실은 다르다. 에이전트는 단지 정보를 기록했을 뿐이다. 그 정보가 향후 에이전트의 의사결정에 실질적인 영향을 미칠지 여부는, 에이전트가 적절한 타이밍에, 적절한 형식으로, 프롬프트 내의 올바른 위치로 그 정보를 불러오느냐(retrieve)에 전적으로 달려 있다. 하지만 대부분의 경우 에이전트는 이를 제대로 불러오지 못한다.
이렇게 비유해 보자. 당신이 수첩에 “절대 프로덕션 데이터베이스를 건드리지 말 것”이라고 적어두었다. 하지만 이후 그 수첩을 한 번도 다시 열어보지 않는다면, 당신은 언젠가 또 프로덕션 DB를 건드릴 것이다. 수첩 자체의 문제가 아니다. ’다시 꺼내어 보는 과정(retrieval)’의 문제다.
에이전트의 메모리 파일은 그저 덮여 있는 수첩에 불과하다.
공책이 작동하지 않는 7가지 이유
Hermes, Claude Code, OpenHands 등 다양한 에이전트 프레임워크를 깊이 파고들다 보면 언제나 동일한 문제 패턴이 드러난다. 크게 일곱 가지 실패 모드가 존재하며, 대부분의 에이전트는 이 중 최소 세 가지 이상의 덫에 걸려 있다.
1. 스냅샷의 동결 (Snapshot Freeze)
대다수의 에이전트 프레임워크는 세션이 시작되는 시점에 메모리 파일들을 동결된 스냅샷(frozen snapshot) 형태로 메모리에 적재한다. 이는 성능 최적화를 위한 조치다. 모델이 매 턴마다 똑같은 시스템 프롬프트를 다시 처리하지 않도록 LLM 프리픽스 캐싱(prefix caching)을 유지하기 위함이다.
문제는 여기에 있다: 에이전트가 세션 도중에 수정 사항을 파일에 기록하면 디스크의 파일은 분명 업데이트되지만, 현재 활성화되어 있는 시스템 프롬프트는 여전히 이전의 동결된 상태 그대로다. 에이전트는 해당 세션이 끝날 때까지 방금 업데이트된 메모리를 물리적으로 읽을 수 없다. 새로운 세션을 시작하기 전까지는 똑같은 실수를 무한히 반복하게 된다.
이것이 가장 흔한 원인이다. 확인하는 방법도 아주 간단하다: 새 세션을 열었을 때 실수가 사라지는가? 만약 그렇다면 이 문제에 해당한다.
2. 엉뚱한 파일에 저장
에이전트의 메모리 시스템은 보통 여러 계층으로 나뉘어 있다. 예를 들어 Hermes 프레임워크의 경우:
- SOUL.md — 영구적인 정체성과 핵심 규칙. 오직 사람만이 수정할 수 있다.
- USER.md — 사용자 선호 사항. 에이전트가 스스로 업데이트할 수 있다.
- MEMORY.md — 작업 메모. 자동으로 요약 및 압축(summarization)되는 대상이다.
함정은 에이전트가 당신의 교정 지시를 MEMORY.md에 적어둘 때 발생한다. 시간이 지나면서 이 파일의 내용은 자동으로 요약되고 정리된다. 그 과정에서 섬세한 뉘앙스는 뭉개져 막연한 표현으로 변해버린다. 혹은 파일 크기가 기본 한도(보통 약 2,200자)를 넘어서는 순간 오래된 규칙들은 완전히 삭제되어 버린다.
게다가 MEMORY.md는 전체 텍스트 검색(full-text search)을 통해 소환된다. 만약 나중에 내리는 사용자의 명령어가 저장된 메모 속의 키워드와 정확히 일치하지 않는다면, 그 메모는 컨텍스트로 불려오지도 않는다. 교훈은 디스크 위에 박제된 채 영원히 보이지 않는 곳에 머물게 된다.
결코 위반해서는 안 되는 엄격한 규칙은 반드시 SOUL.md나 AGENTS.md에 들어가야 한다. 이를 MEMORY.md에 적어두는 것은 접근 금지 명령서를 포스트잇에 적어두는 것과 다를 바 없다.
3. 스킬(Skills)은 선택 사항일 뿐이다
대부분의 에이전트 시스템에서 스킬이나 학습된 절차는 자동으로 실행되지 않는다. 세션이 시작될 때 에이전트는 스킬 이름들과 짧은 한 줄 설명이 담긴 컴팩트한 목록만을 전달받는다. 에이전트가 스스로 판단하여 특정 로드 함수를 호출해야만 비로소 스킬의 전체 본문을 읽어 들인다.
하지만 체급이 낮은 모델들은 “일치하는 스킬을 반드시 불러와야 한다”는 시스템 지시를 무시하기 일쑤다. 심지어 뛰어난 모델들조차 종종 이를 건너뛴다. 컨텍스트를 감지해 강제로 스킬을 로드하도록 만드는 자동 트리거 장치가 없기 때문이다.
설령 스킬이 존재한다 하더라도, 실수가 다시 재현되는 순간의 정황(다른 단어 선택, 다른 도구 호출 순서, 다른 작업 문맥)이 스킬의 트리거 설명과 완벽하게 맞아떨어지지 않는다면 스킬은 절대 발동되지 않는다.
4. 글자 수 제한
메모리 파일은 생각보다 매우 작다. MEMORY.md는 보통 2,200자 내외, USER.md는 약 1,375자 수준이다. 용량이 가득 차면 에이전트는 공간을 확보하기 위해 오래된 항목들을 압축하고 통합한다. 이 과정에서 당신이 세심하게 가르쳐준 구체적 교정 사항은 일반적인 상투어로 왜곡되거나, “더 중요한” 항목들에 밀려 통째로 사라져 버린다.
물론 설정을 통해 한도를 늘릴 수는 있다(hermes config set memory.memory_char_limit 5000). 하지만 근본적인 문제는 여전하다: LLM이 관리하는 유한한 크기의 텍스트 파일은 시간이 지나면 필연적으로 중요한 세부 정보를 깎아내 버린다.
5. 문맥 중간에서의 실종 (Lost in the Middle)
메모리가 프롬프트에 성공적으로 주입되었다 하더라도 LLM 특유의 고질적인 어텐션(attention) 한계가 발목을 잡는다. 스탠퍼드 대학교의 연구(arXiv 2307.03172)에 따르면, 언어 모델은 긴 컨텍스트의 ’중간’에 위치한 정보를 심각하게 간과하는 경향이 있다. 모델은 맨 앞과 맨 끝은 잘 기억하지만, 중간에 끼어 있는 내용은 무시하기 십상이다.
만약 에이전트가 400줄에 달하는 스킬과 문서를 로드했는데 정작 치명적인 규칙이 200번째 줄에 적혀 있다면, 모델이 이를 그냥 지나칠 확률은 매우 높다. 프롬프트 내의 배치 순서는 생각보다 훨씬 중요하다:
- 나쁜 순서: 시스템 프롬프트 → 사용자 요청 → 파일 내용 → 대화 기록 → 메모리
- 좋은 순서: 시스템 프롬프트 → 핵심 규칙 → 현재 작업 → 파일 내용
규칙은 맨 앞에 와야 한다. 하지만 대부분의 프레임워크는 이를 맨 뒤나 중간에 쑤셔 넣는다.
6. 추론 모델과의 압축 실패
DeepSeek-R1이나 Qwen 추론 모델처럼 reasoning 메커니즘을 가진 모델을 메인이나 보조 모델로 사용하고 있다면 또 다른 지뢰가 기다리고 있다. 에이전트 프레임워크는 컨텍스트 한도를 지키기 위해 긴 대화를 주기적으로 압축한다. 그런데 추론 모델들은 API 호출 시 이전 단계의 추론 내용(reasoning content)을 그대로 되돌려줄 것을 요구하며, 많은 프레임워크의 압축 워크플로는 이를 제대로 지원하지 못한다.
압축 시도가 실패를 거듭하면 일부 프레임워크는 전체 세션 컨텍스트를 강제로 초기화(auto-reset)해 버린다. 에이전트는 파일에 영구 저장해 둔 규칙을 포함하여 모든 것을 한순간에 잊어버린다. 대화 도중에, 아무런 경고도 없이 말이다.
7. 서브 에이전트는 백지 상태에서 시작한다
에이전트가 하위 작업을 처리하기 위해 서브 에이전트(sub-agent)를 생성할 때, 그 서브 에이전트는 완전히 깨끗한 백지 상태로 실행된다. 부모 에이전트가 갖고 있던 메모리도, 스킬도, 대화 기록도 전혀 상속받지 못한다. 부모 에이전트가 서브 에이전트에게 내리는 작업 지시서(task description) 안에 규칙을 명시적으로 집어넣어 주지 않는다면, 서브 에이전트는 부모가 방금 호되게 겪고 배운 실수를 고스란히 반복할 것이다.
정교한 멀티 에이전트 시스템을 구축했다고 자부하는 개발자들이 가장 뼈아프게 당하는 지점이 바로 여기다. 컨텍스트가 온전히 전달되지 않는다면 그 정교함은 한낱 환상에 불과하다.
기억은 지능이 아니다
핵심 문제는 개념적인 착각에 있다. 사람들은 ’기억’과 ’학습’을 동의어로 취급한다. 하지만 둘은 전혀 다르다.
수첩에 “항상 입력값을 검증할 것”이라고 적어두었다고 해서 그 사람이 입력값 검증 방법을 배운 것은 아니다. 단지 의도를 기록해 두었을 뿐이다. 진정한 배움은 입력값 검증이 필요한 상황을 맞닥뜨렸을 때, 그 규칙을 머릿속에서 떠올려 실제로 코드에 적용할 때 일어난다. 그리고 대다수의 에이전트가 처참하게 실패하는 지점이 바로 이 ’떠올림(recall)’의 단계다.
LLM은 확률 기계다. 현재 주어진 프롬프트가 이전에 오답을 선택했던 프롬프트와 거의 동일하다면, 모델은 또다시 그 오답 행동을 선택할 것이다. 컨텍스트 내부에서 확률 분포를 유의미하게 뒤흔들 만한 강력한 무언가가 개입하지 않는 한 말이다. 프롬프트 어딘가에 어설프게 끼어 있거나, 중간에 묻혀 있거나, 모호하게 요약되어 버린 메모리 파일 한 줄로는 그 확률 분포를 뒤흔들기에 턱없이 부족하다.
모델은 자기가 실수를 저질렀다는 사실을 “알지” 못한다. “이전에도 이 짓을 했다가 혼났지”라는 식의 자의식은 존재하지 않는다. 모델의 세계에는 오직 컨텍스트 윈도우 안에 들어찬 토큰들과 다음 토큰에 대한 확률 분포만이 존재할 뿐이다. 메모리 파일에서 가져온 토큰들이 현재 눈앞의 작업 토큰들에 비해 충분히 두드러지지(salient) 못하다면, 익숙한 이전 작업의 패턴이 언제나 승리한다.
실제로 작동하는 해결책들
영향력이 큰 순서대로 정리한 실질적인 대처 방안은 다음과 같다:
1. 저장 후 세션을 반드시 재시작하라
이 조치 하나만으로도 수많은 문제가 해결된다. 메모리 파일은 스냅샷이다. 변경 사항은 다음 세션이 로드될 때 비로소 효력을 발휘한다. 에이전트에게 규칙을 저장하라고 지시한 뒤 같은 세션에서 대화를 계속 이어갔다면, 그 규칙은 시스템에 전혀 반영되지 않은 것이나 다름없다.
2. 절대적인 규칙은 AGENTS.md나 SOUL.md에 넣어라
AGENTS.md는 매 세션이 시작될 때마다 예외 없이 자동으로 읽힌다. 합리적인 범위 내라면 요약되거나 글자 수 제한에 잘려 나가지도 않는다. “절대 X를 하지 말 것”과 같은 필수 제약 조건은 어설픈 스킬이나 MEMORY.md 대신 이곳에 배치해야 한다.
반드시 구체적이고 단호한 명령형으로 작성하라:
- 나쁜 예: “데이터베이스 쿼리를 날릴 때는 주의할 것.”
- 좋은 예: “사용자의 명시적 승인 없이 DELETE나 DROP 쿼리를 절대 실행하지 마라. 모든 쓰기 작업은 반드시 트랜잭션으로 감싸라.”
3. 스킬 이름을 명시적으로 지정하라
어떤 스킬을 써야 할지 에이전트 스스로 알아서 판단해 주기를 기대하지 마라. 프롬프트에 명확하게 못 박아라: “deploy-staging 스킬을 사용할 것.” 이렇게 하면 에이전트가 스킬 로드 여부를 스스로 고민하다 건너뛰는 일을 원천적으로 차단할 수 있다.
4. 기억을 일기장이 아니라 조건부 규칙으로 작성하라
- 나쁜 예: “JSON을 파싱할 때 대괄호를 자주 빼먹는 경향이 있음을 발견했다.”
- 좋은 예: “IF: JSON 출력을 파싱할 때 → THEN: API로 전송하기 전에 항상 대괄호
[]로 감쌀 것.”
더 좋은 방법: 실패했던 구체적 행동과 수정된 행동을 나란히 박제해 두어라:
FAILED: api/v1/users 호출 → 오래된 데이터 수신
FIXED: 항상 api/v2/users 호출 → 최신 데이터 수신
LLM은 막연한 긍정적 조언보다 구체적인 실패 사례(negative example)를 볼 때 훨씬 더 확실하게 반응한다.
5. 검증 게이트(Verification Gate)를 추가하라
에이전트가 결과를 입증하지도 않은 채 혼자 성공했다고 선언하게 내버려두지 마라. 작업이 끝난 직후 반드시 스스로 검증 명령을 실행하도록 강제하라:
작업 완료 후 반드시 실행할 것: grep -r "bad_pattern" ./ || echo "PASS"
만약 FAIL이 발생하면 즉시 수정을 다시 적용할 것.
이렇게 하면 모호했던 지침이 실행 가능한 체크포인트로 전환된다. 조언은 무시할 수 있어도, 셸에서 터져 나오는 검증 실패 명령어는 무시할 수 없다.
6. 서브 에이전트에게는 컨텍스트에 직접 규칙을 주입하라
작업을 서브 에이전트에 위임할 때는 작업 지시문 내부에 핵심 규칙을 직접 때려 박아라:
CRITICAL: 절대 api/v1을 사용하지 말 것. 무조건 api/v2를 사용할 것.
직전 시도에서 v1을 썼다가 오래된 데이터가 반환되는 문제가 있었음.
대상 파일: /path/to/file.py
컨텍스트 필드를 게으르게 비워두는 것이 서브 에이전트 작업이 실패하는 가장 큰 원인이다.
가장 결정적인 통찰
저장보다 훨씬 더 중요한 것은 검증과 검색(retrieval)이다.
가장 극적인 성능 향상은 더 많은 메모리를 욱여넣는 데서 오지 않는다. 에이전트가 계획을 세우기 전에 올바른 기억을 먼저 끄집어내도록 만들고, 작업을 끝내기 전에 실수가 확실하게 바로잡혔음을 스스로 증명하게 만드는 데서 나온다.
실패하는 루프:
작업 접수 → 계획 수립 → 실행 → 실패 → 메모리에 저장 → 다시는 꺼내보지 않음 → 또다시 실패
성공하는 루프:
작업 접수 → 관련 규칙 소환 → 규칙을 반영한 계획 수립 → 실행 → 자체 검증 → 완료
대다수의 에이전트 아키텍처는 첫 번째 실패 루프에 갇힌 채 에이전트가 왜 자꾸 같은 실수를 반복하는지 의아해한다. 필요한 것은 더 큰 메모리가 아니다. 기억과 의사결정 사이를 잇는 배관(plumbing)을 제대로 연결하는 일이다.
결론
AI 에이전트는 파일에서 배우지 않는다. 에이전트는 오직 컨텍스트로부터 배운다. 파일은 그 내용이 프롬프트 내부의 올바른 위치에, 적절한 타이밍에, 모델이 즉각 행동할 수 있는 형식으로 안착할 때만 의미를 갖는다. 하지만 대부분의 시스템에서는 그런 일이 일어나지 않는다.
에이전트가 같은 실수를 계속 반복하고 있다면 메모리 파일에 새로운 메모를 한 줄 더 얹으려 하지 마라. 기존의 메모들이 실제로 프롬프트에 제대로 읽혀 들어가고 있는지부터 확인하라. 엉뚱한 파일에 적어둔 것은 아닌지, 세션이 동결된 상태는 아닌지, 지시문의 형식이 즉시 실행 가능한 형태로 되어 있는지 점검하라.
수첩 자체는 죄가 없다. 문제는 언제나 그것을 펼쳐보지 않는 방식에 있다.
참고자료: