본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 01. 04:25

prism-mem: AI 코딩 에이전트를 위한 자동 지식 추출

요약

prism-mem은 AI 코딩 에이전트의 세션 간 상태 유지 문제를 해결하기 위해 트랜스크립트와 git diff에서 지식을 자동 추출하는 기술입니다. 수동 컨텍스트 업데이트의 번거로움을 없애고, 구조화된 그래프 형태의 지식을 통해 에이전트에게 정확한 프로젝트 맥락을 제공합니다.

핵심 포인트

  • 세션 트랜스크립트와 git diff를 활용한 자동 지식 추출
  • 데이터 압축을 통해 컨텍스트 크기를 약 35배 감소
  • 지식 그래프를 통한 모순된 정보 자동 감지 및 업데이트
  • 수동 CLAUDE.md 관리의 한계 및 비효율성 해결

AI 코딩 에이전트 (AI coding agents)는 세션 간에 상태를 유지하지 않습니다 (stateless). 새로운 세션을 시작할 때마다, 에이전트는 당신이 어제 무엇을 만들었는지, 왜 특정 결정을 내렸는지, 또는 무엇을 명시적으로 중단하기로 결정했는지에 대해 아무것도 알지 못합니다. 당신은 CLAUDE.md를 수동으로 작성하지만, 이는 몇 번의 세션이 지나면 쓸모없게 되며, 당신은 매 세션의 첫 몇 분을 에이전트가 이미 알고 있어야 할 컨텍스트 (context)를 다시 설명하는 데 소비합니다.

실제 프로젝트를 대상으로 한 3회 세션 데모에서, prism-mem은 **411,463 바이트 (bytes)**의 가공되지 않은 Claude Code 트랜스크립트 (transcripts)를 구조화되고 쿼리가 가능한 (queryable) 11,707자의 지식으로 압축했습니다. 이는 수동 업데이트 없이 35배의 감소를 달성한 것입니다. 세션 4를 시작한 에이전트는 직접 목격하지 못한 데이터베이스 마이그레이션 (database migration)을 포함하여, 세션 1, 2, 3에서 발생한 모든 일에 대한 정확한 컨텍스트를 보유하고 있었습니다.

prism-mem은 당신에게 기록해 달라고 요청하는 대신, 실제로 일어난 일을 읽음으로써 이 작업을 수행합니다.

🤔 수동 컨텍스트 파일의 문제점

실제 프로젝트에서 일반적으로 발생하는 상황은 다음과 같습니다:

  • 세션 1: 당신은 깔끔한 CLAUDE.md를 작성합니다. 기술 스택 (Tech stack), 결정 사항, 컨벤션 (conventions). 내용은 정확합니다.
  • 세션 3: 당신은 SQLite에서 PostgreSQL로 마이그레이션했습니다. CLAUDE.md를 업데이트하는 것을 잊었습니다.
  • 세션 5: 에이전트는 컨텍스트 파일에 적힌 대로 자신 있게 SQLite API를 사용합니다.

수동 컨텍스트 파일은 스냅샷 (snapshots)입니다. 그것들은 당신이 작성하는 시점에 중요하다고 생각하여 기록한 내용을 반영할 뿐입니다. 그것들은 세션 중에 발생하는 수십 가지의 작은 결정들을 포착하지 못합니다: 당신이 시도했다가 포기한 라이브러리, 당신이 전환한 아키텍처 패턴 (architecture pattern), 데이터 모델 (data model)을 변경한 리팩터링 (refactor) 같은 것들 말입니다.

prism-mem은 매 커밋 (commit) 이후 세션 트랜스크립트 (session transcripts)와 git diff를 읽어 들여, (주어, 술어, 목적어) 트리플 (triples) 형태의 구조화된 지식 (structured knowledge)을 추출합니다. 추출된 지식은 그래프 (graph)로 연결되며, 오래된 사실이 새로운 사실과 모순될 때 이를 자동으로 감지하여 컨텍스트 파일 (context files)을 재생성합니다. 모든 단계에서 수동 입력은 필요하지 않습니다.

📖 아이디어의 기원

두 편의 연구 논문이 prism-mem의 작동 방식을 형성했습니다.

Memori (arXiv:2603.19935, 2026년 3월)는 메모리를 가공되지 않은 텍스트 덩어리 (raw text blobs) 대신 의미론적 트리플 (semantic triples)로 저장하면, 검색 (retrieval) 시 소비되는 토큰 (tokens)이 67% 감소하고 더 정확한 답변을 얻을 수 있음을 보여주었습니다. 핵심 통찰은 다음과 같습니다: 에이전트 (agent)가 컨텍스트 (context)를 필요로 할 때, 발생한 일에 대한 전체 트랜스크립트가 필요한 것이 아니라, 해당 트랜스크립트에서 추출된 사실을 압축된 구조화된 형태로 필요로 한다는 것입니다. 트리플이 바로 그 형태입니다.

