CLAUDE.md와 훅으로 대화 기록을 다음 세션에 이어 붙이기

약 16분 읽기

매번 처음부터 설명하는 게 아까웠다

AI와의 대화는 물론 유용하지만, 매번 같은 설명을 반복하는 것이 나아지지 않다는 생각이 들었다. 특히 매일같이 사용하는 기술을 설명할 때마다 새로운 사람에게 처음부터 모든 과정을 말해야 했다. 어제 무슨 일을 했고, 프로젝트가 어디까지 진행되었는지를 다시 설명하는 데 시간이 많이 소요됐다. 그래서 이 과정을 자동화할 방법이 필요하다고 느꼈다.

처음에는 단순히 메모를 잘 정리하면 된다고 생각했다. 그러나 메모도 시간이 지나면서 찾기 힘들어지고, 관련 정보가 흩어지면 더욱 혼란스러워졌다. 메모는 메모일 뿐, 필요한 순간에 필요한 맥락을 자동으로 제공해주지 않았다. 그래서 이 문제를 해결하기 위해 새로운 접근법을 모색하게 되었다.

누군가와의 대화나 프로젝트 회의를 기록으로 남기고, 그 기록을 필요할 때 자동으로 불러오게 할 수 있다면 훨씬 효율적일 것 같았다. 이렇게 해서 매번 설명하지 않아도 필요한 정보들이 자동으로 제공되기 때문에, 더 큰 그림을 그릴 수 있을 것 같았다. 이런 생각 끝에, 대화나 작업 내용을 기록으로 남기고 자동으로 맥락을 제공하는 시스템을 설계하기로 했다.

이 시스템을 통해 AI와의 대화가 단순한 일회성 상호작용이 아닌, 연속된 대화가 될 수 있기를 바랐다. AI가 과거의 대화를 기억하고, 이를 기반으로 새로운 대화를 이어갈 수 있는 환경을 만드는 것이 목표였다. 그렇게 해서 매번 처음부터 설명할 필요가 없는, 더 나아지고 생산적인 대화가 가능해질 것이라 믿었다.

3단으로 나눈 구조

대화 로그를 관리하고 활용하기 위한 구조를 3단계로 구분했다. 첫 번째 단계는 대화 로그를 파싱해 옵시디언 노트로 저장하는 것이다. 이는 대화의 기록을 체계적으로 관리하고, 필요할 때 쉽게 접근할 수 있게 한다. 대화가 많아지면 로그가 쌓이게 되는데, 이를 빠르게 정리하고 관리하는 것이 중요했다.

두 번째 단계는 최근 대화를 요약해 CLAUDE.md 파일에 기록하는 것이다. 이 파일은 다음 대화 세션에서 AI가 과거의 맥락을 이해할 수 있도록 돕는다. 파일에 요약을 기록함으로써 불필요한 반복을 줄일 수 있었다. 대화를 이어가는 데 있어 시간을 절약할 수 있는 방법이었다.

세 번째 단계는 메모리를 사실 단위로 쪼개 파일로 관리하는 것이다. 각각의 사실을 독립된 파일로 만들어 필요할 때만 접근할 수 있게 했다. MEMORY.md 파일에 인덱스를 남겨 전체를 읽지 않고도 필요한 정보를 빠르게 찾을 수 있도록 구성했다. 이는 대화의 질을 높이는 데 큰 도움이 되었다.

이 세 가지 단계는 서로 연결되어 각각의 기능을 보완한다. 세션이 끝날 때마다 자동으로 동기화되도록 훅을 설정해두었다. 이런 자동화는 반복 작업을 줄이고, 사용자가 직접 개입하지 않아도 된다는 점에서 큰 이점이 있었다. 단, CLAUDE.md에 기록할 때는 사용자가 작성한 내용을 덮어쓰지 않도록 주의가 필요했다. 초기에는 이런 실수가 있었지만, 마커를 사용해 해결했다.

CLAUDE.md에 써두면 다음 세션이 읽는다

프로젝트의 핵심은 CLAUDE.md에 기록된 과거 세션 요약을 다음 세션에서 자동으로 인식한다는 점이다. 이렇게 하면 매번 같은 내용을 반복 설명할 필요가 없다. 기존 대화 로그를 활용해 프로젝트의 진행 상황을 간단히 요약하고, 이를 CLAUDE.md에 기록해 두는 방식으로 구현했다. 이 방식 덕분에 프로젝트의 맥락을 잃지 않고, 손 안 가게 대화를 이어갈 수 있었다.

