
확장 가능한 에이전트 메모리 아키텍처를 처음부터 구축하기 (LangGraph, AutoGen 미사용)
요약
LangGraph나 AutoGen 같은 기존 프레임워크를 사용하지 않고, LLM의 stateless 특성을 극복하기 위한 커스텀 에이전트 메모리 아키텍처 구축 과정을 다룹니다. 슬라이딩 윈도우 방식의 한계를 넘어 계층형 메모리 구조를 설계하여 토큰 효율성과 문맥 유지력을 높이는 방법을 설명합니다.
핵심 포인트
- LLM API의 stateless 특성으로 인한 문맥 관리의 중요성
- 슬라이딩 윈도우 방식의 한계인 채팅 건망증과 프로젝트 오염 문제
- 토큰 효율성을 극대화하기 위한 계층형 메모리 아키텍처 설계
- 워크스페이스, 프로젝트, 채팅으로 이어지는 엄격한 물리적 계층 구조
저는 최근 LangGraph, CrewAI, AutoGen 등을 전혀 사용하지 않고 완전히 처음부터 커스텀 에이전트 AI (Agentic AI) 프레임워크를 구축하는 작업을 진행해 왔습니다. 목표는 자체적인 오케스트레이터 (orchestrator), 동적 도구 실행 (dynamic tool execution), 그리고 커스텀 메모리 관리 (custom memory management) 기능을 갖춘 시스템을 만드는 것이었습니다.
본격적으로 시작하기 전에, AI 엔지니어링의 근본적인 문제점을 인정해야 합니다. 바로 LLM API는 완전히 상태가 없습니다 (stateless). 모델에 프롬프트 (prompt)를 보낼 때, 모델은 10초 전에 당신이 무엇을 말했는지에 대한 기억이 전혀 없습니다. 에이전트가 문맥 (context)을 가질 수 있도록 API를 호출할 때마다 대화의 전체 이력을 매번 다시 제출해야 합니다. 이 문맥을 어떻게 패키징하고, 압축하며, LLM에 전달할지 설계하는 것이 자율 에이전트 (autonomous agent)를 구축하는 과정에서 아마도 가장 어려운 부분일 것입니다.
초기 단계에서 에이전트는 취약한 메모리 아키텍처를 가지고 있었습니다. 문맥 눈덩이 효과 (context snowballing, 즉 방대한 pip install 로그나 코드 출력이 LLM의 문맥 창 (context window)을 넘쳐흐르는 현상)를 방지하기 위해, 단순히 단순한 슬라이딩 윈도우 (sliding window) 방식과 공격적인 출력 절단 (output truncation)에 전적으로 의존했습니다.
하지만 테스트가 진행됨에 따라, 저는 두 가지 거대한 병목 현상에 빠르게 직면했습니다:
- 채팅 건망증 (Chat Amnesia): 슬라이딩 윈도우 때문에, 대화가 충분히 길어지면 에이전트는 채팅 초기에 내린 중요한 아키텍처 결정 사항들을 말 그대로 "잊어버리게" 됩니다.
- 프로젝트 오염 (Project Pollution): 서로 다른 작업 사이를 전환하거나 이전 채팅에서 문맥을 가져올 때, 문맥이 서로 섞여버려 에이전트가 실제로 어떤 프로젝트를 작업 중인지 혼동하게 됩니다.
궁극적인 동기: 저는 최소한의 토큰 (token) 양을 사용하면서도 에이전트가 프로젝트 문맥을 매우 명확하게 인지할 수 있는 시스템이 필요했습니다. 에이전트에게 원시 로그 (raw logs)를 계속해서 다시 읽고 다시 분석하도록 강요하는 것은 값비싼 API 토큰을 소모하고 시간을 낭비하게 만듭니다. 저는 매우 토큰 효율적인 메모리 구조가 필요했습니다.
이를 극복하기 위해, 저는 **계층형 메모리 아키텍처 (Tiered Memory Architecture)**를 설계하고 구축했습니다. 하지만 메모리에 대해 깊이 들어가기 전에, 데이터가 저장되는 물리적 계층 구조를 이해하는 것이 중요합니다.
계층 구조: 워크스페이스 (Workspaces), 프로젝트 (Projects), 그리고 채팅 (Chats)
모든 것을 체계적으로 유지하고 컨텍스트 혼입 (context-bleeding)을 방지하기 위해, 저는 물리적 환경을 엄격한 계층 구조로 설계했습니다:
- 루트 워크스페이스 폴더 (Root Workspace Folder): 모든 물리적 프로젝트 파일과 중앙 SQLite 데이터베이스를 보유하는 마스터 디렉토리입니다.
- 프로젝트 (Projects, Workspaces): 개별적인 샌드박스 (sandboxed) 프로젝트 (예: 틱택토 게임 또는 React 앱)입니다. 각 프로젝트는 고유한
workspace_id를 가집니다. - 채팅 (Chats, Threads): 모든 프로젝트 내부에는 여러 개의 격리된 채팅 스레드 (예: 백엔드 구축을 위한 채팅 하나, UI 디버깅을 위한 완전히 별개의 채팅 하나)가 존재합니다. 이러한 엄격한 구조적 계층 구조가 계층형 메모리 아키텍처 (Tiered Memory Architecture)를 가능하게 합니다. 내부적으로 메모리가 어떻게 작동하는지 살펴보겠습니다.
해결책: 계층형 메모리 아키텍처 (G(P) 및 S(P))와 활성 슬라이딩 윈도우 (Active Sliding Window)
건망증 문제를 해결하고 토큰 효율성을 극대화하기 위해, 저는 에이전트의 메모리를 별도로 격리된 요약 레이어 (summary layers)로 나누었습니다. 추론 능력이 뛰어난 에이전트 (GPT-4o와 같은)는 단순히 가공되지 않은 메시지 목록을 받지 않습니다. 대신, 세 가지 핵심 구성 요소로 이루어진 완벽하게 균형 잡힌 저토큰 컨텍스트 페이로드 (low-token context payload)를 전달받습니다:
1. 글로벌 메모리 (Global Memory) — G(P) (정적 요약)
**G(P)**는 불변의 **프로젝트 청사진 (Project Blueprint)**을 나타냅니다. 워크스페이스가 초기화될 때 G(P)가 정의됩니다. 이는 에이전트에게 환경의 근본적인 사실을 알려줍니다 (예: "이것은 Python과 Tkinter를 사용하여 구축된 커넥트 포 게임입니다"). 역할: 이는 정적 닻 (static anchor) 역할을 합니다. 에이전트가 디버깅의 미궁 속으로 아무리 깊이 빠지더라도, G(P)는 프로젝트의 포괄적인 목표를 절대 잊지 않도록 보장합니다.
2. 세션 메모리 (Session Memory) — S(P) (동적 요약)
**S(P)**는 동적인 **스레드별 컨텍스트 (Thread-Specific Context)**를 나타냅니다. 모든 개별 채팅 스레드는 각자의 S(P)를 가집니다. 이는 이 특정 대화에서 정확히 어떤 일이 일어났는지에 대한 매우 압축된 운영 요약본입니다 (예: "방금 UI 그리드를 구축했지만, 현재 basic_gui.py에서 절대 임포트 (absolute import) 에러를 디버깅 중입니다"). 역할: 이는 채팅 건망증 (Chat Amnesia)과 토큰 소모 (Token Burning) 문제를 해결합니다. 이 요약을 지속적으로 업데이트함으로써, 에이전트는 수천 개의 토큰에 달하는 원문 메시지 기록 없이도 채팅의 여정을 기억할 수 있습니다.
3. 액티브 슬라이딩 윈도우 (Active Sliding Window, 즉각적 컨텍스트)
G(P)와 S(P)가 고수준의 요약을 제공하는 동안, 추론 능력이 높은 LLM (Heavy-reasoning LLM)은 코드를 효과적으로 작성하기 위해 정확히 5초 전에 무슨 일이 일어났는지 여전히 알아야 합니다. 여기서 **액티브 슬라이딩 윈도우 (Active Sliding Window)**가 등장합니다. 이는 오직 마지막 n개의 원문 메시지(보통 직전의 프롬프트와 최근의 도구 출력값)만을 포함합니다. 역할: 이는 거대 에이전트 (Heavy Agent)가 방대한 로그로 인해 영구 메모리가 오염되는 것을 방지하면서도, 현재 단계를 해결하는 데 필요한 즉각적인 라인 단위 구문(syntax)과 터미널 에러를 제공합니다.

