제품
단일 바이너리, markdown 파일, 파생 인덱스
훅이 에이전트 활동을 수집합니다. git의 Markdown은 페이지를 보관하고, SQLite는 검색 인덱스와 세션, 인계, 계정 등 데이터베이스에만 있는 기록을 보관합니다. 둘 다 백업하세요. 아래에서 수집부터 다음 브리핑까지의 흐름을 설명합니다.
바이너리 하나, 데이터 디렉터리 하나.
SQLite는 번들되어 있고, libgit2는 vendoring되어 있으며, 임베더는 순수 Rust입니다. 따로 돌려야 할 사이드카도, 데이터베이스 서버도, 큐도 없습니다. 아는 것은 전부 폴더 하나에 있습니다.
<data_dir>/
wiki/YAML 프런트매터가 붙은 markdown 페이지, git 저장소 안에 있습니다. 단일 진실 공급원입니다.db/memory.sqlite검색 인덱스와 데이터베이스에만 있는 세션, 관찰, 인계, 계정, 감사 기록. WAL 모드입니다.raw/관리형 ai-memory run 세션에서 나온, 변경 불가능하고 정제된 JSONL 세그먼트.models/로컬 임베딩 모델 all-MiniLM-L6-v2. 약 87 MB이고 SHA-256이 고정되어 있습니다.logs/일 단위로 롤링되는 로그 출력.config.toml시작할 때 한 번 읽습니다. 모든 값은 AI_MEMORY_*로 덮어쓸 수 있습니다.서버는 기본적으로 127.0.0.1:49374에 바인드합니다. 백업은 ai-memory backup을 실행하거나, 위키를 git push하고 폴더를 rsync하면 됩니다.
훅에서 다음 브리핑까지.
여덟 단계 중 일곱 단계는 모델도 API 키도 없이 실행됩니다. LLM 단계는 제공자를 설정하기 전까지 꺼져 있습니다.
훅
민감 정보 제거
단일 writer
관찰 기록
관찰 기록
세션 페이지
LLM (선택)
Git 위키
브리핑
Git 위키
훅
훅LLM 불필요
에이전트 CLI가 라이프사이클 훅을 실행합니다. 200 ms 예산 안에서 fire and forget으로 동작합니다. 네이티브 훅은 이벤트를 로컬에 스풀하고, 분리된 헬퍼가 이를 전달합니다. 서버는 202로 응답하고, 포화 상태에서는 429를 돌려줍니다.
민감 정보 제거LLM 불필요
/hook 라우터가 시크릿을 제거하고 크기를 제한합니다. 신뢰할 수 없는 텍스트가 스토어로 들어가는 경로는 여기뿐입니다.
단일 라이터LLM 불필요
정제된 이벤트는 큐 하나에 들어가고, 유일한 쓰기 커넥션을 가진 스레드 하나가 큐를 비웁니다.
관찰 기록LLM 불필요
이벤트는 운영용 감사 추적으로 SQLite에 쌓입니다. 세션을 제한된 범위로 투영한 것이며 전체 트랜스크립트가 되는 일은 없습니다.
세션 종료LLM 불필요
모델 없이 규칙만으로 관찰 기록을 sessions/<id>.md로 만들고, 다음 에이전트를 위한 Handoff 행을 엽니다. 모두 트랜잭션 하나에서 처리됩니다.
통합LLM 선택
제공자를 설정하면 LLM이 요약을 다시 쓰거나 concepts/, decisions/, gotchas/, procedures/로 펼칩니다. 재시도 가능한 큐에서 실행되므로 훅 지연 시간에 영향을 주지 않습니다.
커밋과 인덱싱LLM 불필요
페이지 쓰기는 매번 원자적(tmp, rename, fsync)이고, git에 커밋되며, 해당 행과 같은 SQLite 트랜잭션 안에서 인덱싱됩니다.
쿼리와 브리핑LLM 불필요
memory_query가 인덱스를 검색합니다. 다음 SessionStart에서 훅이 그 디렉터리에 열려 있는 인수인계와 구조화된 브리핑을 가져옵니다.
두 개의 계층, 하나의 원본.
파일과 인덱스가 서로 다르면 파일이 이깁니다.
Writer
git 안의 markdown
원본
SQLite 인덱스
파생
git 안의 markdown
파일 감시
재인덱싱
SQLite 인덱스
파일이 원본입니다
페이지는 wiki/
/ / 아래에 있는, YAML 프런트매터가 붙은 markdown입니다. Obsidian으로 열고, grep하고, 리모트로 push하세요. 페이지 인덱스는 파생 데이터
Reindex는 Markdown에서 페이지, 링크, 전문 검색을 재구축합니다. 데이터베이스에만 있는 세션, 관찰, 인계, 사용자, 키, 감사 기록은 복구하지 못합니다.
쓰기는 서버가 관리합니다
일반적인 쓰기는 위키 계층을 거치며, 이 계층이 파일, git 히스토리, 인덱스를 함께 갱신합니다.
나머지는 감시기가 잡습니다
vim이나 Obsidian에서 한 편집은 파일 감시기가 감지합니다. 놓친 이벤트는 30초마다 도는 전체 diff가 잡아냅니다.
검색: 다섯 개의 스트림, 하나의 순위.
쿼리
FTS5
엔티티
그래프
관찰 기록
벡터
RRF k=60
권위도
결과
전문 검색
페이지 제목과 본문에 대한 SQLite FTS5. 단순 쿼리에서는 불용어를 걸러 냅니다.
엔티티 매칭
프런트매터의 entities와 tags에서 뽑은 이름의 어휘 인덱스. 역페이지 빈도로 가중치를 줍니다.
그래프 이웃
링크 테이블에서 한 홉: 위키링크, markdown 링크, 타입 엣지, 프로젝트 간 링크. 그래프 데이터베이스 없이 일반 SQL입니다.
관찰 기록
프로젝트의 원본 관찰 기록에 대한 전문 검색. 일치하는 세션마다 가장 잘 맞는 관찰 기록 하나로 자기 세션 페이지에 한 번씩 투표하므로, 페이지에 담기지 못한 세부 내용으로도 그 세션을 찾을 수 있습니다. 3.0부터 기본으로 켜져 있습니다.
벡터, 선택 사항
프로세스 내 로컬 모델이 만든 임베딩에 대한 코사인 유사도. 2.0부터 기본으로 켜져 있지만 필수는 아니며, 의도적으로 브루트 포스입니다.
융합 이후
- k=60의 Reciprocal Rank Fusion이 스트림을 순위 기준으로 합치므로, 어떤 스트림도 점수 보정이 필요 없습니다.
- 그다음 상한이 있는 권위도 배수가 근소한 경합을 관리되는 규칙, 결정, 절차, 주의점 쪽으로 살짝 기울입니다. 에피소드성 페이지와 과거 기록 페이지도 계속 검색되며, 아예 제외되는 것은 없습니다.
- 선택적 LLM 리랭크: 쿼리당 호출 한 번, 제목과 스니펫 최대 30개가 대상입니다. 실패하면 로컬 순서를 그대로 씁니다. 로컬 리랭커는 아직 없습니다.
- 컴파일된 페이지에서 아무것도 찾지 못하면 원본 관찰 기록에 대한 제한된 검색이 raw_hits를 돌려줍니다.
- 필터는 랭킹 전에 적용됩니다.
kind,tag,tier와 경로 접두사가 모든 스트림을 좁히기 때문에, 드문 페이지가 흔한 페이지에 밀려나지 않습니다. - 선택 신호 두 가지가 있으며 기본값은 0입니다. 하나는 '오래됨'이나 '틀림' 피드백이 열려 있는 페이지의 권위를 낮추고, 다른 하나는 같은 세션이 비슷한 질의에서 읽었거나 도움이 됐다고 표시한 페이지를 끌어올립니다. 페이지를 다시 쓰면 둘 다 사라집니다.
- explain=true를 넘기면 결과마다 스트림별 순위, RRF 기여도, 배수를 볼 수 있습니다.
일본어, 한국어, 중국어
- 단어 구분자가 없는 한자, 가나, 한글 문자열에는 문자 단위 매칭이 필요합니다. 한 글자와 두 글자 연속으로 구성된 보조 인덱스를 페이지와 같은 트랜잭션에서 기록합니다.
- 모델이 아니라 SQLite 인덱스입니다. 일본어나 한국어로 검색해도 API 호출과 임베딩은 여전히 발생하지 않습니다.
- 로마자 텍스트는 기존 경로를 그대로 지납니다. 예전 버전에서 올라온 저장소는 트래픽을 받기 전에 새 인덱스를 채웁니다.
시간: as_of
- ISO 날짜를 넘기면 그 시점에 위키가 무엇이라고 했는지 물을 수 있습니다.
- 기록하는 것은 수집 시점뿐입니다. ai-memory가 사실을 알게 된 때와 그것을 대체한 때를 기록하며, 현실에서 그 사실이 참이었던 때는 기록하지 않습니다.
- 그 날짜에 검색했다면 나왔을 순위를 재현하지는 않습니다.
타입 엣지
- 프런트매터의
relations:는 닫힌 집합만 받습니다:causes,fixes,contradicts. 오타로 새 종류가 생길 수 없습니다. contradicts는 LLM 없이 린트에 반영되고, 누군가 정리할 때까지 계속 보고됩니다.- 검색에서는 explain 전용입니다. 일반 링크로 그래프에 들어갈 뿐 순위를 움직이지 않습니다. 벤치마크에서 가중치를 줄 근거가 나오지 않았기 때문입니다.
memory_read_page는 이 엣지를 따라가 관련 페이지를 나열할 수 있습니다.
수령도 한 번, 라이터도 하나.
정확성의 대부분은 두 가지 규칙이 떠받칩니다. 인수인계는 한 번만 수령할 수 있고, SQLite에는 스레드 하나만 씁니다.
인수인계는 프로토콜입니다
- 인수인계는 타입이 있는 레코드입니다: 보내는 에이전트와 받는 에이전트, 프로젝트, cwd, 요약, 미해결 질문, 수정한 파일, 다음 단계.
- 수락은 원자적 compare and set입니다. 두 번째로 요청한 에이전트는 아무것도 받지 못합니다.
- cwd는 경로 경계를 기준으로 일치를 봅니다.
/repo는/repo/api를 포함하지만/repo-other는 절대 포함하지 않습니다. - 수동 인수인계가 자동 인수인계보다 우선합니다. 수락하면 같은 트랜잭션 안에서 더 오래된 자동 후보가 만료됩니다.
- 공유 서버에서 인수인계는
shared=true로 보내지 않는 한 소유자의 것입니다.
단일 라이터 규칙, 측정 결과
모든 쓰기는 크기 1024의 제한된 큐 하나를 거쳐 OS 스레드 하나로 갑니다. 읽기는 별도의 읽기 전용 풀을 씁니다. 부하가 몰리면 생산자가 느려질 뿐 쓰기는 하나도 버려지지 않습니다.
- 라이터 1개42/s23.9 ms
- 라이터 8개295/s3.4 ms
- 라이터 32개698/s1.43 ms
- 라이터 128개700/s1.43 ms
- 상한은 초당 약 700건이며 라이터 32개부터는 늘지 않습니다. 라이터가 하나일 때 병목은 fsync이고 CPU는 여유가 있습니다.
- 빠른 로컬 디스크에서 측정했습니다. 네트워크 볼륨이나 느린 볼륨에서는 눈에 띄게 낮아집니다.
- 테스트는 스토어를 직접 구동하며 HTTP 진입점은 거치지 않습니다.
cargo test -p ai-memory-store --test writer_throughput -- --ignored --nocapture로 재현할 수 있습니다.
오래된 세션 페이지에는 점수가 매겨지고, 콜드 페이지는 퇴출, 압축 또는 병합됩니다. 기억이 나이 드는 방식.
crate별로 본 코드.
제품 크레이트 10개가 각각 하나의 역할과 타입이 있는 API를 가지며, 순환 의존성은 없습니다.
| Crate | 역할 |
|---|---|
ai-memory-core | 도메인 타입, 에러, id. IO 없음. |
ai-memory-store | SQLite, 라이터 액터, 리더 풀, 감쇠 계산. |
ai-memory-wiki | 원자적 markdown 쓰기, 파일 감시기, git. |
ai-memory-mcp | MCP 전송과 도구 라우터. |
ai-memory-web | 브라우저 화면과 읽기 전용 JSON API. 계정 관리와 승인 작업은 인증된 경로를 호출합니다. |
ai-memory-hooks | 페이로드 스키마, 새니타이저, /hook 수신부. |
ai-memory-llm | 제공자 인증 경계, LLM과 임베더 trait. |
ai-memory-consolidate | 수집, 린트, sweep, auto-improve 파이프라인. |
ai-memory-workstream | 읽기 전용 네이티브 트랜스크립트와 실행 어댑터. |
ai-memory-cli | ai-memory 바이너리와 얇은 HTTP 서브커맨드. |
MCP 도구 25개, 의도적으로 좁게
일상적인 캡처는 훅이 하므로, 에이전트가 이 도구들을 직접 호출할 일은 드뭅니다.
회상 (7)
memory_querymemory_recentmemory_read_pagememory_read_session_observationsmemory_briefingmemory_explorememory_status
인수인계 (4)
memory_handoff_beginmemory_handoff_listmemory_handoff_acceptmemory_handoff_cancel
프로젝트 간 메시지 (4)
memory_message_sendmemory_message_listmemory_message_popmemory_message_cancel
쓰기와 유지 관리 (10)
memory_write_pagememory_patch_pagememory_delete_pagememory_restore_pagememory_consolidatememory_auto_improvememory_feedbackmemory_lintmemory_forget_sweepmemory_install_self_routing
자주 묻는 질문
ai-memory는 데이터를 어디에 보관하나요?
데이터 디렉터리 하나에 보관합니다. wiki/ 아래에 markdown 페이지의 git 저장소, db/ 아래에 파생 SQLite 인덱스, raw/ 아래에 정제된 워크스트림 세그먼트, models/ 아래에 로컬 임베딩 모델, 그리고 로그가 있습니다.
ai-memory에 LLM이 필요한가요?
아니요. 캡처, 세션 요약, 인수인계, 인덱싱, 검색, 브리핑은 제공자를 설정하지 않아도 동작합니다. LLM 통합, 자동 개선, 리랭크는 옵트인입니다.
SQLite 인덱스가 사라지거나 깨지면 어떻게 되나요?
Reindex는 Markdown에서 페이지, 링크, 전문 검색을 재구축합니다. 데이터베이스에만 있는 세션, 관찰, 인계, 사용자, 키, 감사 기록은 복구하지 못합니다.
검색 결과의 순위는 어떻게 매기나요?
FTS5 전문 검색, 엔티티 일치, 그래프 이웃, 관찰 기록, 선택 사항인 벡터의 다섯 후보 스트림을 합칩니다. k=60인 Reciprocal Rank Fusion으로 결합한 뒤 범위가 제한된 출처 권위도 조정을 적용합니다. LLM 리랭킹은 선택 사항입니다.
초당 쓰기를 몇 건까지 처리하나요?
스토어 측정값은 라이터 하나일 때 초당 42건, 8개일 때 295건이고, 32개부터는 700건 부근이 상한입니다. 빠른 로컬 디스크에서 나온 수치이며, 테스트는 HTTP 진입점 없이 스토어를 직접 구동합니다.