A-MEM (arXiv:2502.12110, NeurIPS 2025)은 메모리가 단순히 축적되는 것이 아니라, 서로 진화하고 연결되어야 한다는 아이디어를 도입했습니다. 이는 제텔카스텐 (Zettelkasten) 방법론에서 유래했습니다. 즉, 모든 새로운 노트 (note)는 기존 노트와 연결되며, 새로운 정보가 기존 정보와 모순될 경우 오래된 노트는 업데이트됩니다. prism-mem의 그래프 엣지 (graph edge) 생성 및 노후화 감지 (staleness detection) 기능은 바로 이 아이디어에서 직접적으로 파생되었습니다. 예를 들어 snap-url uses PostgreSQL이라는 정보가 들어오면, 기존의 snap-url uses SQLite 트리플이 삭제되는 것이 아닙니다. 대신 노후된 (stale) 것으로 표시되고 두 정보 사이에 엣지가 연결되어, 결정이 변경된 이력을 보존합니다.

두 논문 모두 에이전트 간 호환이 가능하고, 오픈 소스이며, 설치 가능한 구현체는 없었습니다. prism-mem은 이를 구축하려는 시도입니다.

⚙️ 작동 방식

주요 진입점은 prism_mem/cli.py에 있는 prism crystallize 명령입니다. 이 명령은 네 가지 단계를 순차적으로 실행합니다. 각 단계는 독립적이며 개별적으로 테스트 가능합니다.

단계 1: 인제스트 (Ingest)

prism은 두 가지 소스, 즉 Claude Code 세션 트랜스크립트 (Session transcripts)와 git 히스토리 (git history)로부터 데이터를 읽어옵니다.

