본문으로 건너뛰기

© 2026 Molayo

Qiita헤드라인2026. 06. 18. 08:39

Claude API로 AI 에이전트의 장기 기억을 구현하는 완전 가이드

요약

Claude API를 활용하여 AI 에이전트의 장기 기억을 구현하는 아키텍처를 소개합니다. 단순 대화 로그 저장이 아닌, 정보를 증류하여 Markdown 파일과 git으로 관리하는 3층 구조의 설계 원칙을 다룹니다.

핵심 포인트

  • 단순 로그 저장은 비용 증가와 컨텍스트 압박, 노이즈 문제를 야기함
  • 증류된 상태(distilled state)를 위해 1파일 1사실 원칙 적용
  • Markdown 파일과 git을 활용한 기억의 자산화 및 상태 관리
  • 프롬프트 캐싱을 통한 API 비용 최적화 전략

우리는 여러 AI 에이전트를 업무(블로그 운영·SNS·배포·QC)에 상주시켜 6개월 이상 운용하고 있습니다. 처음에 부딪힌 벽은 "AI는 다음 세션에서 모든 것을 잊어버린다"는 것이었습니다. 그리고 다음에 부딪힌 벽은 "대화 이력을 저장해도 기억이 되지 않는다"는 것이었습니다.

이 기사에서는 실운용을 통해 정립된 장기 기억 아키텍처를 Claude API(Python SDK) 구현 코드와 함께, 실제로 겪었던 실패와 해결책을 포함하여 해설합니다.

  • 대상 독자: AI 에이전트에게 "이전 작업의 연속"을 시키고 싶은 사람
  • 사용하는 것:
    anthropic Python SDK, Markdown 파일, git
  • 하지 않는 것: 벡터 DB 필수론 (후술하겠지만, 운용 초기에는 필요하지 않았습니다)

처음에는 단순하게 "대화 로그를 전부 저장해서 다음에 읽어오면 된다"라고 생각했습니다. 하지만 이는 세 가지 이유로 파탄 납니다.

1. 컨텍스트 윈도우(Context Window)와 비용

1세션의 대화 로그는 가볍게 수만 토큰에 달합니다. 10세션 분량을 매번 주입하면 입력 비용이 선형적으로 불어나고, 정작 중요한 작업용 컨텍스트가 압박을 받습니다.

2. 로그에는 "결론"과 "과정"이 혼재되어 있다

로그의 대부분은 시행착오의 과정입니다. 다음 세션에서 정말로 필요한 것은 "최종적으로 어떻게 결정했는가", "무엇을 두 번 다시 하지 않을 것인가"뿐입니다. 과정을 매번 다시 읽는 것은 노이즈입니다.

3. 검증되지 않은 정보가 섞인다

대화 중의 가설(나중에 틀린 것으로 판명된 것)도 로그에는 남습니다. 이를 그대로 주입하면 AI가 오래된 가설을 사실로 취급하는 사고가 발생합니다. 실제로, 철회된 설계 방침을 다른 세션의 AI가 "결정 사항"으로 참조하여 재작업이 발생하는 일이 있었습니다.

즉, 장기 기억에 필요한 것은 이력이 아니라, **증류된 상태(distilled state)**입니다.

도달한 결론은 다음과 같은 3층 구성입니다. 데이터 스토어는 "단순한 Markdown 파일 + git"입니다. 이것이 가장 운용하기 쉬웠습니다.

memory/
├── MEMORY.md # 인덱스 (매 세션 전문 주입)
├── feedback_*.md # 행동에 대한 수정 지시 (1파일 = 1사실)
...

설계 원칙은 4가지입니다.

  • 1파일 = 1사실. 입도를 맞추면 업데이트·폐기·검색의 단위가 안정됩니다.
  • 인덱스는 1행 요약만. MEMORY.md에는 각 기억의 1행 포인터만 쓰고, 본문은 포함하지 않습니다.
  • 삭제하지 않고 아카이브한다. 틀렸던 기억도 "왜 틀렸는지"와 함께 archive/로 옮깁니다.
  • git으로 관리한다. 기억은 자산입니다. 후술하겠지만, 이를 소홀히 하여 실질적인 피해가 발생했습니다.

기억 파일의 실물은 다음과 같은 형태입니다.

---
name: deploy-verify-with-image-tag
description: 배포 반영 검증은 HTTP 200이 아니라 Docker 이미지 태그로 수행한다
...

frontmatter의 status가 포인트이며, draft(가설) → confirmed(검증 완료) → deprecated(폐기)의 상태 머신(State Machine)으로 취급합니다. AI에게는 "confirmed 이외의 것은 확인 없이 사실로 취급하지 마라"고 지시합니다.

세션 시작 시 "인덱스 전문 + 관련 기억의 본문"을 시스템 프롬프트(system prompt)에 주입합니다. 여기서 중요한 것이 **프롬프트 캐싱(prompt caching)**입니다. 기억 블록은 매 턴 동일하므로, 캐시에 올리지 않으면 매 턴 전액 과금됩니다.

import anthropic
from pathlib import Path
client = anthropic.Anthropic() # ANTHROPIC_API_KEY 는 환경 변수로 설정
...

