Skip to content

Latest commit

 

History

45 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

claude-obsidian-logger

Claude Code 세션을 Obsidian vault에 자동 기록하는 SessionEnd 훅.

세션이 끝나면 그날 한 일·알게 된 사실·기각한 대안을 주제별 마크다운에 쌓는다. 읽는 것은 필요할 때만 — 세션 시작 시 아무것도 주입하지 않는다. 벡터DB도 상주 프로세스도 없다.

두 축이 있어야 동작한다

담당 설치물
쓰기 세션이 끝나면 훅이 vault에 쌓는다 hooks/session_log.py + settings.example.json
읽기 필요할 때 모델이 vault를 찾아 읽는다 CLAUDE.example.md~/.claude/CLAUDE.md

세션 시작 시 자동 주입이 없으므로 읽기는 규칙이 없으면 일어나지 않는다. 훅만 설치하면 기록은 쌓이는데 아무도 읽지 않는 상태가 된다 — 이게 실제 실패 모드다.

산출물

$OBSIDIAN_VAULT/
├── INDEX.md                    **정본이자 목차.** 주제 + 할 일 + 최근 완료
│                                 할 일은 주제 아래로 접히고 5. / 5-2 번호가 붙는다
│                                 태스크를 체크하면 완료로, 주제를 체크하면 그 주제가 닫힌다
│                                 사람은 체크박스만 건드리고 나머지는 자동으로 다시 쓰인다
├── topics/<slug>.md            주제 지식. 주제 1개 = 파일 1개
│                                 🔜 다음 · 📌 결론 · ❌ 접은 안 · 📈 진행 로그
│                                 훅이 append, 사람이 편집. 기존 줄은 훅이 안 건드림
├── conversations/<sid8>_<날짜>.md   그날 대화 원문 + 진행 요약 머리말
├── daily/<날짜>.md             그날 한 줄 (0토큰)
├── weekly/<ISO주차>.md         주간 집계 (0토큰)
└── 완료 아카이브.md            2주 지난 완료 (INDEX 의 ✅ 완료 섹션에서 내려온다)

각 파일이 답하는 질문이 하나씩이고 겹치지 않는다 — "지금 뭘 해야 하나"(INDEX) / "이 주제 어디까지 왔나"(topics) / "그때 뭐라고 했나"(conversations) / "그날·그주 뭘 했나"(daily·weekly).

설치

원하는 곳에 clone 한다. 훅은 경로만 맞으면 어디 있어도 된다. 다만 ~/.claude/hooks/는 피한다 — 다른 훅들이 함께 쓰는 공용 공간이라 레포를 두면 서로 간섭한다.

1단계 — 훅을 배선한다

~/.claude/settings.json에 훅을 배선하고 command 경로를 clone 위치로 맞춘다 (settings.example.json 참고):

{
  "hooks": {
    "SessionEnd": [{ "hooks": [{ "type": "command", "command": "python3 ~/claude-obsidian-logger/hooks/session_log.py" }] }]
  }
}

claude CLI가 PATH에 있어야 한다 (요약에 claude -p 사용).

2단계 — 조회 규칙을 CLAUDE.md 에 넣는다

CLAUDE.example.md 의 내용을 ~/.claude/CLAUDE.md 에 붙여넣고 <vault> 를 실제 경로(절대경로)로 바꾼다.

무조건 주입을 두지 않는 이유는 비용이다 — 단발 질문 세션까지 매번 수천 토큰을 물었다(실측 월 $4 대). 읽어야 하는 세션은 일부인데 비용은 전체가 문다. 그래서 읽을지 여부를 모델이 요청을 보고 판단하게 했다.

첫 요청 동작 비용
진행 상황·이어서 할 일 질문 INDEX.mdtopics/<slug>.md → (필요시) plan: 경로 필요분만
단발 질문 아무것도 읽지 않음 0
신규 기획 계획서 작성 + 주제 슬러그 바인딩 0