CLAUDE.md에 요약된 내용은 단순히 기록에 그치지 않고, 다음 세션의 출발점이 된다. 이전에 작업한 내용을 기반으로 새롭게 논의를 시작할 수 있는 것이다. 이렇게 매일의 대화 흐름이 자연스럽게 이어지도록 설계했다. 프롬프트에 직접 요약을 주입하는 경우와 달리, 파일에 기록된 내용을 활용함으로써 토큰 사용량도 줄일 수 있었다.

이 방법은 특히 프로젝트가 복잡해질수록 유용하다. 여러 프로젝트를 동시에 진행할 때도 각 프로젝트 상태를 정확히 파악할 수 있다. CLAUDE.md에 요약된 과거 대화는 일종의 ‘기억’ 역할을 하면서, 새로운 아이디어와 피드백을 즉각 반영할 수 있게 해준다. 이렇게 해서 프로젝트 진행 상황을 한눈에 파악할 수 있는 이점이 있었다.

하지만 이런 자동화에도 한계는 있었다. CLAUDE.md에 너무 많은 정보를 기록하게 되면 오히려 필요한 내용을 찾기 어려워질 수 있다. 그래서 가능한 한 요약된 내용을 간결하게 정리하고, 필요한 경우 세부사항은 별도로 관리하는 방법을 택했다. 이렇게 해서 프로젝트의 전체 흐름을 빠르게 관리할 수 있었다.

손으로 쓴 내용을 날린 실수

처음에는 CLAUDE.md 파일 전체를 덮어쓰는 방식으로 자동화 기능을 구현했다. 이렇게 하면 과거 대화에서 자동으로 요약된 내용이 잘 반영될 것이라 생각했다. 하지만 프로젝트 규칙이나 개인적으로 추가한 중요한 메모까지 덮어쓰는 치명적인 문제가 발생했다. 그 결과, 복원할 수 없는 중요한 내용을 잃게 되어 큰 차질을 빚었다. 이를 해결하기 위해 마커로 자동 주입 블록을 감싸고 그 안의 내용만 교체하도록 코드를 다시 작성해야 했다.

자동 블록을 교체하는 과정에서 파일 전체를 다루지 않고 필요한 부분만 수정하려면, 특정한 기준을 사용하여 텍스트를 분리하고 주입할 위치를 정확히 설정해야 했다. 이렇게 해서 파일의 기존 내용을 최대한 보존하면서도 자동화의 속도를 유지할 수 있었다. 이러한 방식은 원래 손으로 쓴 내용과 자동으로 추가되는 요약 정보 간의 경계를 명확히 하여 충돌을 방지한다.

import re
from pathlib import Path

BEGIN = "<!-- AUTO-CONTEXT:BEGIN -->"
END   = "<!-- AUTO-CONTEXT:END -->"

def upsert_context(md_path: Path, block_body: str) -> None:
    """마커 사이만 갈아끼운다.

    처음엔 파일을 통째로 다시 썼다가 손으로 정리해둔 프로젝트 규칙을
    날렸다. 그 뒤로 마커 방식으로 바꿨다.
    """
    new_block = f"{BEGIN}\n{block_body.rstrip()}\n{END}"
    old = md_path.read_text(encoding="utf-8") if md_path.exists() else ""

    if BEGIN in old and END in old:
        out = re.sub(
            re.escape(BEGIN) + r".*?" + re.escape(END),
            lambda _: new_block,          # 치환 문자열의 백슬래시 해석 방지
            old, flags=re.S,
        )
    else:
        out = (old.rstrip() + "\n\n" + new_block).lstrip()

    md_path.write_text(out.rstrip() + "\n", encoding="utf-8")

def build_block(recent) -> str:
    lines = ["## 과거 AI 대화 요약 (자동 주입)", ""]
    for note in recent[:5]:
        lines += [
            f"- [{note.title}] ({', '.join(note.topics)})",
            f"  Q: {note.first_user[:110]}",
            f"  A: {note.first_assistant[:110]}",
            "",
        ]
    return "\n".join(lines)

손으로 쓴 내용을 덮지 않고 자동 블록만 교체

코드를 수정한 이후, CLAUDE.md 파일의 손으로 쓴 내용은 더 이상 사라지지 않았다. 이 방식은 자동화된 시스템을 사용하는 동안에도 사용자가 직접 작성한 중요한 정보가 안전하게 보존되도록 한다. 덕분에 지금은 프로젝트 요약이 잘 유지되면서도, 수작업으로 추가한 메모와 프로세스가 손상되지 않은 채 데이터를 관리할 수 있게 되었다.

메모리를 사실 단위로 쪼갠 이유

대화를 오랫동안 기록하다 보니, 점점 데이터가 방대해졌다. 모든 대화를 하나의 파일에 담게 되면 찾고자 하는 정보를 손 안 가게 검색하기 어려워졌다. 특히, 프로젝트가 커질수록 필요한 정보와 그렇지 않은 정보가 뒤섞이면서 중요한 사실을 놓치는 경우가 발생했다. 이런 문제를 해결하기 위해 메모리를 사실 단위로 쪼개서 각각의 파일에 저장하기로 했다.

이렇게 함으로써 각 파일이 독립적인 정보 단위로 관리되기 때문에, 불필요한 정보를 걸러내고 필요한 정보만 빠르게 접근할 수 있다. 또한, 이 구조는 메모리 확장에 유연하게 대처할 수 있도록 한다. 새로운 사실이 추가되어도 기존 파일에 영향을 주지 않고, 필요한 경우 특정 파일만 업데이트하면 되기 때문이다.

MEMORY_DIR = Path.home() / ".claude/memory"
INDEX      = MEMORY_DIR / "MEMORY.md"

TEMPLATE = """---
name: {slug}
description: {desc}
metadata:
  type: {mtype}
---

{body}
"""

def save_memory(slug: str, desc: str, mtype: str, body: str, links=()):
    """사실 하나 = 파일 하나. 전부 읽지 않고 description 으로 고른다."""
    assert mtype in ("user", "feedback", "project", "reference")
    if links:
        body += "\n\n관련: " + " ".join(f"[[{l}]]" for l in links)

    (MEMORY_DIR / f"{slug}.md").write_text(
        TEMPLATE.format(slug=slug, desc=desc, mtype=mtype, body=body.strip()),
        encoding="utf-8")

    # 인덱스에는 한 줄만. 이것만 항상 컨텍스트에 올라간다
    line = f"- [{desc.split('.')[0][:40]}]({slug}.md) — {desc}"
    idx = INDEX.read_text(encoding="utf-8") if INDEX.exists() else "# Memory Index\n\n"
    if slug not in idx:
        INDEX.write_text(idx.rstrip() + "\n" + line + "\n", encoding="utf-8")

def relevant(query: str, limit: int = 5):
    """description 만 훑어서 후보를 고르고, 고른 것만 본문을 읽는다"""
    hits = []
    for f in MEMORY_DIR.glob("*.md"):
        if f.name == "MEMORY.md":
            continue
        head = f.read_text(encoding="utf-8")[:400]
        m = re.search(r"^description:\s*(.+)$", head, re.M)
        if m and any(w in m.group(1) for w in query.split()):
            hits.append(f)
    return hits[:limit]

메모리는 사실 하나당 파일 하나 + 인덱스만 상주

파일 구조를 통해 텍스트 데이터 변경은 최소화하고, 각 파일에는 frontmatter를 이용해 type과 description을 포함했다. description만 보고 관련 있는 것만 찾을 수 있다는 점이 유용했다. 덕분에 전체 파일을 읽지 않고도 필요한 정보를 빠르게 찾을 수 있었고, 잘 맞는 데이터 관리에 기여했다. 결과적으로, 정보의 검색과 관리가 훨씬 간결해졌고, 프로젝트 진행의 속도를 높이는 데 큰 도움이 됐다.

훅으로 사람 손을 떼기

프로젝트를 진행하면서 매번 데이터를 수동으로 정리하는 것이 번거로웠다. 세션이 끝날 때마다 수작업으로 과거 데이터를 정리하고, 이를 바탕으로 다음 세션에 반영해야 하는 과정은 나아지지 않았다. 직접적으로 걸리는 시간도 문제였지만, 사람의 개입이 필요한 단계에서 실수가 발생할 위험도 있었다. 이런 실수를 줄이기 위해 세션이 종료되면 자동으로 데이터를 정리하도록 훅을 설계하게 됐다.