메모리 라이프사이클: 컨텍스트의 폴딩(Folding) 및 복구(Restoring)
S(P) 업데이트 (백그라운드 폴딩 (Background Folding)): S(P)는 정적이지 않으며 계속 진화합니다. 채팅 스레드가 특정 임계값(예: 슬라이딩 윈도우(sliding window) 내 n개의 프롬프트)에 도달하면, 비동기 백그라운드 태스크(async background task)가 트리거됩니다. 이 태스크는 가장 오래된 원문 메시지들을 잘라내어, 더 작고 저렴한 SLM(예: ministral-3b)으로 보내 업데이트된 S(P) 요약본을 생성합니다. 그 후 원문 메시지들은 ChromaDB에 아카이브되어, 활성 슬라이딩 윈도우를 매우 작고 토큰 효율적으로 유지합니다.
컨텍스트 복구 (Restoring Context): 사용자가 채팅을 종료하면, 해당 세션의 원문 슬라이딩 윈도우는 기본적으로 활성 프로세싱에서 제외됩니다. 하지만 S(P)가 지속적으로 업데이트되어 SQLite에 저장되었기 때문에 메모리는 손실되지 않습니다. 사용자가 이전 채팅을 다시 열면, 에이전트는 정적인 **G(P)**와 동적인 **S(P)**의 조합을 함께 참조함으로써 즉각적으로 전체 컨텍스트를 회복합니다.
프로젝트 간 격리 처리 (Handling Cross-Project Isolation)
가장 어려운 과제 중 하나는 **프로젝트 오염 (Project Pollution)**을 유발하지 않으면서 에이전트가 이전 채팅을 참조할 수 있도록 하는 것이었습니다.
이를 해결하기 위해 엄격한 격리 프로토콜을 구현했습니다. S(P)는 SQLite 내부의 특정 chat_id에 엄격하게 결합됩니다. 만약 코드를 빌려오기 위해 프로젝트 간 채팅을 연다면, 프레임워크는 **읽기 전용 방화벽 (Read-Only Firewall)**을 적용합니다. 에이전트는 시맨틱 검색(semantic search)을 사용하여 이전 채팅의 로직을 읽을 수는 있지만, 이전 채팅의 메모리 상태는 활성 워크스페이스의 G(P)로부터 완전히 샌드박스(sandboxed) 처리되어 격리됩니다.
구현 세부 사항: SQLite, ChromaDB, 비동기 락 (Async Locks)
메모리 시스템의 실제 흐름은 에이전트의 속도를 유지하기 위해 매우 비동기적으로 작동합니다. 기술 스택은 다음과 같습니다:
1. 컨텍스트 조립 (The Context Assembly)
사용자가 프롬프트를 보내면, 메모리 매니저(Memory Manager)는 ChromaDB(Nomic 임베딩 사용)를 쿼리하여 과거 대화에서 관련 있는 시맨틱 청크(semantic chunks)를 가져옵니다. 그런 다음 G(P) + S(P) + RAG 청크 + 최근 원문 메시지들을 하나의 조밀한 페이로드(dense payload)로 엮어 오케스트레이터(Orchestrator)에게 전달합니다.
2. 대칭 락 (Symmetrical Locks)을 통한 레이스 컨디션 (Race Conditions) 해결
Heavy Agent가 SQLite 데이터베이스에 새로운 도구 출력(tool outputs)을 지속적으로 쓰는 동안, Background Folder가 동시에 동일한 데이터베이스를 읽고, 요약하고, 잘라내려(truncate) 하기 때문에, 데이터 손상을 일으키는 심각한 레이스 컨디션 (Race Conditions) 문제에 즉시 직면했습니다.
이를 해결하기 위해, 저는 대칭 메모리 락 (Symmetric Memory Lock) (chat_id별로 매핑된 asyncio.Lock)을 구현했습니다. Background Folder가 데이터베이스를 잘라내기 전에 락을 획득합니다. 이는 **원자적 잘라내기 (Atomic Truncation)**를 보장합니다. 즉, SLM 요약과 ChromaDB 임베딩 (embedding)이 모두 성공했을 때만 SQLite 슬라이딩 윈도우 (sliding window)가 잘려나갑니다. 만약 둘 중 하나라도 실패하면 락이 해제되고, 데이터 손실을 방지하기 위해 슬라이딩 윈도우는 그대로 유지됩니다.
업계 표준에 대한 겸허한 성찰
이 메모리 계층 구조를 구축하기 시작했을 때, 저의 유일한 목표는 로컬 환경에서 겪고 있던 심각한 컨텍스트 (context) 제한과 토큰 소모 (token-burning) 문제를 해결하는 것이었습니다. 저는 어떠한 외부 프레임워크도 참고하지 않았습니다.
하지만 아키텍처를 완성한 후, 제가 구축한 이 설정이 오늘날 주요 AI 연구자들이 사용하는 골드 표준 (gold standard)과 매우 유사하다는 사실을 알고 놀라움과 동시에 확신을 얻었습니다! 계층형 메모리 (Tiered Memory, 정적 핵심 메모리 vs 동적 회상 메모리) 개념은 UC Berkeley의 유명한 MemGPT 논문의 기초입니다. 나아가, 정적인 프로젝트 청사진 (blueprint)과 스레드 격리된 요약본 (thread-isolated summaries)을 결합하는 방식은 Anthropic이 Claude Projects를 설계한 방식과 정확히 일치합니다.
자신만의 프레임워크를 처음부터 구축하면서, 일류 AI 연구소들과 동일한 아키텍처적 결론에 독립적으로 도달했다는 사실을 깨달은 것은 엔지니어로서 매우 보람찬 순간이었습니다. 이는 핵심적인 엔지니어링 병목 현상(예: 토큰 효율성)을 해결하는 데 깊이 집중하면, 올바른 아키텍처가 자연스럽게 드러난다는 것을 증명합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기