규칙에 담긴 것 — 진행 중 작업 조회(번호 지시 대응 포함) / 기획 시 주제 바인딩(plan: frontmatter를 넣으면 INDEX.md 목차에 "먼저 이 파일을 읽어라"는 지시문으로 실려 다음 세션에 따라 읽힌다) / 결정 이관(작업 종료 시 계획서 → 주제 파일) / 태스크 수용 기준.

설정 (환경변수)

변수 기본값 설명
OBSIDIAN_VAULT ~/Documents/Obsidian vault 경로
SESSIONLOG_MODEL claude-sonnet-4-6 요약 모델
SESSIONLOG_INDEX_FILE INDEX.md 목차 겸 태스크 정본 파일명
SESSIONLOG_STATE_DIR ~/.claude/hooks 상태 파일 위치 (아래)
SESSIONLOG_CATCHUP_DAYS 3 수동 --catchup 이 훑는 기간(일)

코드와 상태의 분리

증분 마커 DB(sessionlog.db)·락·디버그 로그는 레포 밖(SESSIONLOG_STATE_DIR)에 둔다. clone 위치를 옮겨도 기록이 이어지고, 레포에는 로컬 상태가 섞이지 않는다.

⚠️ 이 경로를 바꾸면 증분 마커가 초기화되어 전 세션이 재요약되고 진행 로그가 중복된다. 옮겨야 한다면 sessionlog.db를 함께 옮길 것.

마커

사용자 발화에서 찾는다. 판정은 전부 기계가 하고 LLM 에게 묻지 않는다 — 같은 대화가 날마다 다르게 처리되는 것을 막기 위해서다. 프롬프트에 싣지 않으므로 토큰 비용은 0이다.

마커는 그 줄에 마커만 있을 때 인정한다. 부분일치는 마커를 이야기하는 문장까지 잡는다 — #nolog 는 무슨 뜻이야 한 줄이 그 세션의 기록을 통째로 날린다. compact 요약과 백그라운드 작업 알림은 마커를 인용하고 있어도 시스템이 넣은 텍스트라 검사에서 뺀다. 실측: 실제 사용 47건이 전부 단독 줄이었고, 문장 속 12건은 대부분 마커를 설명하는 대화였다.

마커 효과
#nolog · #기록제외 · #skiplog 그 세션을 기록하지 않는다. 한 번 치면 이후 flush 에도 계속 적용된다
#로그 그 줄부터 메시지 끝까지 제외. 바로 아래가 코드펜스면 그 블록만 제외하고 뒤는 남긴다 (사이에 빈 줄이 있어도 된다). 한 메시지에서 여러 번 쓸 수 있다
#완료 · #done 태스크만 만들지 않는다. 진행 로그·대화·주제 매칭은 그대로

#완료 는 "한 일은 남기되 할 일은 안 만든다"다. 단발 작업은 주제에 붙지 못해 남은 일이 태스크로 가지 않으면 어디에서도 보이지 않으므로 기본은 태스크를 만드는데, 그 자리에서 끝난 일이라면 이 마커로 억제한다.

왜 이 에러가 나지?        ← 남음
#로그
app-1 | DEBUG | ...       ← 제외 (줄 수 무관)

펜스로 감싸면(```#로그 … ```) 블록만 제외하고 뒤 내용은 남는다.

이어서 하기 (재개)

INDEX.md 는 목차이자 재개 대시보드다. 최근 활동 역순으로 정렬되고, 주제마다 그대로 복사해 실행할 수 있는 명령이 붙는다.

- [ ] **2.** [[topics/connect-auto-recovery|Kafka Connect 자동 복구]] · 12일 전 · data-infra · 남은 일 2
  ↳ `cd /Users/…/data-infra && claude -r ada80d44-…`  · 기준 HEAD 미기록
  ⚠ 미커밋 변경 있음
  • 원본 트랜스크립트가 30일이 지나 사라졌으면 claude --add-dir <vault> '<주제문서> 를 읽고 …' 처럼 그대로 실행되는 문서 재개 명령으로 바뀐다
  • topics/ frontmatter 에 cwd·branch·head·session·verified 를 남긴다. head 는 그 세션 시점의 것이라 재개 전에 코드가 얼마나 변했는지를 잴 수 있다
  • 한 주제가 여러 저장소에 걸치면(repos·workspaces) 저장소마다 따로 검사한다
  • "변화 없음"과 "측정 불가"를 구분한다. 기준 HEAD 가 없으면 0커밋이 아니라 기준 HEAD 미기록 이다 — 없는 확신을 만들지 않는다