**세션 트랜스크립트 (Session transcripts)**는 prism_mem/ingestion/session_reader.pyread_latest_session()에 의해 처리됩니다. Claude Code는 모든 세션을 ~/.claude/projects/<encoded-path>/ 아래에 .jsonl 파일로 저장합니다. 파일의 각 줄은 사용자 메시지, 어시스턴트 응답, 도구 호출 결과 (tool call result), 또는 요약 (summary)과 같은 단일 이벤트를 나타내는 JSON 객체입니다. prism은 이 모든 것을 파싱하여 텍스트와 사고 블록 (thinking blocks)을 추출하며, <session-uuid>/subagents/*.jsonl에서 발견되는 서브에이전트 (subagent) 트랜스크립트도 병합합니다. 최종 출력물은 role, content_type, content, timestamp, session_id, source를 포함하는 청크 (chunks)의 평탄한 리스트 (flat list)입니다.

결정적으로, prism은 워터마크 기반의 증분 인제스트 (incremental ingestion)를 사용합니다. SQLite의 session_watermarks 테이블은 각 세션 파일에 대해 마지막으로 처리된 타임스탬프 (timestamp)를 저장합니다. 이후 실행 시, get_session_watermark()가 이 타임스탬프를 검색하고 read_latest_session()은 이전 타임스탬프를 가진 모든 청크를 건너뜁니다. 이는 이미 확인한 콘텐츠를 다시 처리하지 않음을 의미하며, 파이프라인 (pipeline)이 장기 실행되는 프로젝트에서도 빠른 속도를 유지할 수 있게 합니다.

**Git 히스토리 (Git history)**는 prism_mem/ingestion/git_reader.pyread_git_diff(), read_git_log(), 그리고 read_git_head()에 의해 처리됩니다. 이 함수들은 서브프로세스 (subprocesses)로서 git diff HEAD~1 HEAD, git log --oneline -20, 그리고 git rev-parse HEAD를 실행합니다. HEAD 커밋 해시 (commit hash)는 processed_commits 테이블과 대조됩니다. 만약 이미 존재한다면, git 단계는 완전히 건너뜁니다. 해시는 mark_commit_processed()를 통해 성공적인 처리 후에 기록됩니다.

단계 2: 추출 (Extract)

prism_mem/extraction/extractor.pyextract_triples()는 결합된 세션(session) 및 git 텍스트를 비정형 텍스트에서 지식 그래프 (knowledge graphs)를 추출하기 위해 특별히 구축된 라이브러리인 kg-gen에 전달합니다. kg-gen은 청킹 (chunking, 청크당 8,000자), LiteLLM을 통한 LLM 호출, 그리고 엔티티 클러스터링 (entity clustering)을 처리합니다. 출력값은 (주어, 술어, 목적어) 튜플 (tuples)의 리스트입니다.

실제 세션에서 추출된 트리플 (triple)은 다음과 같은 형태를 가집니다:

("snap-url", "uses", "PostgreSQL")
("snap-url API", "handles", "authentication via JWT")
("Redis", "stores", "rate limiting state")
...

kg-gen은 기본적으로 cluster=True 설정으로 실행됩니다. 클러스터링은 유사한 엔티티 이름을 그룹화하여 "PostgreSQL", "Postgres", "the database"가 모두 동일한 정준 엔티티 (canonical entity)로 해결되도록 합니다. 만약 LLM이 잘못된 형식의 엔티티를 반환하면 (이 경우 kg-gen에서 Pydantic 검증 오류가 발생함), extract_triples()는 예외를 포착하고 cluster=False 설정으로 동일한 청크를 재시도합니다. 폴백 (fallback) 경로에서도 트리플은 여전히 깔끔하게 추출되며, 다음 단계의 코사인 유사도 (cosine similarity) 연결 작업이 잔여 중복 문제를 처리합니다.

단계 3: 저장 및 연결 (Store and Link) 🔗

이 단계에서 지식 그래프가 실제로 구축됩니다. 추출된 각 트리플에 대해, prism_mem/linking/linker.pyingest_triple()은 특정 순서에 따라 세 가지 작업을 수행합니다.

단계 1 (저장): prism_mem/storage/db.pystore_triple()은 트리플을 triples 테이블에 삽입하고, sentence-transformers/all-MiniLM-L6-v2를 사용하여 384차원 벡터 임베딩 (vector embedding)을 계산하기 위해 embed()를 호출합니다. 이 모델은 완전히 로컬에서 실행되므로 API 호출이 필요하지 않으며, 정규화된 float32 벡터를 반환합니다. 임베딩은 struct pack 형식(384f)을 사용하여 embeddings 테이블에 바이너리 블롭 (binary blob)으로 저장됩니다. vec_from_bytes()는 쿼리 시점에 이를 다시 numpy 배열로 언팩 (unpack)합니다.

sqlite-vec 대신 numpy를 선택한 것은 의도적인 결정입니다. sqlite-vec는 enable_load_extension을 필요로 하는데, 이는 pyenv 설치본을 포함하여 macOS의 시스템 Python 등 많은 Python 빌드에서 비활성화되어 있습니다. prism의 규모(수천 개의 트리플)에서는 numpy의 O(n) 코사인 스캔 (cosine scan)이 약 50ms 정도 소요됩니다. 이 정도 규모에서는 sqlite-vec의 이식성 비용이 인덱스 속도로 얻는 이득보다 크지 않습니다.

2단계 (연결 (Link)): search_similar()는 데이터베이스의 모든 기존 임베딩 (embeddings)을 numpy 행렬로 로드하고, 단일 행렬 내적 (matrix dot product)을 통해 새로운 트리플의 임베딩에 대한 코사인 유사도 (cosine similarity)를 계산합니다. 임베딩이 사전에 정규화 (pre-normalized)되어 있기 때문에, 내적은 코사인 유사도와 동일합니다. 유사도 임계값 (threshold)을 넘는 쌍들은 create_edge()를 통해 edges 테이블에 엣지 (edges)로 기록되며, 유사도 점수는 엣지 가중치 (edge weight)로 저장됩니다.

3단계 (만료 감지 (Stale detection)): check_and_mark_stale()은 동일한 subjectpredicate를 공유하지만 object가 다른 트리플을 쿼리합니다. 이를 발견하면 mark_stale()을 호출하여 기존 트리플의 stale 불리언 (boolean) 값을 반전시킵니다. 이를 통해 prism은 snap-url uses PostgreSQL이 수집된 후, snap-url uses SQLite가 더 이상 최신 정보가 아님을 알게 됩니다.

여기서는 순서가 중요합니다. 연결 (Linking) 작업이 만료 표시 (staleness marking)보다 먼저 실행되므로, 기존 트리플이 여전히 활성 상태인 동안 엣지가 생성됩니다. 이는 그래프 구조를 보존합니다. 즉, 기존 SQLite 트리플과 새로운 PostgreSQL 트리플 사이의 연결이 마이그레이션의 증거로서 그래프에 계속 표시됩니다.

4단계: 생성 (Generate) 📄

prism_mem/constitution/generator.py에 있는 write_constitution()은 먼저 select_top_triples()를 호출합니다. 이는 모든 비만료 (non-stale) 트리플을 필터링하고 다음 식을 사용하여 각 트리플의 점수를 매깁니다:

score = 1 / (1 + age_seconds / 86400) + confidence

최신성 (recency) 구성 요소는 하루에 한 번씩 감소합니다. 신뢰도 (confidence) 구성 요소는 추출 과정에서 kg-gen에 의해 해당 트리플을 뒷받침하는 청크 (chunk)의 개수를 기반으로 설정됩니다. 점수가 높은 상위 30개의 트리플을 선택하여 (subject) [predicate] (object) 사실 형태의 불렛 리스트로 포맷팅한 뒤, _call_haiku()를 통해 LLM에 전달합니다.

LLM은 서로 다른 프롬프트와 함께 세 번 호출되어 세 개의 파일을 생성합니다:

  • CLAUDE.md: 프로젝트 설명, 기술 스택 (tech stack), 아키텍처 (architecture), 결정 사항 및 컨벤션 (conventions)을 포함하는 전체 구조화된 문서
  • .cursorrules: Cursor IDE를 위한 40줄 미만의 명령형 불렛 리스트
  • AGENTS.md: 리포지토리 레이아웃, 빌드 명령 및 행동 규칙을 포함한 에이전트 지향 가이드

세 파일 모두 프로젝트 루트 (root)에 작성됩니다. 이후의 모든 세션은 프로젝트의 실제 현재 상태에서 파생된 컨텍스트 (context) 파일을 읽어 들입니다.

📊 데모 결과: snap-url에서의 3개 세션

snap-url은 FastAPI와 Python으로 구축된 URL 단축 서비스입니다. 긴 URL을 POST하면 6자리의 짧은 코드를 반환받으며, 리다이렉트 (redirect) 경로는 먼저 Redis를 호출하여 밀리초 미만의 캐시 조회를 수행하고, 실패 시 PostgreSQL로 폴백 (fallback)합니다. 클릭 분석 (click analytics)은 리다이렉트에 지연 시간을 추가하지 않고 백그라운드 태스크 (background tasks)를 통해 비동기적으로 기록됩니다.

이 프로젝트는 세 번의 세션에 걸쳐 구축되었습니다:

  • 세션 1: 핵심 API. POST /shorten은 62개 기호 알파벳을 사용하여 짧은 코드를 생성하고, GET /{code}는 리다이렉트를 처리하며, 로컬 개발을 위해 SQLite를 기반으로 하는 SQLAlchemy 모델을 사용합니다.
  • 세션 2: 클릭 분석. Click 모델, record_click 백그라운드 태스크, 그리고 총 클릭 수, 최근 24시간 수치 및 클릭당 메타데이터를 반환하는 GET /stats/{code} 엔드포인트를 추가했습니다.
  • 세션 3: Redis 캐싱 레이어 및 PostgreSQL 마이그레이션 (migration). 0.5초 타임아웃과 우아한 DB 폴백 기능을 갖춘 cache.py를 추가한 후, 운영 환경에서 SQLite를 PostgreSQL로 교체했습니다.

마지막 세션은 정보의 노후화 (staleness) 이야기가 흥미로워지는 지점입니다.

세 세션 모두에 대해 prism을 실행한 후의 수치는 다음과 같습니다:

411,463 bytes의 원시 전사 데이터(raw transcripts)가 입력되어, 11,707자의 구조화된 지식(structured knowledge)으로 출력되었습니다. 이는 **35배의 압축률 (compression ratio)**입니다.

지표 (Metric)값 (Value)
원시 세션 전사 크기 (Raw session transcript size)411,463 bytes
...

35배 압축이 실제로 의미하는 것: 원시 세션 전사 데이터는 도구 호출 결과 (tool call outputs), 재시도된 명령 (retried commands), 구문 오류 (syntax errors), 중간 추론 과정 (intermediate reasoning), 그리고 대규모 파일 읽기 내용으로 가득 차 있습니다. prism은 이 모든 것을 버리고 오직 구조화된 사실(structured facts)만을 남깁니다: 프로젝트가 무엇을 사용하는지, 무엇이 결정되었는지, 그리고 무엇이 변경되었는지 말입니다. 11,707자의 출력물은 에이전트가 세션 시작 시 실제로 필요로 하는 정보입니다.

320개의 노후된 트리플 (stale triples)이 의미하는 것: 추출된 정보의 거의 절반이 결과적으로 더 새로운 정보에 의해 대체되었습니다. 이는 추출 성능이 낮다는 신호가 아닙니다. 오히려 그래프가 시간이 흐름에 따라 결정 사항들을 추적하고 있다는 증거입니다. 평면적인 CLAUDE.md 방식이었다면 700개의 사실을 모두 축적했을 것이며, 그중 어떤 것이 여전히 유효한지 구분할 방법이 없었을 것입니다. 노후화 메커니즘 (staleness mechanism)이야말로 살아있는 지식 그래프 (live knowledge graph)와 단순한 스냅샷 (snapshot)을 구분 짓는 요소입니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0