내가 로컬 우선(Local-First) 에이전트 메모리를 구축한 이유
요약
AI 에이전트의 메모리 구축 시 발생하는 벡터 임베딩의 지연 시간, 비용, 불투명성 문제를 지적합니다. 대안으로 SQLite의 FTS5를 활용한 로컬 우선(Local-first) 메모리 레이어의 장점과 실용성을 제안합니다.
핵심 포인트
- 벡터 임베딩은 네트워크 지연, 비용, 재인덱싱 문제 등 숨겨진 비용이 발생함
- 벡터 검색은 결과의 원인을 파악하기 어려운 불투명성을 가짐
- SQLite FTS5를 활용하면 오프라인에서도 작동하는 결정론적 검색 가능
- 로컬 우선 접근 방식은 데이터 제어권과 디버깅 용이성을 높임
AI 에이전트가 자신이 수행한 일을 기억해야 할 때, 대부분의 제품 로드맵은 벡터 임베딩 (Vector Embeddings)을 곧바로 가리킵니다. 그 약속은 간단합니다. 대화를 고차원 점(high-dimensional point)으로 변환하여 저장한 다음, 나중에 모델에게 가장 가까운 점을 찾으라고 요청하는 것입니다. 하지만 실제로 그 약속은 클라우드 지연 시간 (Cloud Latency), 불투명한 저장소, 그리고 지속적으로 학습되어야 하는 임베딩 모델에 대한 의존성 등 수많은 숨겨진 비용을 초래합니다. 저장 효율성과 쿼리 속도 사이의 균형을 맞추며 매일을 보내는 데이터 엔지니어로서, 저는 로컬 파일만큼이나 즉각적이어야 할 도구에 대해 이러한 트레이드오프 (Trade-offs)를 받아들이기 어렵다고 느꼈습니다.
LoreConvo를 구축하던 첫 몇 달 동안, 저는 잘 조정된 전체 텍스트 검색 (Full-text search) 엔진이 임베딩의 부담 없이도 동일한 재현율 (Recall) 품질을 제공할 수 있음을 증명하고자 했습니다. 그 결과, 단일 SQLite 파일 내에 존재하며 오프라인에서도 작동하고, 제가 사용하는 모든 주요 AI 인터페이스와 통합되는 로컬 우선 (Local-first) 메모리 레이어를 만들어냈습니다. 아래에서는 임베딩 전용 메모리의 문제점을 살펴보고, 왜 SQLite + FTS5가 실용적인 대안인지 설명하며, 실제 사용 시 관찰한 내용과 하이브리드 (Hybrid) 접근 방식이 여전히 유효한 시나리오를 개괄하겠습니다.
임베딩은 강력해 보이지만, 복잡성을 숨기고 있습니다
임베딩 모델은 어떤 문자열이든 고정된 크기의 벡터 (Vector)로 변환하기 때문에 매력적입니다. 일단 벡터를 확보하면, 이를 최근접 이웃 (Nearest-neighbor) 인덱스에 넣고 단 한 번의 내적 (Dot-product) 연산으로 "유사한" 세션을 검색할 수 있습니다. 사고 모델은 깔끔하며, 많은 클라우드 제공업체들이 이를 관리형 서비스 (Managed service)로 제공합니다.
하지만 숨겨진 부작용은 프로덕션 워크플로우(production workflow)에서 빠르게 드러납니다. 첫째, 모든 새로운 세션은 임베딩 엔드포인트(embedding endpoint)로 전송되어야 하며, 이는 네트워크 지연 시간(network latency)과 호출당 비용을 추가합니다. 하루에 수십 개의 에이전트 세션을 실행하는 팀의 경우, 이러한 비용은 계속 누적됩니다. 둘째, 인덱스(index)가 개발자의 통제 범위를 벗어나 있습니다. 제공업체가 모델을 업데이트하면 저장된 벡터(vectors)를 더 이상 비교할 수 없게 되어, 비용이 많이 드는 재인덱싱(re-index)을 강제하게 됩니다. 셋째, 벡터 자체는 불투명합니다. 특정 세션이 왜 반환되었는지 이해하기 위해 벡터를 조사할 수 없으며, 이는 재현성(reproducibility)에 의존하는 누구에게나 디버깅을 어려운 과제로 만듭니다.
마지막으로, 임베딩(embeddings)은 프로젝트의 자연스러운 경계를 존중하지 않습니다. 데이터 파이프라인(data pipeline)에는 각각 고유한 용어를 가진 수십 개의 관련 없는 하위 작업(sub-tasks)이 포함될 수 있습니다. 순수 벡터 인덱스는 문맥이 완전히 다르더라도 몇 개의 일반적인 단어를 공유하는 세션을 기꺼이 반환합니다. 그 결과, 리콜(recall)이 "충분히 가까운" 것처럼 느껴지지만, 엔지니어가 필요로 하는 정확한 결정이나 도구 호출(tool call)을 놓치는 경우가 빈번합니다.
SQLite의 전체 텍스트 검색(Full-text search)은 결정론적 리콜을 제공합니다
SQLite는 수십 년 동안 임베디드 스토리지(embedded storage)의 일꾼 역할을 해왔으며, FTS5 확장 기능은 강력한 전체 텍스트 검색(full-text search) 기능을 추가합니다. 세션의 모든 토큰(token)을 인덱싱함으로써, FTS5는 접두사 일치(prefix matching), 복합 토큰 확장(compound token expansion), 그리고 단어 빈도(term frequency)에 기반한 랭킹(ranking)을 통해 키워드 쿼리에 답할 수 있습니다. 이 엔진은 에이전트와 동일한 프로세스 내에서 완전히 실행되므로, 네트워크 홉(network hop)이 발생하지 않으며 외부 비용도 발생하지 않습니다.
LoreConvo를 위한 크로스 서피스 세션 메모리(cross-surface session memory)를 구축했을 때, 저는 각 대화를 단일 SQLite 파일의 한 행(row)으로 저장했습니다. 스키마(schema)는 원문 트랜스크립트(transcript), 간결한 요약, 사용된 도구 목록, 그리고 세션에 부착된 모든 태그를 캡처합니다. 자동 저장 훅(auto-save hook)은 매 세션이 끝날 때 실행되어 휴리스틱 요약(heuristic summary)을 추출하고, 사용자의 별도 작업 없이 해당 행을 기록합니다. 파일의 소유권이 사용자에게 있기 때문에 백업, 마이그레이션(migration), 버전 관리(version control)가 매우 간단합니다. 내보내기(export) 및 가져오기(import) 명령을 통해 리포지토리(repo)에 체크인하거나 몇 초 만에 새 기기로 옮길 수 있는 JSON을 생성할 수 있습니다.
진정한 놀라움은 검색 성능에서 나타났습니다. 프로젝트 태그와 기술 이름을 포함하는 키워드 쿼리(keyword query)를 실행하면, 노트북 CPU에서도 1초가 채 걸리지 않아 가장 관련성 높은 세션들을 반환합니다. 세션 타임스탬프(timestamp)를 기준으로 정렬하는 간단한 최신성 부스트(recency boost)를 추가하면, 벡터 인덱스(vector index)만큼 신선한 느낌을 주면서도 CLI에서 직접 확인할 수 있는 결정론적 순위(deterministic ranking)를 제공하는 결과를 얻을 수 있습니다. 직접적인 성능 비교를 원하신다면, FTS5를 Chromadb 및 기타 벡터 기반 대안들과 비교하여 실행한 벤치마크를 참조하십시오.
인덱스(index)가 동일한 파일 내에 존재하기 때문에 저장 오버헤드(storage overhead)는 미미합니다. 수천 개의 세션을 보유한 SQLite 데이터베이스는 별도의 서비스에서 동일한 수의 고차원 벡터(high-dimensional vectors)를 사용할 때 필요한 용량의 극히 일부만을 차지합니다. 이 파일은 압축하거나 복사할 수 있으며, 팀 메모리 내보내기(team memory export) 기능을 사용하여 팀원들과 공유할 수 있습니다. 이 기능은 서버 없이 선택된 세션들을 새로운 SQLite 파일로 병합합니다.
프로덕션 환경에서 실행하며 관찰한 점
저의 에이전트 플릿(agent fleet)을 대상으로 LoreConvo를 실행하면서 나타난 패턴들은 이러한 설계 선택이 옳았음을 뒷받침해 주었습니다.
키워드 중심의 엔지니어링 쿼리(engineering queries)에 대한 재현율(Recall quality)은 일관되게 높았습니다. 특정 도구 호출(tool call) — 즉, 특정 함수 이름, 모듈 경로, 설정 플래그(configuration flag) — 을 검색했을 때, FTS5는 거의 매번 첫 페이지에서 올바른 세션을 반환했습니다. 검색에 실패한 사례들은 호출 내용이 다르게 토큰화(tokenized)된 코드 블록(code block) 내부에만 존재했던 세션들이었습니다. 코드 블록 콘텐츠에 보조 인덱스(secondary index)를 추가함으로써 이러한 격차의 대부분을 해소할 수 있었습니다.
데이터베이스가 커짐에 따라에도 쿼리 속도는 반응성을 유지했습니다. FTS5는 프로세스 내(in-process)에서 실행되기 때문에 콜드 스타트(cold-start) 지연 시간이 없습니다. 아침에 저장된 세션은 인덱싱 지연 없이 오후에 즉시 검색이 가능했습니다.
디버깅 용이성(Debuggability)은 가장 과소평가된 이점이었습니다. inspect 명령어를 사용하면 세션을 나열하고, 태그별로 필터링하며, 원본 트랜스크립트(raw transcript)를 볼 수 있습니다. 쿼리가 예상치 못한 결과를 반환할 때, CLI에서 해당 세션을 직접 열어 일치하는 정확한 라인을 확인할 수 있었습니다. 블랙박스 벡터 저장소(black-box vector store)에서는 이러한 수준의 투명성을 얻을 수 없으며, 이 덕분에 디버깅은 좌절스러운 추적 작업에서 5분 내외의 간단한 연습으로 바뀌었습니다.
비용 예측 가능성(Cost predictability)은 진정한 안도감을 주었습니다. 모든 세션 쓰기(write)와 모든 검색은 한계 비용(marginal cost)이 0인 상태로 실행되었습니다. 유일한 API 지출은 선택 사항인 백그라운드 요약기(background summarizer)를 위한 것이었습니다. 이는 Pro 버전에서 자동 저장된 세션을 LLM 품질의 요약으로 업그레이드하는 Claude Haiku 호출이었으며, 설정 가능한 일일 한도(daily cap)가 있는 옵트인(opt-in) 방식이었습니다.
임베딩(embeddings)이 여전히 역할을 하는 경우
임베딩(embeddings)이 쓸모없다고 주장하는 것이 아닙니다. 키워드 중첩(keyword overlap)을 넘어선 의미론적 유사성(semantic similarity)이 필요한 시나리오가 분명히 존재합니다. 만약 팀이 "서비스 전반의 인증(authentication)을 어떻게 처리했나요?"와 같이 추상적인 질문을 자주 던지고, 시스템이 정확한 키워드 없이도 OAuth, JWT, API 키를 논의하는 세션들을 찾아내기를 기대한다면, 하이브리드(hybrid) 방식이 도움이 될 수 있습니다. 이러한 트레이드오프(trade-offs)를 평가하는 방법에 대해서는 benchmark hype vs real memory: what actually matters when you choose a memory tool에서 더 자세히 다루었습니다.
LoreConvo의 Pro 티어는 LanceDB를 기반으로 구축된 의미론적 검색(semantic search) 레이어를 제공하며, 이는 벡터 유사성(vector similarity, BGE-small-en 임베딩 모델 사용)과 FTS5의 기존 BM25 랭킹을 결합합니다. 융합 알고리즘(fusion algorithm)은 상호 순위 융합(reciprocal rank fusion)과 최신성 감쇠 재순위 지정기(recency decay reranker)를 적용하여, 정확한 일치(exact matches)와 개념적으로 관련된 세션을 모두 포착하는 결과를 제공합니다. 하이브리드 인덱스(hybrid index)는 rebuild-index 명령어로 한 번 구축하면 되며, 필요할 때마다 새로 고칠 수 있습니다.
핵심은 임베딩(embeddings)을 대체재가 아닌 보완재(augmentation)로 취급하는 것입니다. 기본 메모리를 SQLite에 유지함으로써, 오프라인에서 비용 없이 작동하는 결정론적 핵심(deterministic core)을 보존할 수 있습니다. 추가적인 의미론적 탐색 범위가 필요할 때는 동일한 데이터 파일 위에 하이브리드 레이어를 활성화하기만 하면 되며, 이 과정에서 이미 신뢰하고 있는 모든 백업 및 내보내기(export) 기능을 그대로 유지할 수 있습니다.
엔지니어의 워크플로를 존중하는 메모리 레이어 구축하기
LoreConvo의 설계 결정은 로컬리티(locality), 투명성(transparency), 제어권(control)이라는 세 가지 원칙을 따랐습니다. 로컬리티(locality)는 전체 메모리가 개발자의 로컬 머신에 존재함을 의미하며, 클라우드 지연 시간(latency)을 제거하고 전체 히스토리에 즉각적으로 접근할 수 있게 합니다. 투명성(transparency)은 단 한 번의 명령으로 세션을 조사, 내보내기 및 삭제할 수 있는 CLI 도구에서 비롯됩니다. 제어권(control)은 세션 시작 시 관련 컨텍스트(context)를 자동으로 로드하고, 세션 종료 시 수동 작업 없이 간결한 요약을 저장하는 훅(hooks)을 통해 제공됩니다.
벤더 간 MCP 호환성(Cross-vendor MCP compatibility)을 통해 동일한 SQLite 파일을 Claude Code, OpenAI Codex, Cursor, 그리고 Hermes Agent에서 사용할 수 있습니다 -- 클라이언트별 설정이 전혀 필요하지 않습니다. .mcp.json 설정 파일은 프로젝트 루트(root)에 배치되며, 각 클라이언트는 추가 설정 없이 이를 읽어들입니다. MCP 서버를 등록할 수 없는 환경의 경우, Python 폴백(fallback) 스크립트가 메모리 계층(memory layer)에 대한 직접적인 접근을 제공합니다.
팀 협업 또한 로컬 우선(local-first) 방식을 유지합니다. 프로(Pro) 사용자는 선택한 세션을 JSON으로 내보내고, 파일을 공유하며, loreconvo merge 명령어를 통해 팀원의 데이터베이스에 병합할 수 있습니다. 중앙 서버가 필요하지 않으므로, 데이터 보안에 민감한 많은 조직의 보안 정책과도 부합합니다.
LoreConvo는 단일하고 휴대 가능한 SQLite 파일에 집중함으로써, 임베딩 서비스(embedding services)의 숨겨진 비용을 피하는 동시에 필요한 사용자에게는 선택적인 시맨틱 계층(semantic layer)을 제공합니다. 그 결과, 즉각적으로 느껴지며, 감사(audit)가 용이하고, 서비스 제공업체의 과금 주기(billing cycle)가 아닌 엔지니어 자신의 하드웨어와 함께 확장되는 메모리 시스템을 구현했습니다.
사용자가 직접 제어할 수 있는 메모리 계층을 시도해보고 싶다면, 무료 티어(free tier)로 시작하거나 하이브리드 검색(hybrid search) 및 팀 메모리(team-memory) 기능을 위해 프로(Pro) 버전을 살펴보세요. 모든 내용은 labyrinthanalyticsconsulting.com/tools에 문서화되어 있습니다. 에이전트 군단(agent fleet)을 위한 메모리 아키텍처를 평가 중이며, 이를 실제 운영 환경(production)에서 실행해 본 사람과 함께 트레이드오프(trade-offs)를 고민하고 싶다면 labyrinthanalyticsconsulting.com/contact로 문의해 주세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기