본문으로 건너뛰기
뒤로가기

[LLM Workbench] btwin Memory: 세션 밖에 맥락 남기기

TL;DR — LLM의 세션 기억에만 의존하면 작업 맥락이 쉽게 끊깁니다. btwin은 기억해야 할 내용을 markdown + frontmatter 문서로 외부화하고, TLDR 임베딩과 정합성 검증을 통해 다음 세션에서도 필요한 기록을 다시 찾도록 만든 개인용 context 관리 실험입니다.

Table of contents

Open Table of contents

들어가며

LLM을 여러 세션과 여러 디바이스에서 사용하다 보면 이전 대화의 맥락, 결정 이유, 다음 행동이 쉽게 끊깁니다. 이 글은 btwin에서 이 문제를 어떻게 파일 기반 memory / context 관리 구조로 풀었는지 정리합니다.

이 글에서 다루는 내용:

사전 지식: LLM 도구 사용 경험, MCP의 기본 개념, vector search의 기본 개념을 전제로 합니다.


1. 문제: 세션 안의 기억은 쉽게 끊깁니다

LLM과 함께 작업할 때 가장 먼저 불편했던 점은 모델의 답변 품질보다 작업 맥락의 지속성이었습니다. 같은 프로젝트를 다루고 있어도 세션이 바뀌면 이전에 어떤 결정을 했는지, 어떤 파일을 봤는지, 다음에 무엇을 해야 하는지 다시 설명해야 했습니다.

기존 memory 도구도 사용할 수 있었지만, 간단한 작업에는 무겁게 느껴졌습니다. 또한 사람이 현재 memory 상태를 직접 이해하기 어려우면, LLM이 어떤 기억을 기반으로 답하는지도 신뢰하기 어려웠습니다.

btwin의 첫 목표는 LLM이 모든 것을 기억하게 만드는 것이 아니었습니다. 기억해야 할 내용을 세션 밖으로 꺼내고, 사람이 읽을 수 있는 형태로 저장하고, 다음 세션에서 다시 찾을 수 있게 만드는 것이었습니다.

flowchart TD
    A["LLM Session"] -->|"중요한 결정, 작업 상태, 다음 행동"| B["External Record Store"]
    B -->|"검색 / handoff / context 복원"| C["Next Session"]

2. 구조: LLM 클라이언트와 기록 저장소를 분리합니다

btwin은 Codex를 중심으로 사용하되, LLM 클라이언트 뒤에 얇은 MCP proxy와 공용 API runtime을 두는 구조를 사용했습니다.

flowchart TD
    A["Codex / MCP-capable LLM Client"]
    B["Local MCP Proxy"]
    C["Shared API Runtime"]
    D["Local Records / Context Store"]

    A --> B --> C --> D

이 구조에서 LLM 클라이언트는 사용자가 실제로 대화하는 표면입니다. MCP proxy는 LLM이 btwin의 기록, 검색, handoff 기능을 도구처럼 호출할 수 있게 하는 얇은 bridge입니다. 실제 상태 관리는 API runtime이 담당하고, 기록은 로컬 파일 시스템에 남습니다.

중요한 점은 LLM의 내부 기억이 source of truth가 아니라는 것입니다. source of truth는 사람이 열어볼 수 있는 로컬 기록입니다.


3. 기록: markdown + frontmatter로 저장합니다

btwin의 기본 기록 단위는 record입니다. 중요한 결정, 작업 내용, 문제 상황, 다음 행동을 하나의 문서로 남깁니다. 다음 세션이나 다른 에이전트가 바로 이어갈 수 있도록 현재 상태를 압축한 기록은 handoff로 다룹니다.

기록은 markdown + YAML frontmatter 형태로 저장합니다.

---
record_id: entry-...
record_type: entry
date: 2026-04-24
source_project: ...
tldr: "검색에 사용할 짧은 요약"
tags:
  - llm
  - context
---

