🚀 요약

SUMMARY

작업하면서 남긴 노트·장애 기록·리포 히스토리를 커리어 자산으로 축적하려고, LLM(Claude Code)이 직접 작성·유지관리하는 개인 위키를 만들었다. 손대지 않는 원본(raw/)과 LLM이 소유하는 위키(wiki/)를 나누고, 소스 종류별 프론트매터 스키마와 운영 규칙을 CLAUDE.md 한 곳에 박아뒀다. 검색은 BM25+벡터+리랭킹 하이브리드(qmd)를 MCP로 붙였고, 다른 프로젝트 세션에서도 이 위키를 조회할 수 있게 Claude Code 플러그인으로 패키징했다.

나는 기억력이 나쁜 편이다. 작업하면서 노트는 열심히 남기는데, 정작 반년 뒤 “그때 그 etcd 문제 어떻게 풀었더라”를 다시 찾을 때는 어디에 적었는지부터 헤맨다. 이력서 쓸 때도 마찬가지다. 분명 한 일은 많은데 구체적인 근거가 여기저기 흩어져 있어서, 매번 처음부터 다시 발굴하는 기분이었다.

그래서 흩어진 기록을 한 곳에 쌓아두는 개인 위키를 만들기로 했다. 단 내가 손으로 관리하는 위키는 지금까지 다 실패했으니(귀찮아서), 이번엔 LLM한테 관리를 맡기는 쪽으로 방향을 잡았다.

1. 왜 RAG가 아니라 위키인가

처음엔 그냥 노트를 폴더에 몰아넣고 RAG로 검색하면 되는 거 아닌가 싶었다. 실제로 대부분 그렇게 쓴다. 파일을 잔뜩 넣고 질문하면 관련 청크를 찾아 답을 만든다. 이 방식의 약점은 축적이 없다는 것이다. LLM이 매 질문마다 지식을 처음부터 다시 발견한다. 다섯 개 문서를 종합해야 답이 나오는 질문이면, 매번 그 다섯 조각을 새로 찾아 짜맞춘다. 쌓이는 게 없다.

내가 원한 건 반대였다. 소스를 넣을 때마다 LLM이 읽고 핵심을 뽑아 기존 위키에 통합하는 것. 엔티티 페이지를 갱신하고, 개념 요약을 고치고, 새 소스가 옛 주장과 모순되면 그걸 표시해두는 것. 한 번 컴파일된 지식을 최신 상태로 유지하고, 질문할 때는 이미 정리된 위키를 읽는 구조다. 상호 참조도, 모순 표시도, 종합 정리도 질문 시점이 아니라 수집 시점에 이미 되어 있다.

사람이 위키를 포기하는 이유는 읽기나 사고가 힘들어서가 아니다. 상호 참조 업데이트하고, 요약 최신화하고, 수십 페이지 일관성 맞추는 기록 관리(bookkeeping)가 지겨워서다. LLM은 이걸 지겨워하지 않고 한 번에 열댓 개 파일을 고친다. 유지관리 비용이 거의 0에 수렴하니까 위키가 안 죽는다. 이 아이디어 자체는 내가 낸 게 아니라 “LLM이 유지관리하는 위키”라는 패턴 글에서 가져왔고, 내 도메인(DevOps·커리어)에 맞게 구체화하는 걸 목표로 삼았다.

2. raw와 wiki, 그리고 스키마

구조는 세 층으로 나눴다.

  • raw/ — 내가 큐레이션한 원본. LLM은 읽기만 하고 절대 수정하지 않는다. 단일 진실 공급원(source of truth)이다.
  • wiki/ — LLM이 생성·관리하는 마크다운. 페이지 생성, 갱신, 상호 참조 유지, 일관성 관리 전부 LLM 몫이다. 나는 읽고, LLM은 쓴다.
  • 스키마(CLAUDE.md) — 위키의 구조·규칙·워크플로우를 LLM에게 알려주는 문서. 이게 사실상 제일 중요한 파일이다. 이거 하나로 LLM이 범용 챗봇이 아니라 규율 있는 위키 관리자가 된다.