데이터를 자동으로 정리하는 과정에서 가장 짚어둘 건 시스템이 사람의 손길 없이도 정확하게 작동해야 한다는 것이었다. 특히, 기존 데이터를 덮어쓰는 일이 없도록 주의해야 했다. 프로젝트 규칙이나 중요한 정보가 손실되는 상황을 방지하기 위해서는 확실한 자동화 프로세스가 필요했다. 이러한 요구에 따라, 세션 종료 시 클로징 훅을 사용해 데이터를 자동으로 정리하도록 시스템을 구축했다.

{
  "hooks": {
    "SessionEnd": [
      {
        "matcher": "*",
        "hooks": [
          {
            "type": "command",
            "command": "python3 ~/scripts/obsidian_sync.py --quiet --source=claude",
            "timeout": 60
          }
        ]
      }
    ]
  }
}

세션이 끝나면 자동으로 돌게 훅 걸기

처음부터 모든 것을 자동화하는 것은 쉽지 않았다. 간단하게 생각하면 세션 종료 시점에만 작업을 걸어두면 될 것 같았지만, 그 과정에서 놓칠 수 있는 부분들이 있었다. 예를 들어, 특정 파일에 대한 접근권한 문제나 파일의 형식적 일관성을 유지하는 문제 등이 있었다. 이런 문제를 해결하기 위해서는 중간중간 조건문을 추가하고, 오류 발생 시에도 시스템이 안정적으로 복구될 수 있도록 설계해야 했다. 이러한 과정을 통해 파일 형식과 내용의 일관성을 유지하면서 자동화 프로세스가 원활하게 작동할 수 있게 됐다.

쓰면서 달라진 점

이 프로젝트를 진행하면서 작업 방식에 몇 가지 변화를 주게 됐다. 처음 시작했을 때는 자동화된 시스템이 모든 것을 해결해 줄 거라고 생각했다. 하지만 차츰 시스템이 흘러가는 데 있어서 작은 오류들이 누적되는 것을 발견했다. 이로 인해 프로세스를 세세하게 점검하고 수동으로 개입해야 하는 상황이 잦아졌다. 결국 모든 것을 자동화할 수 없다는 것을 깨닫고, 시스템과 수동 작업 간의 균형점을 찾아야 했다.

또한, 처음에는 각 단계를 빈틈없이 세분화하는 것이 중요하다고 생각했다. 하지만 실제로는 너무 세분화된 구조가 오히려 복잡성을 증가시켰다. 그래서 일부 단계를 합치고 간소화했다. 간소화 덕분에 관리가 더 쉬워지고, 예기치 않은 오류도 줄일 수 있었다. 결과적으로 시스템이 더 직관적으로 작동하게 되었다.

프로젝트를 진행하면서 다양한 피드백을 받았다. 그 중 일부는 시스템의 효율성과 관련된 것들이었다. 사용자 경험을 좀 더 부드럽게 만들기 위해 인터페이스를 개선하고, 필요 없는 기능들을 제거했다. 이러한 과정을 통해 실제 사용자들에게 더 나은 환경을 제공할 수 있게 됐다. 피드백을 반영하는 과정에서도 시행착오를 겪었지만, 그만큼 만족스러운 결과를 얻었다.

마지막으로, 협업의 필요성을 다시 느꼈다. 여러 명이 함께 참여하는 프로젝트다 보니, 커뮤니케이션과 문서화가 더 중요해졌다. 특히, 프로젝트의 흐름을 모두가 이해할 수 있도록 문서를 체계적으로 정리하는 데 주력했다. 이러한 노력 덕분에 팀원들 간의 이해가 높아지고, 프로젝트 진행 속도도 빨라졌다.

함께 볼 글

이 글과 이어지는 내용은 아래에 정리해뒀다.

황민

황민 (Hwang Min)

IT·RPA·AI 분야 개발자. 웹앱 개발, UiPath RPA, n8n 자동화 실무 경력 4년. AI·금융·IT 트렌드를 현장 개발자 시각으로 분석합니다.

댓글 남기기

𝕏fin