본문은 사람이 읽을 수 있는 markdown으로 작성합니다.

이 방식을 선택한 이유는 두 가지입니다.

entry / convo 계열 기록은 canonical frontmatter를 validator로 검사합니다. 반면 orchestration / collaboration 기록은 별도 document contract를 따르도록 분리했습니다. 모든 기록을 하나의 schema로 억지로 맞추기보다, 기록의 성격에 따라 검증 기준을 나누는 쪽이 관리하기 좋았습니다.


4. 검색: 긴 문서 대신 TLDR을 임베딩합니다

저장된 문서를 다시 찾을 때 모든 문서를 매번 읽는 방식은 비효율적입니다. btwin은 semantic index 기준으로 긴 본문 전체가 아니라 frontmatter의 tldr를 임베딩합니다.

Record Markdown
    |
    +-- full body: 사람이 읽는 원문
    |
    +-- tldr: vector index에 들어가는 검색 요약

이 방식은 검색 비용을 줄이고, 사람이 작성한 요약 단위로 context를 회수할 수 있게 합니다. 물론 단점도 있습니다. tldr 품질이 낮으면 검색 품질도 함께 낮아집니다. 그래서 btwin에서는 TLDR을 필수 필드로 두고, 누락된 경우 검증 단계에서 문제로 다룹니다.

검색 점수는 vector similarity만 사용하지 않습니다. vector 후보군 안에서 lexical match와 최신성도 함께 반영합니다.

relevance = vector similarity 60% + lexical match 40%
final score = relevance * recency score

recency score는 30일 half-life 기반으로 계산해 최근 기록이 자연스럽게 더 높은 우선순위를 갖도록 했습니다. LLM 작업에서는 오래된 설계 문서보다 직전 handoff나 최근 결정이 더 중요한 경우가 많기 때문입니다.


5. 검증: 기록과 index가 어긋나지 않게 관리합니다

파일 기반 기록은 단순하고 투명하지만, 원본 문서와 검색 index가 어긋날 수 있습니다. btwin은 이 문제를 줄이기 위해 validator와 indexer 관리 도구를 둡니다.

구성역할
validatorfrontmatter 필드, TLDR, legacy field, contributors 상태를 검사
index manifest원본 markdown 문서와 vector index의 checksum / status를 추적
repair특정 문서를 stale 처리하고 다시 색인
reconcile파일 시스템과 index 상태를 맞추고 orphan vector를 정리
kpi / statusindex 상태를 사람이 확인할 수 있게 노출

이 구조는 memory 기능을 단순한 저장 기능이 아니라 운영 가능한 기록 시스템으로 만드는 데 중요했습니다. 기록이 많아질수록 “저장했다”보다 “검색 가능한 상태로 유지한다”가 더 중요해졌습니다.


6. 저장 위치: 글로벌 기억과 테스트 상태를 분리합니다

btwin은 기록 저장 위치를 다음 순서로 결정합니다.

BTWIN_DATA_DIR -> ./.btwin -> ~/.btwin

평소에는 글로벌 기억 저장소인 ~/.btwin을 사용할 수 있고, 테스트나 isolated runtime에서는 BTWIN_DATA_DIR로 별도 저장소를 지정할 수 있습니다. 이 분리는 memory 도구를 실제 작업에 붙일 때 중요합니다. 테스트 도중 만든 기록이 개인 작업 기억과 섞이지 않아야 하기 때문입니다.


정리하며

핵심 요약:

참고 자료


이 게시물은 학습한 내용을 바탕으로 초안을 작성한 뒤, LLM의 도움을 받아 내용을 검수하고 다듬어 완성되었습니다.


공유하기:

이전 글
일러스트풍 3D가 FPS 카메라에서 버티는 조건: 외곽선 두 번, 텍스처 다섯 번, 그림자 한 번 갈아엎고 남은 것
다음 글
[LLM Workbench] btwin Orchestration: 에이전트 작업 흐름을 작게 제어하기