OpenClaw(오픈클로) 스타일 메모리 시스템 구현하기 — AI 비서가 기억하는 법
OpenClaw의 파일 기반 메모리를 분석하고, Claude Code 텔레그램 봇에 적용했다. MEMORY.md + 일일 노트로 중요한 것만 기억한다.
컨텍스트 윈도우가 0인 비서는 비서가 아니다
지난 편에서 텔레그램 봇을 만들었다. 동작은 했다. 그런데 치명적인 문제가 하나 있었다. 이 봇은 3초 전 대화를 기억하지 못한다.
“아까 그 코드 수정해줘” → “무슨 코드요?”
매 메시지가 첫 만남이다. 이전 맥락이 전혀 유지되지 않는다. 이건 AI “비서”가 아니라 매번 새로 부르는 콜센터다.
문제의 원인은 명확하다. Claude Code의 -p 모드는 기본적으로 stateless다. 매 호출이 독립적인 세션이므로 이전 대화 기록이 자동으로 전달되지 않는다. 메모리 시스템을 직접 구현해야 한다.
OpenClaw의 메모리 아키텍처를 분석했다
요즘 주목받고 있는 OpenClaw 프로젝트의 메모리 시스템을 뜯어봤다. 벡터 DB나 RAG 파이프라인 같은 복잡한 구조를 예상했는데, 핵심은 의외로 단순했다.
파일이 곧 메모리다. 대화 전체를 저장하는 것이 아니라, AI가 “이건 기억할 만하다”고 판단한 정보만 마크다운 파일에 기록한다.
OpenClaw 메모리 구조
1
2
3
MEMORY.md ← 장기 기억 (선호도, 결정사항)
memory/2026-02-06.md ← 오늘 일일 노트
memory/2026-02-05.md ← 어제 일일 노트
동작 원리는 4단계로 정리된다:
- 세션 시작 →
MEMORY.md+ 오늘/어제 일일 노트만 로드 - 대화 중 → AI가 “이건 기억할 만하다”고 판단하면 자동 기록
- 컨텍스트 포화 → 자동 저장 후 메모리 플러시
- 다음 세션 → 저장된 파일을 읽어서 맥락 복원
벡터 DB? RAG 파이프라인? 없다. 그냥 마크다운 파일이다. 사람이 직접 열어서 편집할 수도 있다. 디버깅이 가능한 메모리 시스템 — 이 투명성이 OpenClaw의 핵심 설계 철학이다.
나는 이 접근법이 개인 비서 수준에서 최적이라고 판단하고, 바로 적용하기로 했다.
v1에서 v2로: 전체 저장 vs 선택적 저장
v1: 대화 전체를 JSON으로 덤프
처음 시도한 방식은 모든 대화를 그대로 저장하는 것이었다.
1
2
3
4
5
[
{"role": "user", "content": "블로그 배포해줘", "timestamp": "..."},
{"role": "assistant", "content": "배포 완료...", "timestamp": "..."},
...40개 메시지 전부 저장
]
문제점이 바로 드러났다. 토큰 낭비가 심하고, “ㅋㅋ 고마워” 같은 불필요한 대화도 전부 포함되고, 파일 사이즈는 기하급수적으로 커졌다. 20개 메시지만 유지해도 프롬프트 길이가 전체 컨텍스트의 상당 부분을 차지했다.
v2: OpenClaw 스타일 — 중요한 정보만 선택적 저장
1
2
3
4
5
data/
├── MEMORY.md ← "사용자는 conda 선호", "ENFP 개발자"
└── daily/
├── 2026-02-07.md ← 오늘 뭐 했는지
└── 2026-02-06.md ← 어제 뭐 했는지
v1 대비 프롬프트 길이가 약 1/5로 줄었다. 동일한 정보량을 훨씬 적은 토큰으로 전달하는 구조다. 이 전환의 핵심 원리는 간단하다: “뭘 했는지”보다 “뭘 기억해야 하는지”가 중요하다.
구현 상세
1. [MEMO] 태그 시스템 — AI가 스스로 기억할 내용을 선별한다
시스템 프롬프트에 다음 지시를 추가했다:
1
2
중요: 대화 중 기억할 만한 정보가 있으면 답변 마지막에 아래 형식으로 알려줘:
[MEMO] 기억할 내용
Claude가 응답을 생성할 때, 기억할 가치가 있다고 판단한 정보에 [MEMO] 태그를 붙인다. 봇이 이를 파싱해서 자동으로 저장하고, 사용자에게는 태그가 제거된 깨끗한 응답만 전달한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def extract_and_save_memos(response: str) -> str:
lines = response.split("\n")
clean_lines = []
memos = []
for line in lines:
if line.strip().startswith("[MEMO]"):
memo = line.strip()[6:].strip()
memos.append(memo)
else:
clean_lines.append(line)
for memo in memos:
# "선호", "항상" 같은 키워드 → 장기 기억
# 그 외 → 일일 노트
if any(kw in memo for kw in ["선호", "항상", "기본", "설정"]):
append_memory(memo)
else:
append_daily_note(memo)
return "\n".join(clean_lines).strip()
2. 장기 기억 (MEMORY.md)
사용자 선호도, 프로필 정보, 프로젝트 결정사항 등 세션 간에 유지되어야 하는 정보를 저장한다. 매 세션 시작 시 자동으로 로드된다.
1
2
3
4
5
# WonderX 장기 기억
- [2026-02-06 17:30] 사용자는 conda 환경을 선호한다
- [2026-02-06 17:45] 블로그 프레임워크: Astro + GitHub Pages
- [2026-02-06 18:00] 댓글 시스템: Disqus (게스트 댓글 가능)
수동으로도 추가할 수 있다:
1
/remember conda 환경 이름은 wonderx-bot
3. 일일 노트 (daily/YYYY-MM-DD.md)
해당 날짜에 수행한 작업을 기록한다. 로드 범위는 오늘 + 어제 노트로 한정된다. 3일 전 노트는 자동으로 로드하지 않는다 — 필요하면 장기 기억에 승격시키는 구조다.
1
2
3
4
5
6
# 2026-02-06 일일 노트
- [17:00] 사용자: 텔레그램 봇 만들어줘
- [17:15] 블로그 SEO 최적화 작업 시작
- [17:30] 카테고리 사이드바 추가
- [18:00] OpenClaw 스타일 메모리 시스템 구현
4. 프롬프트 조합 구조
Claude에게 전송되는 최종 프롬프트는 다음과 같은 구조로 조립된다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[시스템 지시]
너는 WonderX AI 비서야. 게으른 개발자의 비서...
---
[장기 기억]
# WonderX 장기 기억
- conda 선호, Astro 블로그, Disqus 댓글...
[최근 노트]
# 2026-02-06 일일 노트
- 텔레그램 봇 구현, 메모리 시스템 구현...
---
[현재 요청]
아까 만든 메모리 시스템에 검색 기능 추가해줘
이 구조의 효과는 명확하다. Claude가 오늘 어떤 작업을 했는지 아는 상태에서 응답하기 때문에, “아까 그 코드”가 무엇을 지칭하는지 추론할 수 있다. stateless 문제가 해결된다.
OpenClaw vs WonderX Bot 비교 분석
| OpenClaw | WonderX Bot | |
|---|---|---|
| 메모리 방식 | 마크다운 파일 | 마크다운 파일 (동일) |
| 자동 저장 | memory flush + AI 판단 | [MEMO] 태그 + AI 판단 |
| 수동 저장 | “이거 기억해” 자연어 | /remember 명령어 |
| 비용 | API 토큰 과금 (Heartbeat에 하룻밤 $20) | Max 플랜 포함 ($0) |
| 검색 | SQLite + 벡터 + BM25 하이브리드 | 오늘/어제만 로드 (심플) |
| 복잡도 | TypeScript 39개 파일 | Python 1개 파일 (memory.py) |
OpenClaw은 시맨틱 검색, 임베딩, SQLite 인덱스까지 갖춘 완성도 높은 시스템이다. 하지만 개인 비서 수준에서 이 모든 기능이 필요한가? 나의 판단은 “아직 아니다”였다.
오늘/어제 노트 + 장기 기억만으로 일상적인 개발 지원 시나리오의 대부분을 커버할 수 있다. YAGNI(You Ain’t Gonna Need It) 원칙이다. 나중에 메모리가 수백 건을 넘어서 검색이 필요해지면 그때 SQLite나 벡터 검색을 붙이면 된다.
필요하지 않은 기능을 미리 구현하는 것은 기술 부채를 스스로 만드는 행위다.
새 명령어
| 명령 | 기능 |
|---|---|
/memory | 현재 기억 상태 조회 |
/remember 내용 | 장기 기억에 수동 저장 |
/clear | 장기 기억 초기화 |
삽질 기록
모호한 지시는 모호한 결과를 만든다
처음에 시스템 프롬프트를 “기억해야 할 것을 저장해줘”라고 대충 작성했다. 결과는 예측 가능했다. Claude가 무엇을 저장해야 할지 판단하지 못해서, 아무것도 저장하지 않거나 “사용자가 안녕이라고 했다” 같은 의미 없는 정보를 기록했다.
[MEMO]라는 명확한 포맷을 정의하고, 구체적인 예시를 제공하자 즉시 정확도가 올라갔다. AI에게 모호한 지시를 내리면 모호한 결과가 나온다 — 프롬프트 엔지니어링의 기본 원칙이지만, 실전에서는 의외로 자주 잊게 된다.
장기 기억과 일일 노트의 분류 정확도
- “사용자는 conda를 선호한다” → 장기 기억 (정답)
- “오늘 블로그를 배포했다” → 일일 노트 (정답)
이 분류가 잘못되면 장기 기억이 일시적 정보로 오염된다. 현재는 키워드 기반(“선호”, “항상”, “기본”, “설정” 등)으로 분류하고 있으며, 정확도는 약 80% 수준이다. 나머지 20%는 수동으로 보정한다.
향후 개선 방향으로는 Claude 자체에 분류를 위임하는 방법이 있다. [MEMO:long-term] vs [MEMO:daily] 같은 태그를 AI가 직접 붙이도록 하면 키워드 매칭보다 정확한 분류가 가능할 것이다.
다음 편 예고
- 스케줄링 & Heartbeat: 매일 아침 자동 상태 알림, 블로그 빌드 체크 자동화
- OpenClaw에서 가장 인기 있는 기능이다. 이게 동작하면 “요청할 때만 일하는 봇”에서 “스스로 일을 찾아서 하는 비서”로 레벨업한다
메모리가 없는 AI는 도구다. 메모리가 있는 AI는 동료가 된다.
전체 코드는 비공개 레포에 있다. 궁금한 건 x@wonderx.co.kr로.


