AI 에이전트 메모리로 쓰는 옵시디언 인덱스가 12주 만에 4배가 됐습니다 — 크기 상한 없는 덧붙임
AI 에이전트의 장기 메모리로 쓰는 옵시디언(Obsidian) vault에서, 7월 초에 28KB까지 줄여 둔 루트 인덱스를 9월 말에 다시 재 보니 108KB였습니다. 12주 만에 약 4배입니다. 이 인덱스는 AI가 무언가를 찾을 때마다 가장 먼저 펼치는 파일입니다.
결론부터 적자면, 원인은 "지우지 말고 덧붙인다"는 쓰기 규칙에 크기 상한이 없었던 데 있었습니다. 그래서 층마다 크기 상한을 걸고, 지금 상태와 지나간 이력을 서로 다른 곳에 두고, 쓰는 순간과 세션을 여는 순간에 크기를 재도록 바꿨습니다. 이 글은 "AI가 읽는 옵시디언 정리기" 세 편 가운데 첫 편으로, 왜 다시 불어났는지와 그래서 들인 설계를 적은 기록입니다.
에이전트 메모리에 필요한 내용만 남아 다음 세션으로 잘 이어지는지는 에이전트 메모리와 하네스를 점검하던 글에서도 품었던 고민입니다. 이번에는 같은 고민을 옵시디언 쪽에서, 숫자로 마주했습니다.
AI 에이전트가 옵시디언 vault를 메모리 삼아 읽고 쓰는 방식
저는 Claude Code로 여러 레포를 오가며 일합니다. 개인 블로그 모노레포 말고도 성격이 다른 프로젝트가 여럿 있습니다. 숫자로 집계할 기록은 Neon DB에 미러로 따로 쌓기도 하지만, 레포를 넘나드는 맥락과 결정은 옵시디언 vault 하나에 모아 둡니다. vault는 PARA 구조(1-Projects, 2-Areas, 3-Resources, 4-Archives)로 나뉜 마크다운 노트 약 2,000개이고, 자체 git 레포로 버전을 관리합니다.
vault를 읽고 쓰는 일은 Claude Code 서브에이전트인 obsidian-curator가 맡습니다. IN(조회) 모드에서는 루트 인덱스 _AI-INDEX.md에서 출발해 폴더 인덱스 _INDEX.md를 거쳐 핵심 노트 한두 개만 읽습니다. OUT(기록) 모드에서는 작업 결과를 노트에 쓰고, 인덱스와 현황 보드를 갱신한 뒤, vault 레포에 커밋하고 push합니다.
_AI-INDEX.md 루트 인덱스 (라우팅 표)
└─ <폴더>/_INDEX.md 폴더 인덱스
└─ 핵심 노트 1~2개
루트 인덱스는 도메인 하나를 행 하나로 적은 라우팅 표이고, 칸은 키워드와 폴더 경로와 진입 노트 셋입니다. 이번 정리 전까지는 조회할 때마다 이 파일을 통째로 읽었습니다. 그러니 이 파일의 크기는 AI 에이전트 메모리를 조회할 때마다 컨텍스트에 그대로 얹히는 비용이었습니다.
이 밖에 두 가지가 더 있습니다. 전 프로젝트의 상태와 다음 할 일, 무엇을 기다리는지를 적은 현황 보드 _STATUS.md는 세션을 시작할 때마다 Claude Code의 SessionStart 훅(hook)이 요약해 대화 맨 앞에 넣어 줍니다. 그리고 리서치 산출물이나 UI 평가 리포트처럼 vault 경로에 직접 쓰는 스킬과 에이전트가 20개쯤 있습니다. 쓰는 쪽이 여럿이라는 이 사실은 뒤에서 원인 하나가 됩니다.
12주 만에 다시 불어난 옵시디언 인덱스와 로그
2026년 7월 2일에 한 번 정리했었습니다. 그때 루트 인덱스는 frontmatter에 last_out_* 같은 기록 키가 207개 쌓여 799KB였고, 정리하고 나니 28KB가 됐습니다. 9월 25일에 같은 파일들을 다시 잰 결과는 이랬습니다. 크기는 1KB를 1,000바이트로 셌습니다.
루트 인덱스는 108KB였습니다. 행 159개 중 55개가 행 상한 300B를 넘었고, 가장 긴 행 하나가 25.5KB였습니다.
가장 큰 폴더 인덱스는 436KB로, 한 번에 읽을 수 없는 크기였습니다. 20KB를 넘는 폴더 인덱스도 11개였습니다.
OUT 기록 로그
_OUT-LOG.md는 7월 2일의 560KB에서 1.7MB가 됐습니다.현황 보드는 107KB였습니다. 80자 상한을 넘는 칸이 37개, 200자 상한을 넘는 대기 항목이 30개였습니다.
루트 인덱스에서 금지했던
last_out_*키가 폴더 인덱스들에서 98개 다시 생겨 있었습니다.
OUT 로그는 1.7MB가 되는 동안 정작 읽는 곳이 없었습니다. 조회할 때 이 파일을 읽지 말라는 규칙까지 있었으니, 쓰기만 하고 아무도 읽지 않는 파일이었던 셈입니다. 마지막 항목은 7월에 고쳤다고 여긴 문제가 한 층 아래에서 그대로 되살아났다는 뜻이었습니다.
기록할 때마다 조금씩 줄인다는 처방이 빗나간 이유
이 인덱스 구조는 지식베이스를 통째로 먹이지 않으려고 1hop 인덱스를 세웠던 글에서 처음 설계했습니다. 그 글에서는 단일 진입점 인덱스와 1hop 규칙이 조회 비용을 지식베이스 크기와 무관한 상수로 만든다고 적었는데, 그 상수 안에 들어가는 파일이 바로 루트 인덱스와 폴더 인덱스였습니다. 그때 정한 쓰기 규칙은 새 노트를 만들거나 문서 끝에 덧붙이는 것은 자유롭게 허용하고, 기존 본문을 바꾸거나 지우는 쓰기만 사람이 확인한다는 것이었습니다. 자동화가 사람이 쓴 글을 소리 없이 덮어쓰지 않게 하려는 안전장치였지만, 덧붙이는 양에는 상한이 없었습니다.
그 뒤 5월 29일에 옵시디언 vault를 감사하며 내린 결론은 이랬습니다.
새 패러다임은 필요 없다. 기록할 때마다 조금씩 줄이는 자가치유(amortized self-healing)와 점검 스킬 하나면 충분하다.
정리 비용을 한 번에 몰아서 치르지 않고 기록할 때마다 조금씩 나눠 치르면, 전체 크기는 일정한 선에서 유지된다는 발상입니다. 이 처방대로라면 7월에 28KB로 줄인 인덱스는 그 언저리에 머물렀어야 합니다. 실제로는 4배가 됐고, 상수라고 믿었던 조회 비용도 함께 불어났습니다. 따져 보니 처방의 방향이 틀렸다기보다, 처방이 닿지 않는 곳이 너무 많았습니다.
키워드 칸만 상한 밖에 있었습니다
루트 인덱스에는 "행 하나가 300B를 넘으면 안 된다"는 규칙과 "키워드 칸은 절대 축약하지 않는다"는 규칙이 함께 있었습니다. 그리고 기록할 때마다 그 세션의 키워드를 키워드 칸에 덧붙였습니다. 도메인 이름에서 그치지 않고 PR 번호, 이슈 번호, 커밋 해시, 마이그레이션 번호까지 들어갔습니다.
행이 300B를 넘으면 줄여야 하는데 키워드 칸은 건드릴 수 없으니, 줄어드는 쪽은 늘 서술 칸이었습니다. 서술 칸이 줄어드는 동안 키워드 칸은 끝없이 자랐고, 가장 긴 행은 25.5KB가 됐습니다. 두 규칙이 부딪히면 "절대"가 붙은 쪽이 이기고, 상한은 소리 없이 집니다.
다이어트가 아니라 이사였습니다
줄이는 방법에도 문제가 있었습니다. 행이 길어지면 긴 서술을 폴더 인덱스의 "라우팅 상세" 절로 덧붙여 옮겼습니다. 지우지 않는 비파괴 방식이라 안전해 보였지만, 덩어리를 한 층 아래로 내려보냈을 뿐이었습니다. 폴더 인덱스가 새 적재지가 됐고, 세션마다 "~후속" 절이 쌓이면서 가장 큰 폴더 인덱스가 436KB까지 불었습니다.
한쪽을 누르면 다른 쪽이 부푸는 것을 흔히 풍선 효과라고 부릅니다. 서술 칸을 누르는 동안 옆 칸인 키워드 칸이 부풀었고, 한 층 아래인 폴더 인덱스가 부풀었습니다. 줄이는 쪽만 보고 있었으니, 부푸는 쪽은 눈에 들어오지 않았습니다.
규칙이 한 층, 한 주체에만 걸려 있었습니다
last_out_* 키를 만들지 말라는 금지는 루트 인덱스에만 적혀 있었습니다. 7월에 207개를 걷어 낸 바로 그 패턴이 폴더 인덱스들에서 98개 다시 생긴 까닭입니다. 규칙은 그것이 적힌 층에서만 지켜졌습니다.
쓰는 주체 쪽도 마찬가지였습니다. 쓰기 규칙은 curator 에이전트의 정의 안에만 있었고, vault에 직접 쓰는 다른 스킬과 에이전트 20개쯤은 그 규칙을 몰랐습니다. 규칙을 지켜야 할 쪽은 여럿인데, 규칙을 아는 쪽은 하나뿐이었습니다.
상한을 넘어도 울리는 신호가 없었습니다
점검 스킬은 사람이 불러야만 돌았습니다. 크기가 상한을 넘어도 그 사실을 알려 주는 신호가 어디에도 없었으니, 부를 계기도 없었습니다. 5월에 만들어 둔 정리 백로그 약 190건은 4개월째 그대로였습니다.
옵시디언 인덱스가 다시 불어나지 않게 들인 설계
원인을 한 줄로 줄이면 이렇습니다. "지우지 말고 덧붙인다"는 안전 원칙으로는 맞았습니다. 다만 크기 상한 없이 그 원칙만 지키면, 모든 파일이 로그가 됩니다. 그래서 덧붙이는 원칙은 그대로 두고, 덧붙여도 되는 곳과 안 되는 곳을 나눈 뒤 모든 곳에 상한을 거는 쪽으로 고쳤습니다.
모든 층, 모든 칸에 크기 상한을 겁니다
먼저 층마다 크기 상한을 정했습니다. 핵심은 예외를 두지 않는 것입니다. 루트 인덱스의 행 상한 300B는 이제 키워드 칸까지 포함해서 셉니다.
| 대상 | 상한 |
|---|---|
루트 인덱스 _AI-INDEX.md | 전체 40KB, 행 300B (키워드 칸 포함) |
폴더 인덱스 _INDEX.md | 20KB |
| 노트 | 50KB 권장 |
현황 보드 _STATUS.md | 칸 80자, 대기 항목 200자 |
과거 기록 _이력-<폴더>.md, 4-Archives/ | 상한 없음 (대신 통째로 읽지 않음) |
과거 기록 파일에는 상한을 두지 않았습니다. 대신 통째로 읽지 않는다는 규칙을 붙여서, 목차를 grep한 뒤 필요한 절만 읽습니다. 크기를 묶는 대신 읽는 방식을 묶은 셈입니다.
상태는 치환하고, 이력은 덧붙입니다
인덱스와 보드는 "지금 상태"만 담고, 바뀌면 통째로 치환합니다. 어떻게 여기까지 왔는지는 도메인 노트 끝에 덧붙이거나 커밋 메시지 본문에 씁니다. 인덱스에는 "~후속"이나 "~이관" 같은 절을 만들지 않습니다. 코드에서 파일은 지금 상태를 담고, 거기까지 온 과정은 커밋 이력이 담는 것과 같은 구분입니다.
OUT 로그도 이 구분에 따라 없앴습니다. 기록 이력의 정본은 커밋 메시지 본문, 곧 제목과 3~5줄 요약으로 옮겼고, 찾을 때는 git log --grep을 씁니다. 1.7MB 로그는 원문 그대로 보관 폴더로 옮겼습니다.
git log --grep="찾는 말" -i --date=short --format='%ad %s%n%b'
키워드 칸에는 요청에서 실제로 나올 말만 둡니다
키워드 칸의 기준은 사용자가 요청에서 실제로 말할 법한가입니다. 그래서 도메인과 제품의 이름, 그리고 핵심 개념처럼 쉽게 변하지 않는 말만 둡니다. 번호와 날짜, 금액, 세션 서술은 넣지 않습니다.
전 | 도메인·제품 이름, 핵심 개념, PR 번호, 이슈 번호, 커밋 해시, 마이그레이션 번호, … | 1-Projects/<폴더> | <진입 노트> |
후 | 도메인·제품 이름, 핵심 개념 | 1-Projects/<폴더> | <진입 노트> |
조회도 이제는 루트 인덱스를 통째로 읽지 않고, 요청에서 나온 키워드를 2~3개 변형으로 grep해 해당 행만 읽으며, 매칭이 없거나 애매할 때만 전체를 읽습니다. 번호 같은 값은 키워드 칸에서 빼도 찾는 데 지장이 없습니다. 노트 본문에 이미 적혀 있으니, 인덱스로 폴더까지 간 다음 그 폴더 안에서 grep하면 됩니다.
그래서 폴더를 한정한 grep은 허용하고, vault 전체를 grep하는 것은 금지했습니다. 인덱스는 폴더까지 가는 길만 알면 되고, 폴더 안의 세부까지 외우고 있을 필요는 없었습니다.
점검은 쓰는 순간과 세션을 여는 순간에 저절로 일어납니다
사람이 불러야 도는 점검 대신, 두 곳에서 저절로 돌게 했습니다. 하나는 쓰는 순간입니다. 기록을 맡은 curator는 커밋하기 직전에, 이번에 만진 파일의 크기와 행 길이를 wc -c와 awk로 잽니다. 판단이 아니라 측정이라 누가 돌려도 같은 답이 나옵니다. 넘으면 그 기록 안에서 줄인 뒤에 커밋합니다.
V="/path/to/vault"
chk() { s=$(wc -c < "$V/$1"); [ "$s" -gt "$2" ] && echo "OVER $1 ${s}B > $2B"; }
chk _AI-INDEX.md 40960 # 루트 인덱스 40KB. 검사는 1KB를 1,024바이트로 센다
# 이번에 만진 폴더 인덱스는 20480, 노트는 51200 과 같은 식으로 비교한다
# 300B를 넘는 표 행. LC_ALL=C 로 글자 수가 아니라 바이트를 세게 한다
LC_ALL=C awk '/^\|/ && length($0) > 300 { print "ROW L" NR " " length($0) "B" }' "$V/_AI-INDEX.md"
다른 하나는 세션을 여는 순간입니다. 세션 시작 훅이 인덱스 크기를 재서, 상한을 넘었을 때만 브리핑 끝에 "vault 크기 상한 초과 — /vault-health 권장" 한 줄을 붙입니다. 넘지 않으면 아무 말도 하지 않습니다. 이 점검을 넣은 뒤에도 세션 시작 훅 전체가 0.12초 안에 끝났습니다.
규약은 한 곳에 두고, 나머지는 가리키기만 합니다
쓰는 쪽이 여럿이라는 문제는 규약의 자리를 옮겨서 풀었습니다. 옵시디언 vault 루트에 쓰기 규약 파일 _AI-WRITING-GUIDE.md를 두고, 모든 세션이 읽는 글로벌 지침(CLAUDE.md)에는 "vault에 쓰는 모든 주체는 이 규약을 따른다"는 포인터 한 줄만 걸었습니다.
규칙은 쓰는 쪽 모두에게 닿아야 하지만, 같은 규칙을 곳곳에 복사해 두면 한쪽이 바뀔 때 다른 쪽이 조용히 낡습니다. 라우팅 매트릭스를 정본 하나로 정리할 때 내린 결론과 같아서, 이번에도 정본은 한 곳에 두고 나머지는 가리키기만 하는 쪽을 택했습니다.
지우지 않고 줄이려면, 옮긴 뒤 대조합니다
줄인 내용은 지우지 않았습니다. 인덱스에서 걷어 낸 서술과 이력은 이력 파일과 보관 폴더로 원문 그대로 옮겼습니다. 옮긴 뒤에는 원본의 비공백 줄이 새 파일들 어딘가에 모두 있는지를 스크립트로 대조했고, 인덱스와 보드 16개가 모두 통과했습니다.
일을 나누는 기준도 정했습니다. 키워드를 고르거나 요약을 쓰는 것처럼 판단이 필요한 다시 쓰기는 서브에이전트에 맡기고, 옮기기와 크기 점검과 대조는 스크립트로 했습니다. 판단은 모델에게, 측정은 스크립트에게 둔 것입니다. 그리고 에이전트가 한 작업도 메인 세션이 스크립트로 다시 검증한 뒤에만 커밋했습니다.
108KB에서 39.7KB로 줄인 결과와 아직 남은 한계
옵시디언 vault 정리 전후를 나란히 놓으면 이렇습니다. 루트 인덱스의 행 수는 159개를 그대로 둔 채 줄였습니다.
| 대상 | 전 | 후 |
|---|---|---|
| 루트 인덱스 크기 | 108KB | 39.7KB |
| 300B를 넘는 행 | 55개 | 2개 |
| 가장 긴 행 | 25.5KB | 342B |
| 가장 큰 폴더 인덱스 | 436KB | 18.6KB |
| 20KB를 넘는 폴더 인덱스 | 11개 | 0개 |
| 현황 보드 | 107KB | 58KB (상한 넘는 칸 0개) |
| OUT 로그 | 1.7MB | 폐지 (원문은 보관) |
폴더 인덱스의 last_out_* 키 | 98개 | 0개 |
숫자만 보면 깔끔하지만 솔직히 적어 둘 한계가 있습니다. 먼저, 루트 인덱스에서 링크를 두 번 이하로 따라가 닿는 노트의 비율이 44.2%에서 39.8%로 조금 내려갔습니다. 폴더 인덱스가 직접 나열하던 노트 목록을 이력 파일로 옮겼기 때문입니다.
다만 그 목록은 436KB짜리 파일 안에 있어서, 원래도 한 번에 읽히지 않던 목록이었습니다. 대신 인덱스마다 "진입 추천" 5~14개를 새로 썼습니다.
루트 인덱스의 여유도 넉넉하지 않습니다. 상한 검사는 1KB를 1,024바이트로 세는데, 그 기준으로 남은 여유가 1.3KB입니다. 1,000바이트로 세면 여유는 거의 없습니다. 도메인이 다섯 개쯤 늘면 같은 폴더를 가리키는 중복 행을 합쳐야 합니다. 300B를 넘는 행도 아직 두 개 남아 있습니다.
그리고 이 설계가 다시 무너지지 않는지는 몇 달 지나 봐야 압니다. 세션 시작 경고가 그 관측 장치입니다. 적어도 인덱스가 상한을 넘으면 다음 세션을 여는 순간 한 줄이 뜨니, 이번처럼 12주 동안 모르고 지나가지는 않을 것입니다.
지우지 않는 것과 줄이지 않는 것은 다른 말이었습니다
구조는 맞고 규율이 없다는 5월의 진단은 맞았습니다. 틀린 것은 자가치유의 범위였습니다. 서술 칸 하나만 줄였더니 비대화가 다른 칸, 다른 층으로 옮겨 갔습니다.
그래서 상한은 모든 층, 모든 칸에 걸어야 하고, 점검은 사람이 부르는 것이 아니라 쓰는 순간과 세션을 여는 순간 두 곳에서 저절로 일어나야 한다는 결론에 이르렀습니다. 방향은 5월과 같은 "기록할 때마다 조금씩"이지만, 이제는 그 "조금씩"을 판단이 아니라 측정이 정합니다. 사람이 불러야 도는 점검은 부르는 일을 잊는 순간 없는 것과 같았습니다. 옵시디언이 아닌 다른 저장소를 AI 에이전트 메모리로 쓰더라도, 에이전트가 스스로 덧붙이게 두는 한 같은 결론일 것 같습니다.
"지우지 않는다"와 "줄이지 않는다"도 다른 말이었습니다. 옮기고 대조하면 비파괴를 지키면서도 줄일 수 있습니다. 예전 방식이 틀린 것은 옮겼다는 데 있지 않았습니다. 옮긴 곳이 또 하나의 인덱스였고, 거기에도 상한이 없었다는 데 있었습니다.
돌이켜 생각해보면 이번에는 규칙의 내용만큼이나 규칙이 걸린 자리가 문제였습니다. 금지는 한 층에만, 규약은 한 주체에만 걸려 있었습니다. 말투 규칙을 어느 층에 놓을지 따졌던 기록과 결이 같다는 생각이 들었습니다. 무엇을 적을지 못지않게, 어디에 걸지를 물었어야 했습니다.
정리하는 동안에는 크기와 개수를 재는 도구도 여러 번 틀렸습니다. 고아 노트와 깨진 링크를 부풀려 센 일, 검증이 오류를 잡지 못한 일은 2편에서 이어 쓰겠습니다. 그리고 정리하다 보니 전제 하나가 바뀌었습니다. 이 vault의 주 독자가 사람이 아니라 AI였다는 것인데, 그 전제에서 무엇이 달라져야 했는지는 3편에서 적겠습니다.
관련 글
LLM 에이전트에 지식베이스를 통째로 먹이지 않는 법 — 1hop 인덱스 오버레이 패턴
지식베이스를 LLM 에이전트에 통째로 먹이면 컨텍스트 비용이 입력 규모에 비례해 폭발합니다. RAG를 끌어오기 전에 얇은 규칙과 단일 진입점 인덱스, 1hop 매핑만으로 도메인 메모리 주입 비용을 상수로 만든 구축기입니다.
AI 코딩 에이전트의 메모리도 결국 부채가 됩니다 — 감사와 정리의 기록
AI 코딩 에이전트에 규칙과 메모리를 부지런히 쌓았지만, 어느 순간부터 그 기억이 되려 발목을 잡기 시작했습니다. 가장 위험했던 '폐기된 정책의 화석'부터 메모리 부채의 유형을 정리하고, 적대적 검증·3진 판정·근거 부담 차등으로 감사해 걷어낸 과정을 돌아봅니다.
AI 에이전트 메모리에 DB를 붙이게 된 이유 — Neon serverless Postgres 첫 파일럿
Claude Code 환경의 누적 메모리를 SQL로 다루기 위해 Neon serverless Postgres에 dual-write로 미러링한 첫 PR 작업과, 이 패턴을 다른 에이전트 메모리 영역으로 확장하려는 큰 그림에 대한 회고.