`cache_control: {

로 1시간 동안 캐시를 사용합니다 (쓰기 단가는 높아지지만, 간격이 넓은 워크로드에서는 총액이 낮아집니다).

어느 쪽이 더 저렴한지는 "시간당 호출 횟수"에 의해 결정됩니다. 저희는 대화형 에이전트는 5분 TTL, cron형은 1시간 TTL로 나누어 적용했습니다.

기억의 저장은 AI 스스로가 하게 합니다. "중요한 결정이나 수정 지시를 받으면 저장한다"를 tool use (도구 사용)로 구현합니다.

import json, re, datetime
MEMORY_TOOL = {
"name": "memory_store",
...

주목해야 할 점은, 신규 저장이 반드시 status: draft로 들어간다는 점입니다. AI가 스스로 작성한 기억을 즉시 confirmed (확정) 상태로 취급하지 않습니다. 인간(또는 별도의 리뷰어 에이전트)이 확인하고 승격시킵니다. 이를 통해 "AI의 착각이 기억으로 고착되는" 사고를 방지합니다.

도입 초기, AI는 무엇이든 저장했습니다. "오늘은 맑음", "사용자가 고맙다고 말했다" 수준의 것까지 말이죠. 인덱스가 3주 만에 300행을 넘어서면서, 주입되는 토큰(injection tokens)이 오히려 팽창했습니다.

효과적이었던 대책은, tool의 description (도구 설명)에 저장하지 않을 조건을 명시하는 것입니다 (위의 코드 참조). "코드를 읽으면 알 수 있는 것은 저장하지 않는다", "이 대화에서만 의미를 갖는 것은 저장하지 않는다". tool description은 실질적인 프롬프트이므로, 이 부분을 작성하는 것이 동작을 가장 크게 변화시킵니다.

여기에 더해 인덱스에 "1행 200자 이내"라는 기계적 검증(validation)을 넣어, 초과할 경우 CI에서 차단하도록 했습니다.

"장기 기억 = 벡터 검색 (vector search)"이라고만 생각했으나, 운영 초기 단계의 기억 수(약 수백 건)에서는 **frontmatter의 description에 대한 전체 텍스트 grep (전체 검색)**만으로도 실용적이었습니다.

import subprocess
def recall(query_terms: list[str], memory_dir: str = "memory") -> list[str]:
"""description 행에 대한 OR 검색. 히트된 파일 경로를 반환함"""
...

포인트는 description (1행 요약)의 품질입니다. 검색에 히트되는지는 description의 어휘에 의해 결정되므로, 저장 시점에 "미래의 내가 어떤 단어로 검색할 것인가"를 의식하게 합니다. 이는 벡터 검색으로 전환하더라도 동일하게 효과적이므로 낭비가 아닙니다.

건수가 천 건을 넘어서는 시점에 처음으로 임베딩 (embedding) 검색을 검토하면 된다는 것이 현재의 결론입니다.

가장 뼈아팠던 사고였습니다. 서버 장애로부터 복구(restore)할 때, 복구 대상에 기억 디렉토리의 일부가 포함되지 않아 일일 자동화 스크립트 3개와 며칠 분의 기억이 소실되었습니다. 설상가상으로, 백업용 git push가 용량 초과로 인해 15일 동안 silent (조용히) 실패하고 있었다는 사실이 나중에 밝혀졌습니다.

이후의 재발 방지책은 그대로 설계 지침이 되었습니다.

  • 기억 디렉토리는 git으로 관리하고 원격(remote)에 push한다 (로컬 백업만으로는 복구 사고에 휘말릴 수 있음)
  • push 실패를 알림(notification)으로 보낸다. 백업은 "성공하고 있다는 전제"가 가장 위험함
# 백업 스크립트 끝부분의 예: push 실패를 Webhook으로 알림
if ! git push origin main 2>/tmp/push_err.log; then
curl -s -X POST "$DISCORD_WEBHOOK" \
...
  • 복원 및 재구현을 하기 전에, 먼저 기억을 읽는다. 소실 후 복구 작업 중에, 사양을 기록한 기억이 남아 있었음에도 이를 참조하지 않고 열화된 버전을 재구현해 버린 적이 있습니다. 기억은 "남기는 것"만큼이나 "읽는 운용"이 중요합니다.

6개월간 운용하며 정립된 결론입니다.

  • 대화 이력의 저장은 장기 기억이 되지 않는다. **증류된 상태 (distilled state)**를 가져야 한다 - 1파일 1사실 + 1행 인덱스 + status 상태 머신. 스토어는 Markdown + git으로 충분히 시작할 수 있다.
  • 주입(injection)은 prompt caching (프롬프트 캐싱)과 세트로 설계한다 (TTL 5분을 모르면 비용이 폭증한다).
  • 저장은 draft로 넣고 검증 후에 승격한다. AI의 자기 보고를 즉시 신뢰하지 않는다.
  • 기억은 git으로 원격 관리하며, 백업 실패를 알림으로 보낸다.

장기 기억의 본질은 "많이 기억하는 것"이 아니라, "다음 행동을 틀리지 않기 위한 최소한의 상태를 신뢰도와 함께 보유하는 것"이었습니다. 다음 회차에서는 이 기억 스토어를 MCP 서버로 만들어, 여러 AI 클라이언트가 공유할 수 있는 설계를 다루겠습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0