raw/는 성격별로 더 쪼갰다. 내가 직접 쓴 문서(my-docs/), 외부 스크랩(clippings/), 지식 대화 세션 노트(conversations/), 이미지 같은 첨부(assets/). GitHub 리포는 좀 다르게 다룬다. 코드를 raw에 복사해두면 금방 낡으니까, repos.md에 주소만 등록해두고 수집할 때 gh CLI나 GitHub MCP로 원격 조회한다. 커밋 히스토리와 PR을 훑으면 “내가 실제로 무슨 일을 했는지”가 나오는데, 이게 커리어 근거로 꽤 쓸모 있다.

NOTE

raw/를 불변으로 못 박은 게 이 구조의 핵심이다. LLM이 원본을 고치기 시작하면 진실 공급원이 흔들리고, 위키의 어떤 주장이 어느 원본에서 나왔는지 추적이 깨진다. 그래서 스키마 금지 사항 맨 위에 “raw는 절대 수정하지 않는다”를 박아뒀다. 예외는 진행 중인 대화 세션이 자기 파일을 append-only로 쓰는 경우뿐이다.

wiki/ 아래는 목적별 카테고리로 나눴다. 소스 1:1 요약(sources/), 경력 관리(career/), 트러블슈팅 케이스(troubleshooting/), 개념 백과(concepts/), 도구·서비스 엔티티(entities/), 프로젝트 작업 기록(work-log/). 하나의 소스가 여러 카테고리에 동시에 기여한다. 예를 들어 K8s 마이그레이션 회고 문서 하나를 수집하면, 소스 요약 + 커리어 성과 + etcd 트러블슈팅 케이스 + 마이그레이션 개념 + 작업 기록에 전부 흔적이 남는다.

3. 소스별 프론트매터 스키마

카테고리마다 프론트매터 스키마를 정해뒀다. LLM이 페이지를 만들 때 이 형식을 따르게 하고, 나중에 Obsidian Dataview로 “가장 많이 등장한 개념 TOP 20” 같은 뷰를 뽑을 여지를 남겼다. 트러블슈팅 페이지는 이렇게 잡았다.

# wiki/troubleshooting/ 페이지의 프론트매터.
# 필수 섹션(상황/원인/해결/교훈)까지 스키마에 규정해 케이스 형식을 통일한다.
---
title: "제목"
type: troubleshooting
date_occurred: YYYY-MM-DD
date_resolved: YYYY-MM-DD
severity: critical | major | minor
tags: []
related: []
---

개념·엔티티 페이지에는 source_count 필드를 뒀다. 그 개념이 몇 개의 소스에서 언급됐는지 세는 값인데, 이게 쌓이면 “내가 실제로 자주 다룬 기술”이 자연스럽게 드러난다. 커리어 페이지는 period·role·company를, 작업 기록은 date_start·date_end·project를 갖는 식으로 목적에 맞게 필드를 달리했다.

related는 Obsidian이 링크로 인식하게 하려고 형식을 좀 까다롭게 정했다. 한 줄로 늘어놓으면 링크로 안 잡혀서, 반드시 여러 줄 YAML 배열에 따옴표로 감싼 위키링크를 쓰게 했다.

# 이렇게 써야 Obsidian이 related를 실제 링크로 인식한다.
related:
  - "[[page-a]]"
  - "[[page-b]]"

파일명은 영어 kebab-case, 제목(H1)은 한국어로 갔다. 한글 파일명은 Obsidian URL 인코딩이나 git 호환성에서 성가신 문제가 생겨서다. 태그는 한영 병기([쿠버네티스, kubernetes])를 권장했는데, 한국어가 교착어라 형태소 분석 없이는 검색이 잘 안 걸리는 걸 나중에 우회하기 위한 보험이었다.

4. 대화와 클리핑의 역할 분리

만들다 보니 소스 하나가 애매해졌다. 내가 LLM이랑 나눈 대화 자체를 소스로 남기고 싶은데, 그 안에는 성격이 다른 두 정보가 섞여 있었다. 하나는 “내 의도·상황·판단”(내 환경에선 이렇게 쓴다, 이래서 이걸 골랐다)이고, 다른 하나는 “개념 자체의 객관 사실”(X가 무엇인가, 어떻게 동작하는가)이다. 이 둘은 검증 주체가 다르다. 앞엣것은 내가 말한 순간 자명하지만, 뒤엣것은 공식 문서 같은 출처로 검증해야 한다.

그래서 raw를 둘로 갈랐다.

  • raw/conversations/ — 내 의도·상황·개념 간 관계. 검증 주체는 나.
  • raw/clippings/ — 키워드·개념 자체의 객관 정보. 검증 주체는 출처.

