
에이전트가 망각하는 이유
요약
코딩 에이전트가 작업 맥락을 지속적으로 기억하지 못하는 근본적인 이유를 분석합니다. 현재의 메모리 기능은 단순 선호도 저장에 그치며, 모델의 순수 함수적 특성으로 인해 수행한 작업 자체를 학습하는 데 한계가 있음을 지적합니다.
핵심 포인트
- 현재 에이전트 메모리는 선호도 저장에 국한됨
- LLM은 가중치가 고정된 순수 함수로 작동함
- 컨텍스트 윈도우는 호출 종료 시 초기화됨
- 작업 수행 경험으로부터의 학습이 핵심 과제임
당신의 코딩 에이전트(coding agent)는 1년 전보다 더 나아졌지만, 여전히 무언가를 잊어버립니다.
그동안 어시스턴트들은 메모리 계층(memory layer)을 추가했습니다. GitHub Copilot은 이제 세션 전반에 걸쳐 당신의 관습(conventions)을 유지하며, ChatGPT는 당신이 말한 내용을 지속적인 프로필로 유지합니다. 하지만 이러한 기능들은 선호도(preferences)를 기억할 뿐, 작업(work)을 기억하지는 못합니다. 즉, "camelCase를 사용하라"는 기억할 수 있지만, "지난 스프린트에서 배출 정책(eviction policy) 때문에 캐시용으로 Redis를 제외하기로 했다"는 내용은 기억하지 못합니다. 그 밑단의 모델(model)은 여전히 모든 호출(call)을 아무것도 없는 상태에서 시작합니다.
에이전트를 하루 종일 실행해 두었을 때 처음으로 이 현상을 목격하게 됩니다. 에이전트는 부팅되어 테스트를 실행하고, 테스트가 실패하는 것을 지켜본 뒤, 데이터베이스에 먼저 시드(seed)를 심어야 한다는 사실을 처음부터 다시 알아냅니다. 어제의 실행 과정에서 이미 학습했던 내용임에도 불구하고, 오늘의 실행은 이를 알 방법이 없습니다.
제1원리(first principles) 관점에서 보면 그 이유는 신비롭지 않습니다. 언어 모델(language model)은 순수 함수(pure function)입니다. 즉, 출력은 오직 모델의 가중치(weights)와 그 앞에 놓인 텍스트에만 의존합니다. 호출 간에 전달되는 것은 아무것도 없는데, 왜냐하면 정보가 머무를 곳이 없기 때문입니다. 가중치는 훈련(training) 후에 고정되며, 컨텍스트 윈도우(context window)는 호출이 끝나면 비워집니다. 우리가 메모리라고 부르는 모든 것은 이 하나의 사실에 대한 임시방편(workaround)입니다. 즉, 모델 외부에 텍스트를 저장했다가 다음번에 다시 입력으로 넣어주는 것입니다. 2020년 RAG 논문은 이 근본적인 문제를 지적하며 "세계 지식 업데이트(updating world knowledge)"를 미해결 과제로 분류했습니다. 5년이 지난 지금도 여전히 그렇습니다.
에이전트가 질문 하나에 답하고 멈춰있던 시절에는 이것이 각주에 불과했습니다. 하지만 지금은 이것이 핵심 이슈입니다. 우리는 에이전트에게 여러 세션에 걸쳐 몇 시간 동안 실행되는 작업을 맡기며, 아무것도 앞으로 가져가지 못하는 에이전트는 개선될 수 없습니다. 그저 처음부터 다시 시작하는 속도만 빨라질 뿐입니다.
따라서 이 포스트가 진지하게 다루고자 하는 질문은 이것입니다: 에이전트가 이미 수행한 작업으로부터 어떻게 학습할 수 있는가? 이 분야는 목소리가 크고 진실은 세부 사항에 있기 때문에, 모든 주장은 1차 자료(primary source)로 연결됩니다.
북극성 (The north star)
우리는 실제로 어디로 가려고 하는 것일까요? 사람들이 모두 '메모리 (Memory)'라고 부르는 두 가지를 분리해야 합니다. 왜냐하면 이 둘은 동일한 동작이 아니기 때문입니다.
| 지속성 (Persistence) | 학습 (Learning) | |
|---|---|---|
| 변화하는 것 | 입력값 (프롬프트, prompt) | 함수 (가중치, weights) |
| ... |
오늘날 출시되는 거의 모든 것은 왼쪽 열에 해당합니다. Copilot은 당신이 camelCase를 선호한다는 것을 기억합니다. ChatGPT는 당신의 아이 이름을 기억합니다. 이는 실제적이고 유용하지만, 첫 번째 달보다 여섯 번째 달에 더 유능해지는 모델과는 동일하지 않습니다.
북극성 (The north star)은 오른쪽 열입니다. 즉, 자신이 계속해서 같은 실수를 하고 있다는 것을 감지하고 멈추며, 당신의 코드베이스 구조와 스택의 실패 모드 (failure modes)를 내재화하는 에이전트입니다. Anthropic은 최근 강연에서 메모리를 에이전트를 자신의 경험으로부터 개선되는 시스템으로 변화시킨다고 믿는 기본 요소 (primitive)로 정의했습니다.
우리는 그곳에 도달했나요? 아니요, 그리고 왜 그런지에 대해 정확히 짚고 넘어갈 가치가 있습니다. 오늘날 우리가 학습 (learning)이라고 부르는 것은 모델이 아니라 모델에 입력하는 텍스트를 변경하는 것입니다. 가중치 (weights)는 전후가 동일합니다. 역량은 고정되어 있으며, 오직 컨텍스트 (context)만 개선될 뿐입니다. 이 분야의 혼란 대부분은 시스템이 어느 열에 속해 있는지를 명시하지 않는 데서 발생합니다.
현재 우리의 위치
오늘날 에이전트는 어떻게 기억할까요? 중요한 두 가지 동작으로 축소하면 나머지는 세부 사항에 불과합니다. 입력 과정에서는 검색 (retrieval): 컨텍스트 윈도우 (context window)로 무엇을 가져올 것인가의 문제입니다. 출력 과정에서는 쓰기 (writing): 무엇을 지속시킬 것인가의 문제입니다. 메모리는 이 두 가지 결정에 관한 엔지니어링이며, 여기에 실행 사이에 정리 작업을 수행하는 오프라인 패스 (offline pass)가 추가된 것입니다.
전체 설계를 강제하는 제약 조건부터 시작하겠습니다. 컨텍스트 윈도우 (Context window)는 추론 (Inference) 시 새로운 정보가 들어오는 유일한 통로이며, 이는 유한합니다. MemGPT 논문은 이를 희소한 메모리를 관리하는 운영 체제 (Operating system)로 프레임화합니다. 즉, 윈도우는 RAM이고, 외부 저장소는 디스크이며, 시스템은 이들 사이에서 페이징 (Paging)을 수행합니다. 이것은 단순한 수식어가 아니라 아키텍처 그 자체이기에, 유지할 가치가 있는 유일한 비유입니다.
정확히 무엇을 저장하고 있는 것일까요? CoALA는 네 가지 종류를 명명하며, 각 종류는 서로 다른 저장소 및 서로 다른 업데이트 리듬과 매핑됩니다.
| 메모리 계층 (Memory tier) | 인지 유형 (Cognitive type) | 인프라 (Infrastructure) | 업데이트 (Updated) |
|---|---|---|---|
| 인-컨텍스트 (In-context) | 작업 (Working) | 컨텍스트 윈도우 (Context window, RAM) | 매 모델 턴 (Every model turn) |
| ... |
"테스트 전에 데이터베이스를 시딩(Seed)하라"는 절차적 (Procedural)입니다. "이 서비스는 Postgres 14에서 실행된다"는 의미론적 (Semantic)입니다. "3월 마이그레이션으로 인해 스테이징(Staging) 환경이 깨졌다"는 에피소드적 (Episodic)입니다.
적절한 메모리가 어떻게 윈도우에 도달할까요? 기본 사례는 RAG (Retrieval-Augmented Generation)입니다. 지식을 가중치(Weights)에 있는 파라미터 메모리 (Parametric memory)와, 재학습 없이 검색 및 업데이트할 수 있는 비파라미터 저장소 (Non-parametric store)로 분리하는 것입니다. 실제로 그 저장소는 벡터 인덱스 (Vector index)입니다. 모든 것을 임베딩 (Embed)하고, 쿼리 (Query) 시점에 벡터 공간에서 쿼리에 가장 가까운 구절을 가져옵니다.
함정은 '가장 가까운 것'을 '가장 유용한 것'으로 취급하는 것입니다. 유사도 (Similarity)가 곧 관련성 (Relevance)은 아니며, 하나의 저장소가 두 가지 서로 다른 질문에 답할 수는 없습니다. 실제 검색 단계는 지연 시간 예산 (Latency budget) 내에 머물기 위해 여러 저장소를 병렬로 라우팅합니다.
async def build_context(query, session_id):
# 에피소드적(episodic) 정보와 의미론적(semantic) 정보는 서로 다른 저장소에 존재합니다. 동시에 가져오세요.
episodic, semantic = await asyncio.gather(
...
시간적 저장소 (Temporal store)로부터 에피소드적 상태를, 벡터 저장소 (Vector store)로부터 의미론적 사실을 가져오며, 오래된 사실이 절대 드러나지 않도록 메타데이터 필터 (Metadata filter)를 사용합니다. Zep은 시간적 그래프 (Temporal graph)와 의미론적 검색 (Semantic search)의 이러한 조합을 제품화했습니다.
기억은 어디에서 오는가? 손으로 쓴 노트는 변하지 않는 것만을 다루며 그 외의 것은 담지 못합니다. 흥미로운 시스템들은 기억을 스스로 생성합니다. Copilot은 사용자가 승인할 수 있도록 세션으로부터 기억을 제안합니다. A-MEM은 각 새로운 노트를 관련 있는 노트들과 연결하며, 새로운 기록이 기존 기록을 수정할 수 있게 하여 저장소가 성장함에 따라 스스로 재구성되도록 합니다. 또한 기억은 반드시 망각해야 합니다. 그렇지 않으면 노이즈로 가득 차게 됩니다. MemoryBank는 에빙하우스 망각 곡선 (Ebbinghaus forgetting curve)을 적용하여, 항목들이 시간이 지남에 따라 감쇠하고 사용 빈도에 따라 강화되도록 합니다.
정리 작업은 언제 실행되는가? 점점 더 오프라인 방식으로 전환되고 있습니다. Generative Agents는 2023년에 성찰 (reflection) 개념을 도입하여, 정해진 일정에 따라 가공되지 않은 관찰 결과들을 상위 수준의 결론으로 합성했습니다. Letta는 이를 수면 시간 연산 (sleep-time compute)으로 발전시켜, 요청을 대기하는 동안이 아닌 유휴 시간에 사고를 수행하게 함으로써 벤치마크 상에서 쿼리 시간 (query-time)의 작업량을 약 5배 줄였습니다. Anthropic은 최근 세션을 탐색하고 실행 간의 공유 기억을 큐레이션하는 '드림 (dreaming)'이라 불리는 버전을 출시했습니다. 구체적인 내용은 강연을 통해 공개된 것이므로 예비적인 정보로 취급하십시오. 원칙은 일관됩니다: 비용이 많이 드는 기억 과정을 임계 경로 (critical path)에서 분리하는 것입니다.
문제점들
그렇다면 왜 이 문제가 해결되지 않았을까요? 각 계층에는 데모에서 생략되는 실패 모드 (failure mode)가 존재합니다. 알아둘 가치가 있는 여섯 가지가 있습니다.
| 실패 유형 | 발생 원인 | 해결책 |
|---|---|---|
| 신선도 저하 (Staleness) | 유사도에는 시계가 없음 | 최신성 감쇠 (recency decay) + 시간적 저장소 (temporal store) |
| ... | ||
| 이 중 세 가지는 표 한 줄 이상의 설명이 필요합니다. |
**신선도 저하 (Staleness)**는 실제 운영 환경에서 뼈아프게 다가오는 문제입니다. 에이전트가 Postgres 13을 학습했습니다. 그런데 당신이 14로 업그레이드했습니다. 기존 노트가 여전히 가장 유사한 매칭 항목이므로, 에이전트는 확신을 가지고 이를 검색합니다. 벡터 유사도 (Vector similarity)에는 시간이라는 개념이 없습니다. Generative Agents는 이미 해결책을 보여주었습니다. 기억을 단순히 유사도만으로 점수 매기는 것이 아니라, 최신성과 중요도에 따라 가중치를 둔 유사도로 점수를 매기는 것입니다. 이를 한 줄로 요약하면, 최신성을 고려한 점수 (recency-aware score)는 다음과 같습니다.
score(m) = similarity(query, m) · e^(−λ · Δt)
여기서 $\Delta t$는 메모리가 마지막으로 검증된 이후 경과된 시간이며, $\lambda$는 사실 유형(fact type)별로 조정하는 감쇠 상수 (decay constant)입니다. 인프라 버전의 경우 높은 값을, 사람의 이름의 경우 0에 가까운 값을 사용합니다.
라우터(router)로부터 메타데이터 필터를 가져오고 각 사실이 언제 유효했는지를 기록하는 시계열 저장소 (temporal store)를 추가하면, 정보의 노후화 (staleness)는 소리 없이 발생하는 현상에서 감지 가능한 현상으로 바뀝니다. 이 지수 감쇠 (exponential-decay) 형태는 MemoryBank가 망각 곡선 (forgetting curve)에서 빌려온 것과 동일한 개념입니다.
**문맥 부패 (Context rot)**는 미묘한 문제입니다. 이는 비유가 아니라 Anthropic이 문서화한 용어입니다. 문맥 창 (context window) 내의 토큰 수가 증가함에 따라, 해당 문맥으로부터 정확하게 회상하는 모델의 능력이 감소합니다. 따라서 특정 지점을 넘어서까지 더 많은 검색된 메모리를 밀어 넣는 것은 답변을 개선하는 것이 아니라 오히려 악화시킵니다. 더 많은 토큰이 더 많은 지능을 의미하지는 않습니다. 선택 (Selection)이 핵심적인 작업입니다.
**비용 (Cost)**은 장기 실행 에이전트를 조용히 죽이는 요인입니다. 매 호출마다 전체 문맥이 입력 토큰 (input tokens)으로 다시 청구되며, 이러한 입력 토큰을 처리하는 과정인 프리필 (prefill) 단계가 긴 히스토리를 다룰 때 지불하는 비용의 대부분을 차지합니다. 프롬프트 캐싱 (Prompt caching)은 반복되는 프리필의 부담을 완화해주지만, 이를 완전히 없애지는 못합니다. 비대해진 메모리에 의존하는 에이전트는 매 턴마다 그 세금을 지불하며, 일정 수준을 넘어서면 워크플로우는 단순히 느려지는 것이 아니라 재정적으로 실행 불가능해집니다. 필요한 규율은 문맥 부패가 요구하는 것과 동일합니다. 검색된 모든 것을 제공하는 것이 아니라, 신호가 높은 (high-signal) 최소한의 토큰 세트만을 모델에 공급해야 합니다.
이 여섯 가지 요인 아래에는 진짜 근본적인 원인이 자리 잡고 있습니다. 이 중 그 어떤 것도 모델 자체를 바꾸지는 않습니다. 우리는 고정된 함수 (fixed function)에 전달하는 텍스트를 정제하고 있는 것입니다. 왜냐하면 함수를 바꾼다는 것은 실제 경험으로부터 가중치 (weights)를 업데이트하는 것을 의미하는데, 모델의 드리프트 (drifting) 없이 이를 안전하고 저렴하게 수행하는 방법은 아직 아무도 보여주지 못했기 때문입니다. 위의 모든 기술은 그 벽을 피하기 위한 방법들입니다.
도달하는 방법
그렇다면 어떻게 이 격차를 줄일 수 있을까요? 세 가지 지평(Three horizons)이 있습니다.
| 지평 (Horizon) | 모습 | 상태 |
|---|---|---|
| 현재 (Now) | 하이브리드 검색 (hybrid retrieval), 타임스탬프 (timestamps), 통합 (consolidation), 자체 평가 (your own evals) | 현재 사용 가능 |
| ... |
만약 당신이 오늘 에이전트를 구축하고 있다면:
- 벡터 (vectors) 및 키워드 (keywords)로 검색하여, 정확한 이름과 용어가 퍼지 유사도 (fuzzy similarity)로 인해 손실되지 않도록 하세요.
- 모든 사실에 타임스탬프 (timestamp)를 찍고 검색 시 감쇠 (decay) 시키세요. 그래야 오래된 메모가 최신 메모보다 높은 순위를 차지하지 않습니다.
- 세션 사이에 오프라인 패스 (offline pass)를 추가하여, 에이전트가 매 실행마다 똑같은 교훈을 다시 배우는 일을 멈추게 하세요.
- 자체 평가 (eval)를 먼저 작성하세요. 리더보드 점수는 당신의 업무량이 아니기 때문입니다.
중기적으로, 메모리는 인프라 (infrastructure)가 됩니다. 에이전트가 실행 지침서 (runbook)를 읽을 수는 있지만 이를 손상시키지는 못하도록 하는 권한 (Permissions) 설정, 그리고 무엇을 언제 왜 저장했는지 확인하고 잘못되었을 때 되돌릴 수 있는 버전 관리 (Versioning) 및 감사 로그 (audit logs)가 필요합니다. 메모리는 단순한 텍스트 덩어리 (text blob)를 벗어나 이력을 가진 하나의 시스템이 됩니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기

