Memory Architecture — 3-Tier Persistence
메모리 아키텍처 — 3계층 영속성
CLAUDE.md → MEMORY.md → JSONL 트랜스크립트 — 3계층 메모리 구조를 이해하면 세션이 바뀌어도 에이전트가 맥락을 잃지 않는다.
Overview
에이전트의 가장 큰 약점은 기억이 없다는 것이다. 새 세션을 시작할 때마다 모든 컨텍스트가 사라진다. 이것은 밤 교대 근무를 하는 엔지니어가 인수인계 없이 일하는 것과 같다.
Claude Code는 3계층 메모리 구조로 이 문제를 해결한다. Layer 1: CLAUDE.md — 프로젝트 관례, 항상 로드됨. Layer 2: MEMORY.md — 진화하는 세션 상태, 첫 200줄 자동 로드. Layer 3: JSONL 트랜스크립트 — 감사 불가능한 세션 기록, 필요시 검색.
Anthropicr의 내부 연구에서 발견된 패턴: JSON 형식의 feature list는 Markdown보다 에이전트가 부적절하게 수정하는 경우가 적다. 구조화된 데이터가 모델의 '창의적 개입'을 억제한다. 이것이 장기 실행 태스크에서 JSON 파일을 중간 상태 저장소로 쓰는 이유다.
- 3계층 메모리(CLAUDE.md, MEMORY.md, JSONL) 각각의 역할과 수명을 설명할 수 있다
- MEMORY.md의 200줄/25KB 자동 로딩 메커니즘을 설명할 수 있다
- autoDream이 무엇이고 왜 필요한지 설명할 수 있다
- 장기 실행 멀티세션 에이전트를 위한 Dual-Agent Architecture를 구현할 수 있다
Sections
3계층 메모리 구조
| 계층 | 파일 | 수명 | 로딩 방식 | 용도 |
|---|---|---|---|---|
| 1. Procedural | CLAUDE.md | 영구 | 항상 자동 | 프로젝트 관례, 결정 사항 |
| 2. Episodic | MEMORY.md | 세션 간 | 첫 200줄 자동 | 아키텍처 상태, 진행 상황 |
| 3. Working | JSONL 트랜스크립트 | 세션 내 | 명시적 요청 | 도구 호출 기록, 세션 감사 |
MEMORY.md 자동 로딩: ~/.claude/projects/{hash}/memory/MEMORY.md의 첫 200줄(또는 25KB)이 모든 세션 시작 시 자동으로 로드된다. 이 파일은 가장 중요한 현재 상태를 담아야 한다.
인지과학 기반 메모리: Nemori 연구(arXiv:2508.03341)에 따르면 최적의 에이전트 메모리는 에피소드(episodes) → 시맨틱 지식 → 절차적 스킬 순으로 통합된다. autoDream이 이 통합을 자동화한다.
MEMORY.md 구조 — 무엇을 기록해야 하는가
MEMORY.md는 '현재 상태의 스냅샷'이다. 자주 바뀌는 정보가 아니라 세션이 바뀌어도 Claude가 알아야 할 아키텍처 결정을 담는다.
실전 MEMORY.md 템플릿:
# MEMORY.md — 최종 업데이트: 2026-06-17
## Architecture State
- Auth: Clerk (NOT NextAuth — 2026-03 마이그레이션, ADR #003)
- DB: PostgreSQL via Prisma (raw SQL 금지 — SQL injection 이력, ADR #007)
- Deploy: Vercel preview(PR), production(main 머지)
- Testing: Vitest unit, Playwright E2E, 최소 80% coverage
## Current Work
- 작업 중: 결제 플로우 리팩토링 (tickets/PAY-123)
- 블로커: Stripe webhook 타임아웃 문제 (see docs/incidents/2026-06-15.md)
- 다음: 구독 갱신 로직 구현
## Key Decisions (recent)
- 2026-06-10: Redis 캐시 → in-memory 로 교체 (비용 이유)
- 2026-06-08: pagination을 cursor 기반으로 변경 (성능 이슈)
## More Detail On-Demand
- [user_profile.md](user_profile.md)
- [feedback_patterns.md](feedback_patterns.md)
경험칙: MEMORY.md는 새 팀원이 첫날 읽어야 할 '현재 상태 브리핑'이다. 과거 기록이 아니라 지금 이 순간의 상태를 담는다.
autoDream — 자동 메모리 통합
유출된 소스코드에서 확인된 기능: autoDream은 Claude Code의 자동 메모리 가비지 컬렉터다.
- 유휴 상태에서 백그라운드 실행
- MEMORY.md에서 중복 제거, 모순 해결, 오래된 컨텍스트 정리
- 에피소드 메모리 → 의미적 지식으로 통합
실용적 의미: 적극적으로 MEMORY.md를 관리하지 않아도 autoDream이 자동으로 정리한다. 하지만 중요한 정보는 명시적으로 표시해두면 autoDream이 보존한다.
KAIROS: 유출 코드에서 150회 이상 참조된 자율 에이전트 시스템. 5분 크론 사이클, GitHub 웹훅 구독, /dream 스킬로 야간 메모리 정리. 'Your own memory is a hint — verify against actual codebase before acting'이라는 지침이 내장되어 있다.
장기 실행 멀티세션 패턴
Anthropic 엔지니어링 팀이 공개한 200개 이상의 feature를 여러 세션에 걸쳐 구현하는 패턴:
Dual-Agent Architecture:
초기화 에이전트 (첫 세션만):
feature_list.json생성 — 200개+ feature를 JSON 형식으로 (Markdown 아닌 이유: 모델이 부적절하게 편집하는 것을 방지)init.sh생성 — 개발 환경 시작 스크립트claude-progress.txt생성 — 세션 작업 로그- 기준 git commit
코딩 에이전트 (이후 모든 세션):
1. pwd 확인
2. git log 읽기
3. MEMORY.md/claude-progress.txt 읽기
4. feature_list.json 읽기 (미완료 feature 파악)
5. init.sh로 개발 서버 시작
6. 정확히 1개 feature만 구현 (컨텍스트 부족 방지)
7. E2E 테스트 (Puppeteer MCP 등)
8. feature_list.json 업데이트 (pass: true)
9. 디스크립티브 커밋
10. 진행 로그 업데이트
JSON feature list의 중요성: JSON 구조는 모델이 '창의적으로 수정'하는 것을 억제한다. Markdown feature list는 종종 모델이 마음대로 체크하거나 삭제했다.
3계층 메모리를 병원 차트 시스템으로 생각해보자.
CLAUDE.md는 표준 치료 프로토콜 — 어떤 의사가 담당해도 따라야 할 병원 규칙. 바뀌지 않는 기반.
MEMORY.md는 현재 환자 차트 — 오늘 이 환자의 상태, 진행 중인 치료, 주의사항. 교대 근무 때마다 새 의사가 이것을 읽는다. 200줄 이내로 핵심만.
JSONL 트랜스크립트는 상세 처방 기록 — 언제 무슨 약을 줬는지, 어떤 검사를 했는지 상세 기록. 감사나 분쟁 시 꺼내보는 것.
autoDream은 야간 당직 의사 — 모순되는 처방을 정리하고, 오래된 기록을 아카이브하고, 다음 교대를 위해 차트를 정리한다.
교대 근무 인수인계 없이 치료하는 의사 = 세션마다 처음부터 시작하는 에이전트. MEMORY.md는 그 인수인계 문서다.
현재 프로젝트에 MEMORY.md 구조를 자동 생성하고 관리하는 스크립트. 세션 시작 시 자동으로 MEMORY.md를 업데이트한다.
#!/usr/bin/env python3
"""memory-manager.py — MEMORY.md 자동 생성 및 관리"""
import json
import subprocess
from pathlib import Path
from datetime import date
def get_project_hash():
cwd = subprocess.run(['pwd'], capture_output=True, text=True).stdout.strip()
return cwd.replace('/', '-').lstrip('-')
def get_git_info():
info = {}
try:
info['branch'] = subprocess.run(
['git', 'branch', '--show-current'], capture_output=True, text=True
).stdout.strip()
info['recent_commits'] = subprocess.run(
['git', 'log', '--oneline', '-5'], capture_output=True, text=True
).stdout.strip()
info['dirty_count'] = len(subprocess.run(
['git', 'status', '--short'], capture_output=True, text=True
).stdout.strip().split('\n'))
except:
pass
return info
def setup_memory_structure(project_dir: str = '.'):
"""프로젝트에 memory 구조 초기화"""
proj_hash = get_project_hash()
memory_dir = Path.home() / '.claude' / 'projects' / proj_hash / 'memory'
memory_dir.mkdir(parents=True, exist_ok=True)
memory_index = memory_dir / 'MEMORY.md'
if not memory_index.exists():
git = get_git_info()
content = f"""# MEMORY.md — {date.today()}
## Architecture State
<!-- 핵심 기술 결정, 마이그레이션 이력 -->
- Stack: [여기에 기술 스택]
- Branch: {git.get('branch', 'unknown')}
## Current Work
<!-- 진행 중인 작업, 블로커 -->
- 작업 중: [여기에 현재 태스크]
## Key Decisions (recent)
<!-- 최근 아키텍처 결정 -->
## More Detail On-Demand
- [user_profile.md](user_profile.md)
- [feedback_patterns.md](feedback_patterns.md)
"""
memory_index.write_text(content)
print(f"✅ MEMORY.md 생성: {memory_index}")
else:
# 날짜 업데이트
content = memory_index.read_text()
lines = content.split('\n')
lines[0] = f"# MEMORY.md — {date.today()}"
memory_index.write_text('\n'.join(lines))
print(f"✅ MEMORY.md 날짜 업데이트: {memory_index}")
# line count 체크
line_count = len(memory_index.read_text().split('\n'))
if line_count > 200:
print(f"⚠️ MEMORY.md {line_count}줄 — 200줄 이내 권장 (현재 모두 로드됨)")
print(f"📍 Memory directory: {memory_dir}")
return memory_dir
def create_feature_list(features: list, output_path: str = 'feature_list.json'):
"""장기 실행 태스크를 위한 JSON feature list 생성"""
feature_list = [
{
'id': i + 1,
'category': f.get('category', 'general'),
'description': f.get('description', ''),
'verification_steps': f.get('verification_steps', []),
'passes': False,
'notes': ''
}
for i, f in enumerate(features)
]
with open(output_path, 'w') as file:
json.dump({'features': feature_list, 'total': len(features), 'completed': 0}, file, indent=2, ensure_ascii=False)
print(f"✅ {output_path} 생성 ({len(features)} features)")
print("⚠️ JSON 형식 사용: 모델이 부적절하게 수정하는 것을 방지")
if __name__ == '__main__':
memory_dir = setup_memory_structure()
print(f"\n다음 세션에서 Claude Code는 자동으로 MEMORY.md를 로드합니다.")
print(f"첫 200줄이 항상 컨텍스트에 포함됩니다.") setup_memory_structure()로 현재 프로젝트의 memory 디렉토리를 초기화하고 MEMORY.md 파일을 생성한다. create_feature_list()는 장기 실행 태스크를 JSON 형식으로 저장해서 여러 세션에 걸쳐 진행 상황을 추적한다.
✅ 시니어가 보는 것
- 모든 프로젝트에 MEMORY.md 파일이 있고 최신 상태로 유지되는가
- MEMORY.md가 200줄 이내로 관리되는가
- 장기 태스크에 JSON feature list를 사용하는가
- 세션 시작 루틴(pwd → git log → MEMORY.md 읽기)이 정착되어 있는가
⚠️ 레드 플래그
- 세션마다 같은 컨텍스트를 반복해서 설명
- MEMORY.md가 없어서 에이전트가 이전 결정을 모름
- Markdown feature list (모델이 마음대로 체크하거나 삭제)
🎤 예상 인터뷰 질문
- MEMORY.md의 200줄 자동 로딩 제한이 있을 때 중요한 정보를 어떻게 관리하는가?
- Anthropic이 장기 실행 태스크에서 Markdown 대신 JSON feature list를 권장하는 이유는?
- autoDream이 MEMORY.md에서 삭제하면 안 되는 정보를 보존하는 방법은?
Key Takeaways
3계층 메모리
CLAUDE.md(영구) → MEMORY.md(세션 간) → JSONL(세션 내). 각각 다른 역할과 수명을 갖는다.
200줄 자동 로딩
MEMORY.md 첫 200줄(또는 25KB)이 항상 자동으로 세션에 포함된다.
JSON > Markdown
feature list는 JSON으로 저장하라. 모델이 Markdown 체크리스트를 부적절하게 수정한다.
autoDream = 메모리 GC
유휴 시 자동 실행되어 MEMORY.md를 정리하고 중복을 제거한다.
Dual-Agent Architecture
초기화 에이전트(1회) + 코딩 에이전트(반복). 세션마다 init.sh + progress 읽기로 시작.
git = 메모리 백업
커밋 메시지와 로그가 documentation이자 recovery 메커니즘이다.
세션 시작 루틴
pwd → git log → MEMORY.md → feature_list.json → init.sh — 이 순서를 CLAUDE.md에 명시하면 에이전트가 자동으로 따른다.
중요 정보 표시
MEMORY.md에서 autoDream이 보존해야 할 정보는 명시적으로 '중요' 표시를 해두면 정리 대상에서 제외된다.