에이전트 지식 저장소는 LLM 에이전트가 참고할 지식을 어디에 정본으로 둘지 정하는 설계 문제다. 쓰기 주체가 하나고 문서가 수십~수백 편인 시작 단계에서는 데이터베이스보다 파일과 Git 쪽이 검증 게이트를 거의 공짜로 얻는다.
폴더엔 파일, 회의실엔 DB

혼자 쓰는 메모나 자료를 관리할 때, 우리는 그것들을 폴더에 파일로 쌓아 둘 뿐 따로 데이터베이스를 세워 넣지는 않는다. 그런데 LLM 에이전트가 참고할 지식을 어디 둘지 정하는 회의에선, 화이트보드에 거의 반사적으로 데이터베이스부터 그려진다.
같은 사람이, 같은 종류의 자료를 두고 정반대로 움직인다. 혼자 볼 땐 폴더면 충분하다고 느끼다가도, ‘에이전트’와 ‘지식’이라는 단어가 붙는 순간 검색·스키마·정합성이 줄줄이 떠오른다. 그건 DB의 영역이라고 배운 대로 손이 먼저 나가는 것이다. 하지만 이 반사는 DB가 이 문제에 강하다는 판단이 아니다. 그냥 습관이다.
그래서 이 글은 파일과 DB의 강점을 맞대결시키지 않는다. 파일이냐 DB냐는 강점의 유무가 아니라 순서의 문제이기 때문이다. 시작 단계에서는 파일과 Git이 지식의 정본 자리에 먼저 앉고, DB는 나중에 특정 신호가 켜질 때 그 위에 얹는 파생물이 된다. 그러니 ‘파일이 먼저’라는 말은 DB의 강점을 부정하는 주장이 아니라, 그 강점을 언제 불러올지에 대한 순서 논증이다.
‘에이전트 지식 저장소’가 성립하는 좁은 자리
논증을 펴기 전에 자리부터 좁혀 두자. 조건을 안 걸면 ‘무조건 파일이 낫다’는 과한 말이 되고, 그런 주장은 반박당하기 딱 좋다. 순서 논증이 성립하는 시작 레짐에는 세 조건이 있다.
첫째, 쓰기를 오케스트레이터 하나가 전담한다. 지식을 고쳐 넣는 주체가 여럿이 아니라 한 파이프라인이다. 둘째, 문서가 수십에서 수백 편 규모다. 수만 편이 아니다. 셋째, 갱신이 기계 속도가 아니라 사람 검토 속도로 일어난다. 하루에 몇 편이 사람 눈을 거쳐 들어온다.
이 셋이 모두 참일 때만 ‘파일이 먼저’가 성립한다. 하나라도 깨지면 이야기가 달라진다는 것은, 글 끝에서 세 신호로 다시 짚는다. 지금은 세 조건이 다 켜진 세계 안에서, 파일이 무엇을 값싸게 내주는지부터 보자.
파일이 LLM의 네이티브 인터페이스인 이유
에이전트가 파일을 다룰 때 쓰는 도구는 Read·Grep·Write다. 별도 설정 없이 처음부터 손에 쥐고 있는 기본 동작이다. 반면 DB에 지식을 넣으려면 스키마를 정하고, 쿼리를 짜고, 커넥터를 물려야 한다. 같은 ‘지식 하나를 읽고 쓴다’에 세 겹이 더 얹힌다.
물론 에이전트도 파일을 잘못 쓴다. 엉뚱한 노드를 링크하고, 형식을 어기고, 있지도 않은 문서를 가리킨다. 그런데 차이는 오류가 있느냐 없느냐가 아니다. 차이는 그 오류가 드러나는 비용에 있다.
파일에서 저지른 실수는 diff 한 조각으로 눈앞에 뜬다. 무엇이 어떻게 바뀌었는지 텍스트로 그대로 보인다. DB 안에서 잘못 들어간 값은 같은 실수라도 발견 경로가 길다. 스키마는 통과했는데 의미가 틀린 행은, 누군가 질의를 던져 이상을 눈치채기 전까지 조용히 앉아 있다. 파일이 유리한 건 실수를 안 해서가 아니라, 실수를 싸게 들키기 때문이다.
Git이 공짜로 주는 것들
실수를 싸게 들킨다는 그 장점은 사실 Git이 떠받친다. 파일 위에 Git을 얹으면, 인프라를 한 줄도 더 세우지 않고 네 가지가 딸려 온다. diff는 리뷰 단위가 되고, 커밋 이력은 이 지식이 언제 누구 손을 거쳤는지의 출처 기록이 된다. 브랜치는 검토 전 임시 무대가 되고, revert는 되돌리기 버튼이 된다. DB였다면 감사 로그·스테이징 테이블·롤백 절차로 따로 마련했을 것들이다.
여기서 한 걸음 더 나간다. 조회용으로 만든 파생물, 이를테면 그래프를 그리는 graph.json이나 목차를 뽑은 INDEX.md를 전부 ‘빌드 산출물’로 강등하는 것이다. 정본은 사람이 손으로 고치는 MD 파일뿐이고, 나머지는 언제든 다시 만들 수 있는 그림자로 둔다. 그러면 지식을 바꾸는 경로가 파일 하나로 좁아진다.
경로가 하나로 좁아지면, 바로 그 경로 위에 검증 게이트를 세울 수 있다. 그림 1에서 세 번째 칸이 그 자리다 — 정본이 커밋되고 산출물이 재생성되기 직전, 빌드가 스키마와 참조 무결성을 검사한다. 어긋나면 통과시키지 않는다. 정본과 산출물을 가른 그 경계 자체가 게이트가 되는 셈이다.
이 규율의 원문은 지식그래프 레포의 헌장 첫 조항에 그대로 박혀 있다.
MD 파일이 유일한 진실. graph.json, INDEX.md, 뷰어 화면은 전부 빌드 산출물. 산출물을 손으로 고치지 않는다. 서버(M4)도 DB 없이 MD 파일을 직접 읽고 쓴다.
출처: 지식그래프 레포 CLAUDE.md 절대 원칙 1산출물 파일 맨 위에 AUTO-GENERATED — 직접 편집 금지 한 줄을 헤더로 박아 두면, 사람이 실수로 산출물을 손으로 고치는 사고를 기계적으로 막는다. 자동 생성되는 목차 INDEX.md가 대표적이다. 손댈 파일과 손대면 안 되는 파일이 첫 줄에서 갈린다.
게이트가 말뿐인지 코드로 보자. 정본 노드들이 서로 가리키는 참조 하나가 깨졌을 때, 검사기가 어떻게 반응하는지 최소 버전으로 재현한 것이다.
# build.py --check 의 핵심: 정본 노드들이 서로 가리키는 참조가 모두 실재하는지 검사한다.
nodes = {
"CAU-0003": {"indicates": ["SYM-0002", "SYM-0006"]},
"SYM-0002": {"caused_by": ["CAU-0003"]},
"SYM-0006": {"caused_by": ["CAU-0003"]},
"MET-0005": {"detects": ["CAU-0099"]}, # CAU-0099 는 어디에도 없는 노드
}
ids = set(nodes)
broken = [
(src, rel, tgt)
for src, edges in nodes.items()
for rel, targets in edges.items()
for tgt in targets
if tgt not in ids
]
for src, rel, tgt in broken:
print(f"E: {src} --{rel}--> {tgt} (대상 노드 없음)")
print(f"--check: 깨진 참조 {len(broken)}건")
raise SystemExit(1 if broken else 0)E: MET-0005 --detects--> CAU-0099 (대상 노드 없음)
--check: 깨진 참조 1건깨진 참조 한 건에 검사기가 exit 1로 멈춘다. 이 종료 코드가 CI나 pre-commit에 물려 있으면, 참조가 어긋난 커밋은 애초에 들어오지 못한다. 게이트는 문서에 적힌 다짐이 아니라 실제로 커밋을 막는 스위치다.
위키 한 편이 검증되기까지
게이트가 정말 커밋을 막는 것까지 봤다. 그럼 이 규율이 얼마만 한 규모에서 돌아가는지, 숫자로 잡아 두자. 이 지식그래프의 노드는 지금 56개, 그 배경이 되는 위키 코퍼스는 105편이다. 운영을 시작한 지 여섯 주 남짓 됐다 — 수만 편이 아니라 딱 앞서 말한 ‘수십~수백 편’ 레짐 한복판이다.
그림 2은 그 여섯 주 동안 위키 문서가 어떻게 쌓였는지를 보여준다. 곡선은 매끈한 우상향이 아니다 — 문서 대부분이 4주차에 한꺼번에 들어왔고, 검증 게이트가 자리 잡은 뒤로는 새 문서가 게이트를 거쳐 천천히만 더해진다. 그 문서들이 검증 상태까지 올라갔는지는 다음 그림이 보여준다.
문서 한 편이 정본에 앉기까지 밟는 단계는 정해져 있다. 주제 큐에서 대기(queued)하다가, 수집되고(harvested), 게이트를 통과하고(gated), 검토를 거쳐 검증된다(verified). 그림 3은 그 네 시점을 코퍼스 전체의 스냅샷으로 나란히 세운 것이다. 시간이 갈수록 검증 상태의 셀 비중이 늘어난다.
여기서 핵심은, 이 상태 자체도 DB가 아니라 마크다운 테이블에 들어 있다는 점이다. 어떤 주제가 큐에 남아 있고 어떤 문서가 검증됐는지가 전부 사람이 읽는 표로 관리된다. 그래서 상태가 바뀌면 그 전이가 커밋 이력에 그대로 남는다.
배당은 여기서 나온다. 도메인 전문가가 이 지식을 검토할 때, 어드민 UI도 권한 테이블도 필요 없다. 파일을 그냥 읽고, diff에 코멘트를 달아 틀린 곳을 짚는다. 사람이 별도 도구 없이 그냥 읽을 수 있다는 것, 그게 이 구조가 주는 진짜 배당이다.
DB가 이기는 자리는 정직하게 인정한다
그렇다고 DB의 강점을 허수아비로 세워 두고 넘어가면 안 된다. DB는 여러 필자가 동시에 쓰는 상황을 트랜잭션으로 안전하게 처리하고, 방금 쓴 값을 즉시 일관되게 읽어 주고, 복잡한 질의를 인덱스로 빠르게 답한다. 이건 진짜 강점이고, 파일이 흉내 내기 어려운 자리다.
그러면 앞의 구조는 동시성 문제를 어떻게 ‘이겼’을까. 답은, 이기지 않았다는 것이다. 이 구조는 동시 쓰기를 트랜잭션으로 푼 게 아니라, 단일 쓰기 주체 규율과 제안 큐로 애초에 동시 쓰기가 생기지 않게 설계했을 뿐이다.
그림 4의 두 레인이 그 순서를 보여준다. 변경은 제안으로 큐에 쌓이고, 사람이 diff를 검토해 승인하면, 그제서야 오케스트레이터 한 주체가 파일을 쓰고 산출물을 다시 만든다. 쓰기가 한 지점을 통과하니, 트랜잭션이 필요할 만큼 겹칠 일이 없다.
이 레포엔 다중 클라이언트 동시 접속을 받는 서버(설계상 M4) 자리가 못박혀 있다. 다만 설계 문서와 디렉터리 구조에만 있을 뿐 아직 구현하지 않았다 — 실제 server/ 폴더는 비어 있다. 그리고 헌장은 그 서버조차 "DB 없이 MD 파일을 직접 읽고 쓴다"고 미리 못박아 둔다.
정직하게 말하면, 이 구조는 동시성·트랜잭션·대규모 질의 같은 DB의 홈그라운드에서 스트레스 테스트를 해 본 적이 없다. 여기서 파일이 DB를 이겼다는 게 아니다. 그 문제들이 아직 발생하지 않는 자리에 이 저장소가 있을 뿐이다.
DB로 갈아탈 세 신호
그러니 ‘언제 갈아타나’가 진짜 질문이다. 세 신호 중 하나라도 켜지면, DB를 정본 파일 위에 얹을 때다.
첫째, 독립적으로 쓰는 주체가 여럿이 된다. 오케스트레이터 하나가 아니라 여러 클라이언트가 동시에 지식을 고친다. 둘째, 방금 쓴 값을 지연 없이 즉시 읽어야 하는 요구가 생긴다. 주기적 감사로 맞추는 결과적 일관성으론 부족해지는 순간이다. 셋째, 파생 인덱스로 감당이 안 되는 질의 부하가 걸린다. 파일을 훑어 만드는 산출물이 더는 응답 속도를 못 받쳐 준다.
세 신호가 다 꺼져 있는 동안 붙잡을 원칙은 세 줄이면 된다. 정본은 파일이고 조회는 산출물이다. 산출물은 손으로 고치지 않는다. 쓰기 경로는 하나로 좁히고 그 위에 게이트를 세운다.
‘파일이냐 DB냐’는 취향으로 정하는 문제가 아니다. 지금 이 세 신호가 몇 개나 켜져 있는지로 갈린다. 아직 하나도 안 켜졌다면, 화이트보드에 원통부터 그릴 이유도 아직 없다.
더 읽기
- 지식그래프 레포 설계 문서, "절대 원칙" (CLAUDE.md). MD 파일 정본·빌드 산출물 분리와 M4 서버 조항의 원문.
- 관련 글: 짧은 CLAUDE.md가 더 강한 에이전트를 만든다 — 같은 레포의 규율 문서를 3층 메모리로 다룬 자매 편.
- 관련 글: 일을 나눠 주면 빨라질 줄 알았다 — 단일 쓰기 주체가 왜 값싼지, 팬아웃의 비용 쪽에서 본 배경.