개념 자체에 대한 사실은 conversations에 안 들어간다. 내가 대화 중에 “TCP 핸드셰이크가 뭐였지”라고 물으면, LLM은 그걸 대화 노트에 적는 게 아니라 공식 문서를 fetch해서 클리핑으로 우회한다. 위키 페이지는 결국 이 둘의 합성이다 — “정의·동작(클리핑 기반)” + “내 환경 맥락(대화 기반)“.

여기에 규칙 몇 개를 더 걸었다. 클리핑은 고아 금지다. 어떤 주장을 뒷받침하려고 인용했는지(supporting_claims)가 최소 하나는 있어야 저장한다. 탐색하다 그냥 열어본 자료는 저장 안 하고 휘발시킨다. 그리고 클리핑의 ## 원문 섹션은 fetch 결과를 그대로 보존한다. paraphrase·요약·불릿 변환 전부 금지. LLM이 “원문에 따르면 ~다”로 재서술하는 순간 인용의 신뢰도가 깨지기 때문이다.

대화 세션에는 가드레일도 하나 뒀다. 대화 중에는 wiki/를 절대 건드리지 못하게 막았다.

IMPORTANT

대화하면서 동시에 위키까지 고치면, 아직 검증 안 된 즉흥적 판단이 곧바로 위키에 박힌다. 그래서 대화 세션은 raw/conversations/raw/clippings/만 쓸 수 있고, 위키 반영은 반드시 별도 세션의 ingest 단계에서 한다. 세션 중에 위키 변경 명령을 부르면 거절하도록 커맨드에 못 박았다. 대화의 열기와 위키의 확정을 물리적으로 분리하는 셈이다.

5. 하이브리드 검색 MCP

위키가 작을 땐 index.md 하나로 충분하다. 카테고리별 페이지 목록에 한 줄 요약이 달려 있어서, 질의가 들어오면 LLM이 인덱스부터 읽고 관련 페이지로 들어간다. 실제로 페이지 수백 개 규모까지는 이 방식이 놀랍도록 잘 된다. 임베딩 인프라 없이도.

지금은 개념 40여 개, 엔티티 35개, 트러블슈팅 케이스 36개, 소스 요약 30여 개까지 쌓였다. 이 규모부터는 인덱스만으로 탐색이 슬슬 벅차서 제대로 된 검색을 붙였다. qmd를 골랐는데, 마크다운용 로컬 검색 엔진이라 온디바이스로 BM25 + 벡터 검색 + LLM 리랭킹을 다 한다. 클라우드로 내 노트가 나갈 일이 없다는 게 마음에 들었다.

qmd는 CLI와 MCP 서버 두 인터페이스를 준다. LLM이 셸로 검색 스크립트를 호출하는 것보다, MCP로 노출해서 네이티브 도구처럼 부르는 쪽이 응답도 빠르고 에러 처리도 깔끔하다. Claude Code라면 프로젝트 루트 .mcp.json에 등록하면 끝이다.

// .mcp.json — qmd를 MCP 서버로 붙인다. 이러면 LLM이 wiki 검색을 네이티브 도구로 호출한다.
{
  "mcpServers": {
    "qmd": { "command": "qmd", "args": ["mcp"] }
  }
}

위키를 수정하면 인덱스가 낡으니 갱신이 필요하다. 페이지를 추가·수정한 뒤 인덱스와 임베딩을 다시 만든다.

# 위키 페이지 변경 후 검색 인덱스와 벡터 임베딩을 재생성한다.
qmd update && qmd embed

6. 다른 세션에서 조회하는 플러그인

위키를 한참 쓰다 보니 문제가 하나 생겼다. 정작 이 지식이 필요한 순간은 다른 프로젝트를 작업할 때인데, 그때는 위키 저장소가 아니라 남의 코드베이스에서 세션을 열고 있다는 것이다. 위키를 조회하려고 매번 디렉토리를 옮기는 건 번거로웠다.

그래서 조회 기능만 떼어 Claude Code 플러그인으로 패키징했다. 이 플러그인은 위키를 읽기 전용으로 조회만 한다. 수집·수정은 원래 저장소에서 하고, 다른 세션에서는 검색만 하는 운영 모델이다. skill을 네 개 넣었다.

  • wiki-query — 자유 질의
  • career-history — 경력 히스토리 조회
  • task-trace — 개별 작업·장애·프로젝트 추적
  • career-evidence — 특정 기술의 경력 근거 수집(이력서·면접 준비용)