동작

  • 활동일 단위로 1콜. 세션이 며칠에 걸쳐도 날짜별로 나눠 각각 요약한다.
  • 요약이 실패하면 아무것도 쓰지 않는다. 오프라인·타임아웃으로 LLM 호출이 실패하면 증분 마커를 전진시키지 않고 중단한다 — 다음 실행이 같은 구간을 다시 요약한다.
  • 요약기는 기존 태스크에 접근하지 않는다. 추가할 것만(tasks_add) 받아 파이썬이 목록에 끼워 넣는다. {"text": "…", "after": "이 항목 뒤"} 로 위치를 지정할 수 있고, 못 찾으면 맨 뒤에 붙는다. 전체를 다시 쓰게 하던 방식보다 출력 토큰이 35~38% 적고, 순서·태그·링크가 훼손될 수 없다.
  • 막힌 것(blocker)을 따로 뽑는다. 남의 승인·VPN·권한처럼 지금 당장 풀 수 없는 것만. 재개하기 전에 "이거 하려면 뭐가 필요한가"를 먼저 알려준다. 풀리면 다음 flush 에 사라진다.
  • INDEX 첫 줄에 고를 재료를 준다 — 진행 중 개수 · 가장 최근 주제 · 하다 만 흔적(미커밋이 있는 저장소). 기계가 "오늘 이걸 하라"고 고르지는 않는다. 우선순위를 잘못 정하면 정보가 없느니만 못하다.
  • 콜마다 비용·토큰을 SQLite llm_usage 에 남긴다. 프롬프트를 대화/태스크/주제목록/지시문으로 쪼개 함께 기록하므로, 나중에 "무엇이 비용을 만들었나"를 쿼리로 따질 수 있다.
  • vault 가 git 저장소면 flush 후 자동 커밋한다. topics/ 는 자체 백업이 없어 git 이 유일한 복구 수단인데, 수동 커밋에만 의존하면 되돌릴 지점이 드문드문해진다.
  • 증분. 처리한 turn 수를 SQLite에 기록해 재요약하지 않는다.
  • 기록 여부는 기계 판정. 분량 게이트만 쓰고 LLM에게 묻지 않는다 (같은 대화가 날마다 다르게 처리되는 것을 막는다).
  • SessionEnd 하나로 돈다. 예약 실행이 없다. 세션이 며칠에 걸쳐도 종료 시점에 날짜별로 나눠 기록하므로 빠지지 않는다.
  • 실패한 세션은 pending 에 남고 다음 SessionEnd 워커가 회수한다. 자동 실행이 없으니 회수 지점도 거기뿐이다. 현재 세션을 먼저 기록하고 그 뒤에 실행당 2건까지 재시도한다 — 백로그가 방금 끝난 세션을 밀어내지 않게 하기 위함이다. 5회 실패하면 자동 재시도를 멈추되 경고에서는 빠지지 않는다(멈추는 것과 없던 일로 만드는 것은 다르다). INDEX.md 머리말에 건수와 회수 명령이 함께 뜬다.
  • 주제 파일은 사람만 만든다. 요약기는 topics/ 에 있는 슬러그 중에서 고른다. 못 고르면 이번 대화를 부를 이름표(topic_new)를 짓고, 파이썬이 그것을 완료된 주제와 이미 쓰이는 이름표까지 포함해 유사도로 대조한다(0토큰). 맞으면 그쪽에 합치고, 아니면 새 이름표를 쓴다. 파일은 어느 경우에도 만들지 않는다 — 이름표는 목차에서 할 일을 묶는 키일 뿐이다. topics/<그 슬러그>.md 를 사람이 만들면 링크가 이미 그 슬러그를 가리키므로 묶음째 주제로 올라간다.
  • 빈 파일로 만들어도 된다. 승격된 주제 파일에 frontmatter 가 없으면 다음 렌더에서 제목(태스크 링크 alias)·뼈대 4섹션·재개 좌표(session/cwd, ~/.claude/history.jsonl 에서 복구)를 채우고, 그 주제를 가리키던 대화의 📌 결론·❌ 접은 안·📈 이날 진행 을 옮겨 온다. 뼈대가 없으면 세 곳이 조용히 깨진다 — 목차에 슬러그가 그대로 뜨고, frontmatter 가 없어 주제 체크(status: done)가 저장되지 않으며, 섹션이 없어 이후 세션의 결론이 버려진다.
  • 도구 호출/결과와 #로그 블록은 저장·요약 양쪽에서 제외하되 turn 구조는 보존한다(증분 마커가 깨지지 않도록).
  • 결론·기각한 대안을 자동 축적. 📌 결론·❌ 접은 안append 만 한다. 요약기는 기존 목록을 볼 수 없으므로(주제가 같은 콜에서 정해진다) 파이썬에서 유사도로 중복을 막는다.
  • 체크는 INDEX 에서 하고 즉시 반영된다. FileChanged 훅이 완료 시각을 남기고 바로 다시 렌더한다. 워커가 락을 쥐고 있으면 건너뛴다 — 그 워커가 끝나며 어차피 다시 렌더하므로 기다릴 이유가 없다.
  • 주제도 체크할 수 있다. 체크하면 topics/<slug>.mdstatus: done 이 되어 목록에서 빠진다. 태스크가 0인 주제가 "끝난 것"인지 "아직 안 쪼갠 것"인지 목차만 봐서 알 수 있어야 하기 때문이다. 다시 열려면 그 파일의 status 를 되돌린다.
  • 태스크의 정본은 INDEX.md 하나다. 목록 파일을 따로 두면 번호가 어긋나고 "체크했는데 목차엔 남아있다"가 생긴다.
  • 세션 허브를 두지 않는다. 주제가 잡히면 topics/ 가 정본이라 허브에는 포인터만 남았다 — 파일만 늘고 읽히지 않는다. 주제 파일이 없는 세션의 진행 요약·결론·접은 안은 대화 페이지 머리말로 얹는다. 기록의 축은 주제와 날짜이지 세션이 아니다.
  • 태스크는 생길 때 주제에 묶는다. 새 태스크 줄에 [[topics/<slug>|…]] 를 심고 INDEX 가 그것으로 그룹핑한다. 귀속은 태스크가 생기는 순간에만 확실한 정보라, 나중에 텍스트 유사도로 추정하지 않는다(세션마다 답이 흔들린다). 주제 파일이 없는 슬러그도 기타 태스크 에서 묶음으로 렌더되고 번호가 주제에서 이어진다. 링크 alias 가 그 묶음의 제목이다 — 목차는 매 렌더 다시 쓰이므로 제목을 둘 곳이 거기뿐이다.
  • 묶음 줄에는 체크박스가 없다. 담을 파일이 없어 '묶음 완료' 상태를 저장할 곳이 없기 때문이다. 하위 태스크 체크는 주제와 동일하게 동작하고, 전부 체크되면 미완료가 0이 되어 묶음이 사라진다. 대신 마지막 활동일과 방치 경과(30일 이상 ⚠ N일째)를 계산해 붙인다 — 상태를 저장하는 대신 보여준다. 보류·재개가 필요해지면 그때 주제로 올리라는 신호다. 날짜는 링크 대상(| 앞)에서만 뽑는다 — 제목에 든 날짜를 활동일로 오인하지 않기 위해서다.
  • 🔜 다음 이 로드맵, 태스크는 그 밖의 것. 둘 다 "다음에 할 일"이라 같은 콜에서 뽑으면 같은 문장이 양쪽에 나온다. 🔜 다음 은 재개 지점 + 남은 단계 요약(1개, 매번 덮어씀)이고, 태스크는 ① 주제 밖 단발 작업 ② 주제 안이지만 순서 밖이라 잊기 쉬운 일 ③ 완료 날짜를 남겨야 하는 일 셋만 받는다. 순서가 있고 서로 의존하는 단계는 체크박스로 쪼개지 않는다. 완료 이력이 남는 것은 태스크뿐이다 — 🔜 다음 은 덮어써서 무엇을 끝냈는지 남지 않는다.
  • INDEX 번호는 렌더 순번. 5. 5-2 로 지시하면 짧게 끝난다. 영구 ID 가 아니라 다음 렌더에 밀리는데, INDEX 를 열어보고 지시하는 이상 사용자가 보는 번호와 읽히는 번호가 같은 파일에서 나오므로 무해하다. 대신 남는 문서에는 번호를 쓰지 않는다 — 거기 적힌 번호는 다음 렌더에 거짓이 된다.

