
대화 로그를 Obsidian의 장기 기억으로 변환하기 ― 매일 밤 자동으로 증류하는 파이프라인
요약
Claude Code의 대화 로그를 Obsidian의 구조화된 지식 베이스로 자동 변환하는 파이프라인 구현 방법을 다룹니다. macOS의 보안 권한(TCC) 문제와 iCloud 동기화 제약을 극복하며 안정적인 자동화 시스템을 구축하는 노하우를 공유합니다.
핵심 포인트
- 대화 로그를 Obsidian Vault로 자동 증류하는 3단계 파이프라인 구축
- macOS TCC 보호 영역 접근을 위한 전체 디스크 접근 권한 설정 필요
- iCloud와 심볼릭 링크 충돌 방지를 위한 스크립트/데이터 위치 분리
- 사일런트 실패 방지를 위한 프리플라이트 검증 및 알림 체계
- 재시도 로직과 락(lock) 메커니즘을 활용한 자기 회복형 구조
「Claude Code 환경」 시리즈입니다. 기억을 4개 층으로 나눈 이야기에서 「대화 로그 = 층 1」, 「Obsidian Vault = 층 4」라고 썼습니다만, 이번에는 그 **층 1에서 층 4로 매일 밤 자동으로 증류하는 배관(pipeline)**의 구현에 대해 쓰겠습니다.
대화 로그는 있는 그대로 두면 거대해서 읽을 수 없습니다. 반면 Obsidian의 인간 Wiki는 구조화된 장기 기억입니다. 이 두 가지를 「매일 밤, 최근 28시간 분량만, 도메인 구조를 지키며 추가」하는 것을 무인으로 돌리고 있습니다. 직접 해보며 알게 된 사일런트 실패(silent failure)를 방지하는 방법과 날조를 방지하는 장치가 이 기사의 내용입니다.
launchd로부터 매일 아침 기동하며, 대략 3단계 공정으로 이루어집니다.
채취: 대화 로그(층 1)를 최신화한다 (extract_conversations.py)
증류: claude -p를 헤드리스(headless)로 실행하여, 최근 28시간의 차분(diff)만 Vault의 해당 도메인에 추가
백업: 매번 git commit + private repo에 push (상황이 나빠져도 revert 가능)
증류는 소스별로 2개(Claude 대화와 Codex 대화)로 나누었습니다. 이유는 후술하겠습니다만, 하나로 합치면 시간 프레임 안에 들어오지 않아 매일 밤 타임아웃(timeout)이 발생했기 때문입니다.
처음에 전부 막혔던 부분이 이것입니다. Vault는 iCloud 동기화가 적용되는 ~/Documents 하위에 있으며, macOS의 TCC(개인정보 보호)에 의해 보호되고 있습니다. launchd에서 git을 실행해도 보호 영역에 쓸 수 없어 실패합니다.
게다가 까다로운 점은, 이것이 「사일런트 실패」가 되기 쉽다는 것입니다. 그래서 프리플라이트(pre-flight) 단계에서 조기에 검지하여 큰 소리로 로그를 남기고 알림을 보냅니다.
# launchd 하위에서는 ~/Documents(보호 영역)에 접근할 수 없는 경우가 있다.
# 여기서 조기에 검지하여, 사일런트 실패(exit 0 위장)를 방지한다.
if ! ( cd "$VAULT" && git rev-parse --git-dir >/dev/null 2>&1 ); then
...
해결책은 「시스템 설정 → 개인정보 보호 및 보안 → 전체 디스크 접근 권한에서 /bin/bash를 허용」하는 것입니다. 스크립트 자체는 보호 외 영역(~/.claude/scripts/)에 둡니다. ~/Documents에 두면 launchd에서 exec할 수 없습니다.
symlink를 사용하여 ~/로 우회하는 방안도 시도해 보았습니다만, iCloud가 symlink를 충돌 처리하여 망가뜨리기 때문에 불가능했습니다. 보호 영역과 iCloud 동기화가 겹치면 직관에 반하는 제약이 늘어납니다. 스크립트는 보호 외 영역에, 데이터는 보호 영역 내에 두는 것으로 위치를 확실히 나누는 것이 안정적입니다.
덮개를 닫고 슬립(sleep) 상태가 되면 야간 작업은 동결됩니다. caffeinate -s는 AC 전원 연결 시에만 작동하므로, 배터리 구동 시에는 평소처럼 멈춰버립니다. 그래서 **「성공할 때까지 여러 슬롯에서 재시도, 성공하면 즉시 종료」**하는 자기 회복형 구조로 만들었습니다.
- plist는 4:55 / 8:15 / 10:15 / 12:15 등 여러 슬롯에서 발화
- 금일 분량이 이미 성공했다면, 후속 슬롯은 done 마커를 확인하고 즉시
exit 0(헛수고를 하여 로그를 더럽히지 않음) - 이중 실행은
mkdir락(lock)으로 배제 (프로세스 생사 확인을 통해 stale 상태를 자동 회수)
DONE_MARKER="$HOME/.claude/logs/.vault-ingest-done-${TODAY}"
[ -f "$DONE_MARKER" ] && exit 0 # 금일 성공했다면 즉시 종료
증류를 2개(Claude/Codex)로 나눈 것도 같은 이유입니다. 활동이 많은 날은 28시간 분량의 로그 소화 + 기사 리라이트(rewrite)가 40분 프레임 안에 들어오지 않아 연일 타임아웃이 발생했고, 그 원인으로 핫 캐시(hot cache)가 동결되었습니다. 서브 스텝마다 독립적인 마커를 갖게 하여, 한쪽이 타임아웃되더라도 다음 슬롯이 남은 부분만 재시도합니다.
이것이 가장 중요한 배움입니다. claude -p에 「대화 로그로부터 오늘의 브리프(brief)를 작성하라」고 던지면, 노트에 없는 예정된 구속 시간이나 소요 일수를 그럴싸하게 창작하는 경우가 있었습니다. 「M/D~M/D」라는 산문을 보고 「이 기간 내내 구속된다」며 내용을 부풀려 버리는 것입니다.
대책은 두 가지입니다.
① 캘린더의 실체 스냅샷을 정전(ground truth)으로 삼는다. 산문이 아니라, Google Calendar API에서 가져온 시간 정보가 포함된 일정을 ground truth로서 별도 파일로 내려받고, 「이것을 유일한 정전 스케줄로 읽어라」고 지시합니다.
② 취득 실패 시에는 last-known-good을 보존한다. API가 실패하더라도 정전(Ground Truth)을 빈 값으로 덮어쓰지 않습니다. 지난번 성공했을 때의 값을 stale 표시와 함께 남겨둡니다.
if [ -n "$CAL_SRC" ]; then
cp "$CAL_TMP" "$CAL_SNAPSHOT"; cp "$CAL_SNAPSHOT" "$CAL_LASTGOOD"
elif [ -s "$CAL_LASTGOOD" ]; then
...
그리고 프롬프트 측에서도 강력하게 제약합니다. "노트에 없는 정보를 추측으로 추가하지 마라", "날짜·시간에 대한 주장은 확정 정보와 추측을 명시적으로 구분하라". AI에게 장기 기억을 쓰게 하려면, 구조를 통해 창작의 여지를 없애는 수밖에 없습니다.
브리프(Brief)를 날짜와 함께 아카이브할 때, "이 런(run)의 시작 시각보다 새로운 파일만"을 오늘의 성과로 인정합니다.
START_STAMP=$(mktemp ...) # 런 시작 시각 스탬프
# ...생성...
if [ -s "$BRIEF_SRC" ] && [ "$BRIEF_SRC" -nt "$START_STAMP" ]; then
...
이 장치가 없으면, 생성이 타임아웃(timeout)되어도 "전날의 오래된 브리프"를 복사하여 "성공"이라고 기록해 버립니다. 신선도를 mtime(수정 시간)으로 판정함으로써, 실패를 성공으로 위장하지 않도록 하고 있습니다.
launchd에서 보호 영역에 쓰지 못해 발생하는 사이런트 실패(silent failure) → TCC 프리플라이트(preflight)에서 조기 감지 및 FDA 부여
심볼릭 링크(symlink)로 보호 영역 밖으로 빼는 안 → iCloud가 충돌 처리 과정에서 망가뜨림. 불가
슬립(sleep) 동결·이중 실행 → 다중 슬롯 재시도 + mkdir 잠금(lock) + done 마커
28시간 분량이 하나로 되어 있어 프레임 초과로 매일 타임아웃 → 소스별 2개 분할 + 서브 독립 마커
예정된 구속 시간을 창작 → 캘린더 실체를 정전(ground truth)화 + last-known-good + 추측 금지 프롬프트
오래된 결과물을 오늘의 성과로 오인 → 런 시작 스탬프보다 새로운 경우에만 완료 처리
대화 로그(층 1) → Obsidian(층 4)을
매일 밤·최근 28시간 차분만 자동 증류 - 보호 영역은 TCC에 의해 차단됨.
조기 감지로 경고를 보내, 사이런트 실패를 허용하지 않음 - 슬립·이중 실행은
다중 슬롯 재시도 + 잠금 + done 마커로 자기 회복 - AI에게 기억을 쓰게 하려면
정전(ground truth)을 별도로 유지하고, 추측을 구조적으로 금지함 - mtime 신선도 판정으로
실패를 성공으로 위장하지 않게 함
다음에는 이 무인 작업의 우두머리 ―― Claude Code의 자율 루프를 24시간 돌리며, 비용으로 폭주를 막는 법에 대해 쓰겠습니다.
Lily(@bokuwalily)― 개인 개발자. Claude Code로 자동화 기반을 구축하며, iOS 앱과 웹 서비스를 양산하고 있습니다.
- 만든 앱은 포트폴리오에 모아두고 있습니다 📱
- 신착 소식 및 개발 뒷이야기는 X @bokuwalily 에서 발신하고 있습니다 🌍
- OSS: github.com/bokuwalily 🐙
여러분의 ❤️와 공유가 큰 힘이 됩니다!
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기