Obsidian 메모가 AI로 '저절로 자란다'. 두 번째 뇌를 만드는 구조
흩어진 Obsidian 메모를 AI에게 읽혀, 링크 걸기·요약·정리를 반자동화하는 법. 쓰기는 신중하게, 읽기 중심으로 '저절로 자라는 Vault'를 만드는 실체험.
새벽 0시, 저는 Obsidian 검색창에 “그 명령어, 뭐였더라”라고 치고 있었습니다.
2년 전에 메모한, 고작 세 줄짜리 셸 명령어. 있는 건 확실해요. 그런데 500개를 넘긴 노트 어디에 묻혀 있는지, 도무지 떠오르지 않았습니다. 결국 그날은 찾기를 포기하고, 다시 구글에서 검색했어요.
이거, 저만 그런 게 아닐 거예요.
Obsidian(메모 앱)은 쓰면 쓸수록 편해집니다. 그런데 동시에, 확실하게 어질러져요. 일일 로그, 독서 메모, 코드 조각, 글 소재, 회의 메모. 전부 Markdown 파일로 쌓여 가다가, 반년만 지나면 “나만을 위한, 검색하기 어려운 쓰레기집”이 됩니다.
그래서 제가 시작한 게, Vault(메모 보관소)를 AI에게 읽혀, 정리를 절반쯤 대신 시키는 것이었습니다. 링크를 걸고, 요약을 만들고, 미아가 된 메모를 주워 올린다. 이걸 매일 조금씩 돌렸더니, 흩어져 있던 메모가 저절로 이어지기 시작해서, 정말로 “두 번째 뇌” 같아졌어요. 오늘은 그 이야기를 합니다.
애초에 ‘메모가 자란다’는 게 무슨 뜻?
보통 메모는, 쓴 순간이 정점입니다. 이후엔 잊혀 갈 뿐이죠.
그런데 Obsidian에는 [[노트 이름]] 이라는 표기로 노트끼리 잇는 기능이 있어서(Wikilink라고 합니다), 이으면 이을수록 한 장의 지도처럼 됩니다. “TypeScript 타입 오류” 메모에서 “그날 헤맸던 Docker 이야기”로 날아가고, 거기서 “지난달 장애 대응 회고”로 이어진다. 점이 선이 되고, 선이 면이 되는 감각이에요.
문제는, 이 잇기를 사람이 하면 너무 귀찮아서 안 이어진다는 것.
여기를 AI에게 맡깁니다. 구체적으로는 짝꿍으로 Claude Code라는 도구를 씁니다. 이건 코드를 짜기 위한 AI인데, 요는 “Markdown 파일이 잔뜩 든 폴더를, 안전하게 읽고 쓸 수 있는 작업 담당”으로 쓸 수 있어요. Obsidian의 로컬 퍼스트(자기 PC에 파일이 놓인다)한 장점은 그대로, 정리 수고만 AI에게 넘긴다. 이게 오늘의 작전입니다.
단, 딱 하나 처음에 못을 박게 해주세요. Vault 전체에 AI의 편집 권한을 주는 건, 절대 안 하는 게 좋습니다.
.obsidian/(설정 폴더)도, 공개 완료된 글도, 계약서 같은 개인 메모도, API 키도, AI에게 읽힐 이유가 없습니다. 그래서 저는 “읽기는 넓게, 쓰기는 극도로 신중하게”라는 방침으로 합니다. 이건 전에 쓴 AI에게 일을 맡기는 ‘발판’ 만드는 법의 사고방식 그대로로, 요는 보조 바퀴를 단 뒤에 달리게 한다는 이야기예요.
이런 장면에서 효과를 본다 (3가지)
추상론으로는 안 와닿으니, 제가 실제로 “오, 효과 있네” 싶었던 순간을 3가지.
첫째는, 아침의 일일 로그. 매일 아침, 전날 노트를 AI에게 읽혀 “안 끝난 작업만 오늘로 옮기고, 어제의 배움을 세 줄로 압축해줘”라고 부탁합니다. 그러면 어제의 내가 어질러 놓은 메모가, 오늘의 내가 움직일 수 있는 형태로 다듬어져 나와요. “아, 이거 어제 막혔던 거다” 하고 한순간에 떠오릅니다. 인수인계 상대가, 미래의 나인 거죠.
둘째는, 코드 조각의 구출.
아까의 “2년 전 명령어” 문제. snippets/ 폴더에 돌아간 명령어를 던져 두면, AI가 “이거, 무엇을 위한 명령어?”라는 설명문과, 관련된 다른 메모로의 링크를 멋대로 달아줍니다. 벌거벗은 명령어가, 검색으로 찾히는 ‘제대로 된 메모’로 둔갑해요.
셋째는, 글 소재 다지기.
블로그를 쓰는 날, content-ops/ 폴더의 소재장을 AI에게 보여주고 “이 글, 어느 과거 글에 내부 링크 걸 수 있을까?”라고 묻습니다. 저도 잊고 있던 반년 전 글을 끌어와 줄 때가 있어서, 이게 은근히 효과적이에요. 점과 점이 이어지는, 그 순간입니다.
내가 저지른 실패 3가지
여기서부터가 본론일지도 모릅니다. 처음 몇 주, 제 Vault는 AI에게 마구 헤집어졌어요. 솔직히 까발립니다.
첫째. 다짜고짜 Vault 전체를 “정리해줘”라고 부탁했다. 이게 가장 심했어요. AI는 2년 전의 알 수 없는 메모까지 “친절하게” 정리하려 들어서, 멋대로 태그와 제목을 잔뜩 자라게 했습니다. Graph view(메모의 연결을 시각화하는 화면)가, 모르는 태그투성이 정글이 됐어요. 오래된 메모의 의도 따위 AI는 알 수 없습니다. 그런데도 추측으로 움직여요. 그 뒤로 대상은 반드시 한 폴더만으로 좁힙니다.
둘째. 노트 이름을 AI에게 멋대로 바꾸게 했다.
“제목, 알기 쉽게 해둬.” 이걸로 [[옛 노트 이름]] 의 링크가 일제히 끊겼습니다. Obsidian은 자기가 이름을 바꾸면 링크도 따라와 주는데, 외부 AI가 일괄로 파일명을 갈아엎으면 그 따라오기가 안 먹혀요. 링크 끊김의 시산혈해. 지금은 “이름 변경은 반드시 사람에게 확인”을 철의 계율로 삼고 있습니다.
셋째. private/ 를 읽혀 버렸다.
이건 가슴 철렁 정도가 아니었어요. 계약 금액 메모가 든 폴더를, 권한 설정을 게을리한 탓에 AI가 읽을 수 있는 상태로 두고 있었습니다. 사고는 안 났지만, 운영 DB를 신입에게 맨손으로 만지게 한 것이나 마찬가지. deny(거부 리스트)에 제대로 적어 뒀으면, 애초에 사고 날 수가 없어요. 설정을 아낀 내가 잘못, 이라는 교훈입니다.
시작하는 법: 파일 3개만 둔다
어렵지 않습니다. Vault 바로 아래에 3개만 준비하면 돼요.
먼저, 폴더를 나눕니다. 처음부터 너무 잘게 분류하면 반드시 무너지니, 대충으로 충분합니다.
# Vault 바로 아래에서 실행. 매일 쓰는 곳·프로젝트·공개 작업·보호 영역만 나눈다
mkdir -p inbox daily projects content-ops snippets templates scripts archive private
각 폴더의 역할과, AI에게 어디까지 맡길지는 이렇게 배분합니다.
| 폴더 | 역할 | AI에게 맡길 범위 |
|---|---|---|
inbox/ | 미정리 메모, 웹 클립 받이 | 읽기와 신규 작성 |
daily/ | 일일 로그, 작업 메모 | 작성·추가·전날 요약 |
projects/ | 진행 중 작업, 인수인계 | 작성·갱신·미결 사항 정리 |
content-ops/ | 글 초안, 내부 링크, 공개 점검 | 초안 정리·링크 확인 |
snippets/ | 코드 조각과 사용법 | 설명 추가·태그 정리 |
templates/ | Obsidian 템플릿 | 원칙은 확인 후 편집 |
archive/ | 완료·보관 완료 | 편집 금지 |
private/ | 개인정보·계약·비밀 | 읽기도 금지 |
다음으로, Vault 바로 아래에 CLAUDE.md 를 둡니다. 이건 AI에게 “우리 규칙”을 적은 종이예요. 긴 이념을 늘어놓기보다, 지켜줬으면 하는 입출력 규칙을 짧게 적는 편이 효과 있습니다.
# Obsidian Vault 의 규칙
## 역할
- 이 Vault 는 일일 로그·프로젝트 인수인계·코드 조각·글 초안을 두는 곳.
- private 을 안 읽어도 도움이 되도록 움직일 것.
## 폴더 방침
- `daily/`: `YYYY-MM-DD.md` 로 일일 노트를 작성·갱신한다.
- `projects/`: 활성 안건마다 인수인계 노트를 1장 유지한다.
- `snippets/`: 돌아간 명령어와, 그 배경·실패 예를 세트로 남긴다.
- `templates/`: 읽는 건 자유. 편집하기 전에 반드시 묻는다.
- `archive/` 와 `private/`: 편집하지 않는다. private 의 내용은 요약도 안 한다.
## 쓰는 법 규칙
- 내부 링크는 `[[프로젝트 이름]]` 의 Wikilink 형식으로 쓴다.
- 노트 맨 앞에 YAML 속성(status 등)을 붙인다.
- 한 문단은 5줄 이내로 담는다.
## 안전 규칙
- 기존 노트의 이름 변경은, 반드시 먼저 확인한다.
- `.obsidian/` 의 설정은 갈아엎지 않는다.
- 일괄 편집 전에, 대상 파일을 목록으로 내고 승인을 기다린다.
- 편집 후에는 반드시 `node scripts/audit-wikilinks.cjs .` 를 돌린다.
마지막이 가장 중요합니다. .claude/settings.json 으로 권한을 물리적으로 묶어요. CLAUDE.md 는 ‘부탁’이지만, 이쪽은 ‘물리 잠금’. 실패담 셋째를 두 번 다시 반복하지 않기 위한 문지기입니다. 읽기는 넓게, 쓰기는 작업 폴더만, 위험한 조작은 deny 로 완전 봉쇄. 설정의 자세한 내용은 공식 권한 문서를 보세요.
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Read(./CLAUDE.md)",
"Read(./daily/**)",
"Read(./projects/**)",
"Read(./content-ops/**)",
"Read(./snippets/**)",
"Read(./templates/**)",
"Read(./scripts/**)",
"Edit(./daily/**)",
"Edit(./projects/**)",
"Edit(./content-ops/**)",
"Edit(./snippets/**)",
"Write(./inbox/**)",
"Write(./daily/**)",
"Write(./projects/**)",
"Write(./content-ops/**)",
"Write(./snippets/**)",
"Bash(node scripts/audit-wikilinks.cjs .)"
],
"ask": [
"Edit(./templates/**)",
"Bash(git *)"
],
"deny": [
"Read(./private/**)",
"Read(./**/.env)",
"Read(./**/.env.*)",
"Edit(./.obsidian/**)",
"Edit(./archive/**)",
"Write(./archive/**)",
"Bash(rm *)",
"Bash(del *)"
]
}
}
이 세 장을 두었으면, 이후엔 부탁하기만 하면 됩니다. 요령은 “알아서 잘 정리해줘”라고 말하지 않는 것. 범위·결과물·금지 사항·검증까지, 매번 전부 지정합니다.
daily/2026-06-04.md 와 projects/site-refresh.md 를 읽어줘.
templates/daily.md 를 바탕으로 daily/2026-06-05.md 를 만들어줘.
안 끝난 작업은, 담당이 지금도 내 것인 것만 인계해줘.
.obsidian/ 와 archive/ 와 private/ 는 건드리지 마.
편집이 끝나면 node scripts/audit-wikilinks.cjs . 를 실행하고 결과를 보고해줘.
짧지만, 입력·출력·금지 범위·검증 명령이 전부 들어 있습니다. “알아서 잘”보다 몇 배나 재현성이 높아요.
마무리 문지기: 링크 끊김을 기계로 점검한다
외부 AI에게 파일을 편집시키면, 가끔 Wikilink가 깨집니다. 사람 눈으로는 놓쳐요. 그래서 마지막에, 깨진 링크를 기계적으로 골라내는 스크립트를 끼웁니다. 이걸 scripts/audit-wikilinks.cjs 로 저장하세요.
#!/usr/bin/env node
// Vault 내의 깨진·모호한 내부 링크([[...]])를 골라내는 문지기 스크립트
const fs = require("node:fs");
const path = require("node:path");
const vaultRoot = path.resolve(process.argv[2] || ".");
const ignoredDirs = new Set([".git", ".obsidian", ".trash", "node_modules"]);
const allFiles = [];
const markdownFiles = [];
function walk(dir) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
if (ignoredDirs.has(entry.name)) continue;
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
walk(full);
} else if (entry.isFile()) {
allFiles.push(full);
if (entry.name.toLowerCase().endsWith(".md")) markdownFiles.push(full);
}
}
}
function toPosix(filePath) {
return filePath.split(path.sep).join("/");
}
function withoutMd(value) {
return value.replace(/\.md$/i, "");
}
function stripTarget(raw) {
return raw.split("|")[0].split("#")[0].split("^")[0].trim();
}
function safeDecode(value) {
try {
return decodeURIComponent(value);
} catch {
return value;
}
}
function isExternal(target) {
return /^(https?:|mailto:|obsidian:|#|\/)/i.test(target);
}
function candidateKeys(target, fromFile) {
const clean = safeDecode(stripTarget(target));
if (!clean) return [];
const keys = new Set();
const normalized = toPosix(path.normalize(clean));
keys.add(normalized);
keys.add(withoutMd(normalized));
if (clean.includes("/") || clean.includes("\\")) {
const fromDir = path.dirname(toPosix(path.relative(vaultRoot, fromFile)));
const relative = toPosix(path.normalize(path.join(fromDir, clean)));
keys.add(relative);
keys.add(withoutMd(relative));
} else {
keys.add(withoutMd(path.posix.basename(normalized)));
keys.add(path.posix.basename(normalized));
}
return [...keys].filter(Boolean);
}
walk(vaultRoot);
const byPath = new Map();
const byBase = new Map();
for (const file of allFiles) {
const rel = toPosix(path.relative(vaultRoot, file));
const lowerRel = rel.toLowerCase();
byPath.set(lowerRel, file);
if (rel.toLowerCase().endsWith(".md")) byPath.set(withoutMd(lowerRel), file);
const base = rel.toLowerCase().endsWith(".md")
? withoutMd(path.posix.basename(rel)).toLowerCase()
: path.posix.basename(rel).toLowerCase();
const list = byBase.get(base) || [];
list.push(file);
byBase.set(base, list);
}
const problems = [];
const wikilink = /!?\[\[([^\]]+)\]\]/g;
const markdownLink = /!?\[[^\]]*\]\(([^)]+)\)/g;
for (const file of markdownFiles) {
const text = fs.readFileSync(file, "utf8");
const rel = toPosix(path.relative(vaultRoot, file));
const targets = [];
let match;
while ((match = wikilink.exec(text))) targets.push(match[1]);
while ((match = markdownLink.exec(text))) {
const target = match[1].replace(/^<|>$/g, "").trim();
if (!isExternal(target)) targets.push(target);
}
for (const target of targets) {
const clean = stripTarget(target);
if (!clean || isExternal(clean)) continue;
const keys = candidateKeys(clean, file);
const pathHit = keys.some((key) => byPath.has(key.toLowerCase()));
const baseHits = keys.flatMap((key) => byBase.get(path.posix.basename(key).toLowerCase()) || []);
if (!pathHit && baseHits.length === 0) {
problems.push(`${rel} -> missing [[${clean}]]`);
} else if (!pathHit && baseHits.length > 1) {
problems.push(`${rel} -> ambiguous [[${clean}]] (${baseHits.length} matches)`);
}
}
}
if (problems.length) {
console.error("깨진/모호한 내부 링크가 발견됐습니다:");
for (const problem of problems) console.error(`- ${problem}`);
process.exit(1);
}
console.log(`OK: ${vaultRoot} 의 Markdown ${markdownFiles.length} 건을 확인했습니다`);
실행은 이게 전부입니다.
node scripts/audit-wikilinks.cjs .
링크가 전부 살아 있으면 OK 가 나옵니다. 끊겼으면, 어느 파일의 어느 링크가 죽었는지 목록으로 알려줘요. AI가 정리한 직후에 이걸 돌리는 게, 제 매일의 마무리입니다.
참고로 Obsidian 쪽 구조(Daily notes, Templates, Properties, 내부 링크)의 정확한 사양은 Obsidian 공식 도움말에 닿아 주세요. AI에게 맡기기 전에, 자기가 모선의 사양을 알아 두면 이상한 지시를 안 내려도 됩니다.
실제로 해본 결과
3개월, 이 구조로 제 Vault를 돌려 봤습니다.
가장 효과적이었던 건, 역시 “일일 로그 인수인계”와 “공개 전 링크 감사”였어요. 아침 일찍 AI에게 전날을 요약시키면, 시동이 걸리는 게 확연히 빠릅니다. “어제의 나, 여기서 멈춰 있었구나”가 한눈에 보이니까, 커피를 내리는 사이에 머리가 오늘 모드로 전환돼요. 링크 감사를 끼우게 된 뒤로는, 글을 공개한 다음에 “아, 내부 링크 끊겼네” 하고 핏기가 가시는 밤이 0이 됐습니다. 숫자로 말하면, 감사 전에는 한 달에 2~3번은 링크 끊김을 공개했는데, 지금은 0이에요.
또 하나, 뜻밖의 부산물이 있었습니다. AI에게 매일 만지게 한다는 전제가 생기면서, 제 메모 쓰는 법 자체가 바뀐 거예요. 나중에 기계에게 읽힐 거라 생각하면, 자연스럽게 status: 를 붙이거나, 거친 메모에도 한 줄만 맥락을 보태게 됩니다. AI를 위해 다듬고 있었던 셈인데, 결국 가장 읽기 편해진 건 사람인 나 자신이었어요.
반대로, 처음에 전체 Vault를 한 번에 정리시킨 회차는 확실히 실패였습니다. 오래된 메모의 의미를 AI가 너무 추측해서, 태그와 제목이 오히려 늘고 어질러졌어요. 결론은 단순합니다. “허가 범위는 좁게·템플릿은 얇게·마지막에 Node 감사” 세 가지를 지킬수록 잘 굴러갑니다.
재미있는 건, 반년 전이라면 절대 못 떠올렸을 2년 전 그 명령어에, 지금은 5초 만에 닿을 수 있다는 겁니다. AI가 꾸준히 달아준 링크를, 제가 그저 따라가기만 하면 돼요. 메모가, 제대로 뇌가 되어 왔습니다.
정리
“메모가 저절로 자란다”의 정체는, 마법이 아닙니다. 읽기는 넓게, 쓰기는 좁게, 마지막에 기계로 검산한다. 이 당연한 걸 구조로 만들었을 뿐이에요.
할 일은 3가지. 폴더를 나누고, CLAUDE.md 로 규칙을 적고, .claude/settings.json 으로 위험한 조작을 물리적으로 막는다. 이후엔 “알아서 잘”이라고 말하지 말고, 범위를 좁혀 부탁하기만 하면 됩니다. 오늘은 우선 daily/ 하나부터 시작해 보세요. 내일의 내가, 조금 편해집니다.
권한과 샌드박스 사고방식을 더 자세히 알고 싶은 분은 Claude Code의 승인·샌드박스 설계도 보세요. 손을 움직이는 템플릿이나, 팀 도입 상담처는 교재 목록에 정리해 뒀습니다.
무료 PDF: Claude Code 치트시트
이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.
개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.
작성자 소개
Masa
Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.
관련 글
Claude Code에게 파일 하나만 고치게 하는 지시문 작성법
'더 좋게 만들어줘'로 40줄이 바뀐 실패에서 배운, 수정 범위·검증·되돌리기를 묶은 Claude Code용 요청문 템플릿을 소개합니다.
Claude Code 권한 거절에서 복구하기: guardrail 을 약하게 만들지 않는 법
거절된 Claude Code 명령을 이유, 안전한 대안, 증거 명령, 재시도 조건으로 나누는 복구 workflow.
Claude Code Harness Smoke Test: 에이전트를 믿기 전 15분 검증 루프
Claude Code 작업 전에 범위, 금지 영역, 증거 명령, 공개 URL, 수익 CTA를 확인하는 실무 체크입니다.