플러그인이라 위키 저장소 경로를 하드코딩할 수 없다는 게 문제였다. 그래서 실행 시점에 환경을 확인하는 작은 런타임 헬퍼를 뒀다. 환경변수(OBSIDIAN_WIKI_ROOT)가 있으면 그걸 쓰고, 없으면 현재 경로에서 상위로 올라가며 wiki/index.md가 있는 저장소를 찾는다. qmd CLI가 깔려 있는지도 여기서 확인한다.

조회 전략은 세 단계 폴백으로 잡았다.

  1. qmd MCP 우선 — 탐색과 카테고리 횡단 검색
  2. qmd CLI — MCP가 없지만 qmd는 깔려 있을 때
  3. 로컬 Glob/Grep/Read — 위키 루트를 찾을 수 있으면 결정적 읽기와 근거 검증

즉 탐색은 qmd로 넓게 하고, 정밀 검증은 로컬 파일 읽기로 좁힌다. 단순 검색 래퍼가 아니라 위키 구조 이해와 근거 검증을 합친 조회 레이어에 가깝다. 각 skill의 답변은 마지막에 [[위키링크]]로 근거 페이지를 명시하게 했고, 위키에 없는 경험은 지어내지 않도록 제약을 걸었다. 이력서 근거를 뽑을 때 없는 걸 그럴듯하게 부풀리면 그게 제일 위험하니까.

7. 운영 흐름과 규칙

일상 운영은 세 동작으로 돈다.

  • 수집(Ingest) — 새 소스를 읽고, 어느 카테고리에 어떤 페이지를 만들/고칠지 나한테 먼저 제안한다. 확인하면 소스 요약 → 관련 페이지 갱신 → index.md 갱신 → log.md 기록 순으로 처리한다. 나는 소스를 하나씩 넣으면서 요약을 읽고 방향을 잡는 쪽을 선호한다.
  • 질의(Query)index.md로 1차 탐색하고, 상세가 필요하면 qmd로 넓힌 뒤 관련 페이지를 읽어 종합한다. 답변이 저장할 가치가 있으면(비교, 새 연결, 종합 정리) 새 페이지로 저장을 제안한다. 내 탐색 활동도 위키에 복리로 쌓이는 셈이다.
  • 점검(Lint) — 주기적으로 위키 건강을 확인한다. 깨진 링크, 미등록 페이지, 인바운드 링크 없는 고아 페이지, 페이지 간 모순, 언급만 되고 페이지 없는 개념 같은 걸 훑는다.

두 개의 특수 파일이 이 흐름을 떠받친다. index.md는 콘텐츠 중심 카탈로그고, log.md는 시간순 활동 기록(append-only)이다. 로그 항목을 일관된 접두사로 시작하게 해두면(## [2026-07-21] ingest | 제목) 유닉스 도구로 파싱할 수 있다. 최근 활동 다섯 개는 이렇게 본다.

# log.md에서 최근 활동 항목 5개만 뽑아본다.
grep "^## \[" log.md | tail -5

새 세션이 열리면 LLM이 CLAUDE.mdindex.mdlog.md 최근 항목을 먼저 읽게 규칙을 박아뒀다. 이게 사실 위키가 해결하는 가장 실전적인 문제다. LLM 에이전트는 세션이 끝나면 대화를 잊는다. 어제 두 시간 파고든 분석도 새 세션에선 백지에서 다시 시작한다. 위키가 있으면 어제의 분석이 페이지로 남아 있고, 오늘은 인덱스만 읽고 거기서 이어간다. 커밋도 수집 단위로 끊어서(ingest: 소스 제목) 소스 하나가 위키에 어떤 변화를 줬는지 diff로 바로 보이게 했다.

물론 아직 다듬을 게 많다. 한국어 검색은 여전히 미진하고, 스키마도 소스가 쌓일 때마다 조금씩 고치는 중이다. 처음부터 완벽할 필요는 없고, LLM이랑 몇 번 반복하면서 내 도메인에 맞게 깎아가는 게 이 방식의 실제 재미이기도 하다.

🔗 참고