AI 에이전트에 메모리 추가하는 방법: 프로덕션용 상태 지속성 패턴
상태 비저장 에이전트——Worker가 종료될 때 모든 것을 잊는 유형——는 일회성 작업에 적합합니다. 에이전트가 어제 무슨 일이 있었는지 기억하거나, 재방문 고객을 인식하거나, 이전 출력을 기반으로 작업해야 하는 순간, 메모리가 필요합니다. 세 가지 패턴이 있습니다: 워킹 메모리(실행 중 컨텍스트, 실행 기간 동안 KV에 저장), 에피소딕 메모리(무엇이 언제 일어났는지, 쿼리 가능한 로그), 시맨틱 메모리(당신이 아는 것, 벡터 검색이나 구조화된 데이터로 검색). 올바른 패턴을 올바른 작업에 연결하세요.
매주 수요일. 28,400명+ 구독자. 핵심만.
✓ 받은편지함을 확인하세요 — 확인 링크를 클릭해 가입을 완료하세요.
✓ 구독이 완료되었습니다!
✓ 이미 목록에 있습니다.
목차
2026년 6월 업데이트.
TL;DR: 상태 비저장 에이전트——Worker가 종료될 때 모든 것을 잊는 유형——는 일회성 작업에 적합합니다. 에이전트가 어제 무슨 일이 있었는지 기억하거나, 재방문 고객을 인식하거나, 이전 출력을 기반으로 작업해야 하는 순간, 메모리가 필요합니다. 세 가지 패턴이 있습니다: 워킹 메모리(실행 중 컨텍스트, 실행 기간 동안 KV에 저장), 에피소딕 메모리(무엇이 언제 일어났는지, 쿼리 가능한 로그), 시맨틱 메모리(당신이 아는 것, 벡터 검색이나 구조화된 데이터로 검색). 올바른 패턴을 올바른 작업에 연결하세요.
[운영자의 관점] 상태 비저장의 벽에 한두 번 이상 부딪혔습니다. 소셜 답장 에이전트가 20번씩 대화한 고객에게 계속 자기소개를 했습니다. 데일리 브리프 에이전트는 어제 플래그했다는 기억이 없어 같은 문제를 나흘 연속으로 플래그했습니다. 올바른 종류의 메모리를 추가하자 두 가지 모두 해결되었습니다. 이것이 제가 사용하는 방법입니다.
상태 비저장 에이전트가 계속 실패하는 이유
상태 비저장 에이전트는 명시적으로 전달한 내용으로만 각 실행을 시작합니다: 시스템 프롬프트, 사용자 메시지, 호출 시 새로 가져온 데이터. 이전 실행, 이전 사용자, 이전 결정에 대한 인식이 없습니다.
일회성 분류 작업——댓글을 읽어 카테고리를 반환하는——에는 상태 비저장이 맞습니다. 빠르고, 저렴하고, 예측 가능합니다.
연속성이 필요한 순간 장애 표면이 나타납니다:
- 고객의 이력을 인식하지 못하는 고객 대면 에이전트
- 지난주에 이미 추천한 기사를 추천하는 콘텐츠 에이전트
- 해결된 케이스를 계속 재에스컬레이션하는 모더레이션 에이전트
- 무한정 같은 오래된 알림을 표시하는 데일리 브리프
이 모든 것은 같은 문제의 증상입니다: 에이전트가 실행 간에 컨텍스트를 전달할 방법이 없습니다.
세 가지 메모리 유형
프로덕션에서 유용하게 사용하는 프레임워크:
- 워킹 메모리 — 에이전트가 단일 실행 중 지금 알고 있는 것. 호출 생애 동안 KV나 메모리에 보관됩니다.
- 에피소딕 메모리 — 무엇이 언제 일어났는지. 각 실행 시작 시 에이전트가 읽어 방향을 잡는 구조화된 로그.
- 시맨틱 메모리 — 세계, 고객, 또는 지식 베이스에 대해 알고 있는 것. 관련될 때 구조화된 쿼리나 벡터 검색으로 검색됩니다.
항상 세 가지 모두 필요한 것은 아닙니다. 제가 실행하는 대부분의 에이전트는 워킹 + 에피소딕이 필요합니다. 시맨틱 메모리는 구축하기 가장 어려우며 지식 베이스가 컨텍스트 윈도우에 들어가기에 너무 클 때만 자리를 차지합니다.
워킹 메모리: 실행 중 컨텍스트
워킹 메모리는 에이전트 실행 기간 동안 존재하는 상태입니다. 가장 단순한 형태는 함수 스코프의 변수입니다. 더 흥미로운 형태는 같은 실행 내의 서브태스크들이 읽고 쓰는 공유 KV 키입니다.
소셜 답장 에이전트는 하나의 큐 메시지에서 댓글 배치를 처리하는 동안 컨텍스트를 축적하기 위해 워킹 메모리를 사용합니다. 시작 시 각 고객의 최근 대화 이력을 KV에서 읽고, 처리하면서 새 컨텍스트를 추가하고, 끝에 다시 씁니다.
// workers/social-reply.ts
async function processComment(
comment: SocialCommentEvent,
env: Env
): Promise<void> {
// 이 고객의 최근 이력을 KV에서 로드 (워킹 메모리)
const historyKey = `customer:${comment.userId}:history`;
const rawHistory = await env.AGENT_KV.get(historyKey);
const history: ConversationTurn[] = rawHistory
? JSON.parse(rawHistory)
: [];
// 이력에서 컨텍스트 인식 시스템 프롬프트 구성
const systemPrompt = buildSystemPrompt(history);
const response = await anthropic.messages.create({
model: "claude-opus-4-8",
max_tokens: 512,
system: systemPrompt,
messages: [{ role: "user", content: comment.text }],
});
const reply =
response.content[0].type === "text" ? response.content[0].text : "";
// 이력 업데이트 — 마지막 10턴 유지, TTL 30일
const updatedHistory: ConversationTurn[] = [
...history.slice(-9),
{ role: "assistant", content: reply, timestamp: comment.timestamp },
];
await env.AGENT_KV.put(historyKey, JSON.stringify(updatedHistory), {
expirationTtl: 60 * 60 * 24 * 30,
});
await postReply(comment, reply, env);
}두 가지를 주목하세요. 이력은 10턴으로 제한됩니다——슬라이딩 윈도우를 삽입하고, 무한정 늘리지 마세요. TTL은 30일: 고객이 한 달 동안 침묵하면 이력이 만료되고 에이전트가 새로 시작합니다. 둘 다 의도적입니다.
에피소딕 메모리: 무엇이 언제 일어났는지
에피소딕 메모리는 에이전트의 로그입니다. 각 새로운 실행 시작 시 에이전트가 읽어 반복을 피하기 위한 과거 실행의 구조화된 기록입니다.
데일리 브리프 에이전트는 각 실행이 이미 플래그된 것에 대한 인식이 없어 매일 같은 오래된 알림을 표시했습니다. 해결책: 에이전트가 브리프를 생성하기 전에 읽는 과거 알림의 구조화된 로그입니다.
// workers/daily-brief.ts
interface AlertLogEntry {
id: string;
surfacedAt: string; // ISO 타임스탬프
resolvedAt?: string;
summary: string;
}
async function buildDailyBrief(env: Env): Promise<void> {
const [emails, calendar, tasks] = await Promise.all([
fetchOvernightEmails(env),
fetchTodayCalendar(env),
fetchTopTasks(env),
]);
// 에피소딕 메모리 로드: 이미 플래그된 것
const rawLog = await env.AGENT_KV.get("brief:alert-log");
const alertLog: AlertLogEntry[] = rawLog ? JSON.parse(rawLog) : [];
// 최근 미해결 알림만 필터링
const sevenDaysAgo = new Date(
Date.now() - 7 * 24 * 60 * 60 * 1000
).toISOString();
const recentAlerts = alertLog.filter(
(e) => e.surfacedAt > sevenDaysAgo && !e.resolvedAt
);
const brief = await synthesizeBrief(
{ emails, calendar, tasks, recentAlerts },
env
);
// 이 실행에서 플래그된 새 알림으로 로그 업데이트
const newAlerts: AlertLogEntry[] = brief.newAlerts.map((a) => ({
id: crypto.randomUUID(),
surfacedAt: new Date().toISOString(),
summary: a,
}));
const updatedLog = [...alertLog, ...newAlerts].slice(-100); // 마지막 100개 유지
await env.AGENT_KV.put("brief:alert-log", JSON.stringify(updatedLog));
await writeToWorkspace(brief.content, env);
}에이전트는 이제 자신이 무엇을 말했는지 알고 있습니다. 중복 알림은 기저 문제가 바뀔 때까지 브리프에서 제외됩니다. 알림을 해결됨으로 표시하면 활성 목록에서 사라집니다.
이 패턴은 일반화됩니다: 결정, 플래그, 추천을 생성하는 모든 에이전트는 로그의 혜택을 받습니다. 로그는 저렴하고(KV에 수 KB), 보상은 높습니다(더 이상 중복 출력 없음).
시맨틱 메모리: 당신이 아는 것
시맨틱 메모리는 지식 베이스입니다. 모든 것을 시스템 프롬프트에 미리 채워 넣는 대신, 쿼리 시점에 “X에 대해 무엇을 알고 있나요?”에 답합니다.
가장 단순한 형태는 KV나 데이터베이스의 구조화된 조회입니다. Pickleland 예약 에이전트는 확인서를 작성하기 전에 고객 프로필과 코트 선호도를 조회합니다:
// workers/booking-agent.ts
interface CustomerProfile {
userId: string;
preferredCourts: string[];
experienceLevel: "beginner" | "intermediate" | "advanced";
specialNotes: string;
}
async function draftConfirmation(
booking: BookingEvent,
env: Env
): Promise<string> {
// KV에서 고객 프로필 가져오기 (시맨틱 메모리——사실적 지식)
const profileKey = `customer:${booking.userId}:profile`;
const rawProfile = await env.AGENT_KV.get(profileKey);
const profile: CustomerProfile | null = rawProfile
? JSON.parse(rawProfile)
: null;
const systemPrompt = profile
? `개인화된 예약 확인서를 작성합니다. 이 고객은 ${profile.preferredCourts.join(", ")}을 선호하며, ${profile.experienceLevel} 레벨의 선수입니다. ${profile.specialNotes}`
: "피클볼 시설의 예약 확인서를 작성합니다.";
const response = await anthropic.messages.create({
model: "claude-haiku-4-5-20251001",
max_tokens: 256,
system: systemPrompt,
messages: [
{
role: "user",
content: `다음에 대한 확인서를 작성하세요: ${JSON.stringify(booking)}`,
},
],
});
return response.content[0].type === "text" ? response.content[0].text : "";
}더 큰 지식 베이스——제품 문서, 지원 지식 베이스, 컨텍스트 윈도우에 들어가기에 너무 큰 것——에는 벡터 스토어가 필요합니다. 워크플로는: 쿼리를 임베딩하고, 가장 관련성 높은 k개의 청크를 검색하고, 컨텍스트에 주입합니다. Cloudflare Vectorize는 이미 Workers를 사용 중이라면 이를 네이티브로 처리합니다. 더 큰 인덱스에는 Upstash Vector를 사용했습니다. 선택은 규모에 달려 있으며, 원칙이 아닙니다.
시맨틱 메모리에 대한 솔직한 메모: 세 가지 중 구축하고 유지하기 가장 어렵습니다. 인덱스는 최신 상태를 유지해야 합니다. 검색 품질이 다양합니다. 구조화된 조회——KV, D1의 테이블——에서 시작하고, 구조화된 접근 방식이 필요한 지식 표면을 커버할 수 없을 때만 벡터 검색으로 넘어가세요.
메모리 의사결정 프레임워크
에이전트에 메모리를 추가하기 전에, 세 가지 질문에 답하세요:
-
에이전트가 실행 간에 기억해야 하나요? 각 호출이 진정으로 독립적이라면——번역, 분류, 일회성 생성——메모리를 건너뛰세요. 상태 비저장이 더 간단하고 저렴합니다.
-
에이전트가 자신의 이력에 눈을 감고 반복하고 있나요? 그렇다면, 먼저 에피소딕 메모리를 추가하세요. 가장 적은 노력으로 수정할 수 있으며, “에이전트가 X를 계속 한다”는 불만의 대부분을 해결합니다.
-
에이전트가 그러면 안 될 때 모든 사용자나 엔티티를 동일하게 대하고 있나요? 그렇다면, 워킹 메모리(고객 이력, 사용자 프로필)나 시맨틱 메모리(검색 또는 검색 시스템)를 추가하세요.
제가 가장 많이 보는 실수: 누군가가 에피소딕 메모리가 없어서——이미 한 일의 로그가 없어서——실패하던 에이전트에 거대한 지식 베이스(시맨틱 메모리)를 추가합니다. 복잡성이 문제와 일치하지 않습니다.
프로덕션에서 실제로 사용하는 것
30개 이상의 에이전트 중:
- 모두 최소한 워킹 메모리를 가지고 있습니다——그것이 단지 컨텍스트 윈도우 자체일지라도, 실행 내 어떤 형태의 상태.
- 약 절반이 에피소딕 메모리를 가지고 있습니다——과거 실행, 결정, 플래그의 로그. 이것은 거의 항상 추가할 가치가 있습니다.
- 서너 개가 벡터 스토어로 뒷받침되는 진정한 시맨틱 메모리를 가지고 있습니다. 이것들은 크고 동적인 지식 베이스에 대해 질문에 답하는 에이전트입니다.
Cloudflare KV는 워킹 메모리와 에피소딕 메모리를 위한 기본 스토어입니다. 빠르고, 저렴하고, Workers에 네이티브로 통합되어 있습니다——추가 클라이언트 없이, 별도의 자격 증명 없이. 제한 사항: KV는 결국 일관성이 있으며 고빈도 쓰기에는 적합하지 않습니다. 초당 여러 번 상태를 쓰는 에이전트에는 대신 Durable Objects나 D1 데이터베이스를 사용합니다.
벡터로 뒷받침되는 시맨틱 메모리에는 소~중형 인덱스(~10만 벡터 미만)에 Cloudflare Vectorize를, 더 큰 것에는 Upstash Vector를 사용합니다. 둘 다 일류 JavaScript 클라이언트를 가지고 있습니다.
운영자의 결론
상태 비저장 동작이 실제 문제를 야기할 때만 에이전트에 메모리를 추가하세요——반복 출력, 고객 이력의 맹점, 과거 결정에 대한 무지. 그런 다음 올바른 레이어를 선택하세요: 실행 중 컨텍스트에는 워킹 메모리, 역사적으로 일어난 것에는 에피소딕 메모리, 아는 것에는 시맨틱 메모리. 확신이 없다면 에피소딕부터 시작하세요——가장 적은 복잡성으로 가장 흔한 장애 모드를 수정합니다. 구조화된 조회를 소진할 때까지 벡터 데이터베이스를 사용하지 마세요. 최고의 메모리 시스템은 에이전트가 올바르게 동작하게 만드는 가장 단순한 것입니다.
관련: 30개 이상의 프로덕션 에이전트를 실행하는 에이전트 스택 · 이벤트 트리거 에이전트 vs 스케줄 에이전트 · AI 에이전트가 실제로 작동하는지 측정하는 방법
귀하의 사용 사례를 위한 에이전트 메모리 설계에 도움이 필요하신가요? 연락하기 — 운영자 팀을 위한 프로덕션 에이전트 시스템을 설계합니다.
매주 수요일. 28,400명+ 구독자. 핵심만.
✓ 받은편지함을 확인하세요 — 확인 링크를 클릭해 가입을 완료하세요.
✓ 구독이 완료되었습니다!
✓ 이미 목록에 있습니다.
관련 게시물
첫 번째 MCP 서버 만들기: 실전 가이드
2026년 업데이트. MCP 서버를 구축하고 등록하는 데 사용하는 TypeScript 코드 — stdio 전송, 툴 정의, 30분 이내에 Claude Desktop에서 테스트하는 방법.
AI AgentsClaude API의 프롬프트 캐싱: 모델을 바꾸지 않고 입력 비용 줄이기
cache_control을 사용해 크고 안정적인 프롬프트를 쓰는 에이전트에서 Claude API 입력 비용을 최대 90%까지 줄이는 방법 — 접두사 일치 불변식, 무엇을 캐싱할지, 조용한 무효화 요인, 그리고 손익분기 계산까지.
AI AgentsClaude Fable 5 첫인상: 어느 운영자의 관점
2026년 최신판. 30개 이상의 프로덕션 에이전트를 운영하는 사람이 전하는 Claude Fable 5 첫인상 — 실제로 무엇이 다른지, 아무도 경고해 주지 않는 비용 함정, 그리고 갈아탈 만한지.
AI 플레이북을 받아보세요
매주 수요일. 28,400명+ 구독자. 핵심만.
받은편지함을 확인하세요.
확인 이메일을 보냈습니다 — 링크를 클릭해 구독을 완료하세요. 1분 안에 보이지 않으면 스팸함을 확인하세요.
구독이 완료되었습니다.
환영합니다 — 다음 호가 곧 받은편지함에 도착합니다.
이미 목록에 있습니다 — 매주 수요일에 확인하세요.