토큰 사용량

요약 1콜당 대략 이 정도다 (실측, 대화량에 비례):

구분 토큰
입력 — 프롬프트 (대화 + 주제 목록 + 태스크) ~17,000
입력 — 캐시 읽기 (claude -p 의 시스템 프롬프트·툴 정의 등 고정분) ~20,000
출력 (JSON 6필드) ~2,400

콜은 (세션 × 활동일)마다 한 번이다. 하루에 세션 3개를 돌렸다면 그날 3콜.

입력의 절반가량은 대화가 아니라 claude -p 하네스 고정 오버헤드이고, 이 부분은 프롬프트 캐시로 읽히므로 실제 과금은 신규 입력분보다 훨씬 싸다. 정확한 비용은 SESSIONLOG_MODEL 로 지정한 모델의 단가에 위 값을 곱해 계산하면 된다.

한 콜이 읽는 대화량은 SUMMARY_CHAR_BUDGET(기본 15,000자)으로 묶여 있다. 그날 대화가 이보다 많으면 뒤에서부터 채우고, 남는 예산으로 앞쪽의 질문과 그 직후 답변 첫 문단을 담는다. 결론은 대개 끝에 나오므로 뒤를 우선한다.

한계

  • 한국어 전용 — 프롬프트·섹션 헤더·기본 파일명이 한국어다.
  • macOS에서만 검증fcntl 사용. Linux는 동작할 것으로 보이나 미검증, Windows 미지원.
  • vault 구조가 고정되어 있다.

안전장치

--dry-run / --dry-run-llm으로 실파일 변경 없이 임시 디렉토리에 산출물을 만들어 확인할 수 있다.

python3 session_log.py --dry-run-llm <transcript.jsonl> --out /tmp/check

트랜스크립트는 ~/.claude/projects/<프로젝트>/<세션id>.jsonl에 있다.

기록에 실패한 세션은 INDEX.md 머리말에 건수로 뜬다. 회수는 아래 명령이다 — 자동 재시도 상한(5회)을 넘긴 것까지 전부 다시 돌리고, 남는 게 있으면 exit 1 이다.

python3 hooks/session_log.py --retry-pending

--catchup 은 최근 SESSIONLOG_CATCHUP_DAYS(기본 3)일 안에 열려 있던 세션을 훑고, 밀린 pending 도 함께 비운다.

테스트

pytest 가 아니라 그냥 스크립트다. 각자 임시 vault 와 임시 상태 디렉터리를 만들어 돌므로 실제 vault·DB 를 건드리지 않는다.

for t in tests/*.py; do python3 "$t"; done
  • test_handoff.py — SessionEnd 입력 → 주제 frontmatter → INDEX 전 경로
  • test_bare_topic.py — 빈 주제 파일 승격(제목·뼈대·대화 이관·체크 유지)
  • test_hardening.py — 실제로 재현된 결함들의 회귀 방지

About

Claude Code 세션을 Obsidian vault에 자동 기록하는 훅 — 주제별 마크다운, 벡터DB 없음

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages