1. 에이전트에 별도 메모리가 왜 필요한가
이 부분은 모듈 12의 에이전트 관련 이전 강의를 바탕으로 합니다. 그곳에서 이미 기본 아키텍처, run 사이클, 툴을 다뤘고, 여기서는 메모리와 상태에 초점을 맞춥니다.
익숙한 웹 애플리케이션과 비교하면, 여기서 LLM은 복잡한 “텍스트 프로그램”을 실행할 수 있는 매우 똑똑한 CPU이고, 에이전트의 상태는 RAM과 SSD의 조합입니다. 즉, 단명하는 세션 데이터와 장기 저장소입니다.
순수한 ChatGPT형 채팅에서(여러분의 코드가 없을 때) “메모리”는 모델이 현재 요청에서 보는 system/user/assistant/tool 메시지 리스트일 뿐입니다. 에이전트에게는 이것만으로는 부족합니다. 왜냐하면:
- 복잡한 프로세스의 진행 상황을 기억해야 합니다. 예: workflow의 어느 단계까지 완료했는지, 어떤 선물 후보를 이미 필터링했는지, 사용자가 무엇을 확정했는지;
- 사용자에 대한 장기 사실을 알아야 합니다. 예: 선호, 배송지, 과거 주문 이력;
- 장애를 견뎌야 합니다. 예: 선물 추천 도중 서버가 다운되더라도 사용자가 모든 것을 다시 입력하지 않도록.
이 모든 것을 프롬프트 컨텍스트에만 넣으려 하면 금세 컨텍스트 윈도우 한계에 부딪히고 같은 사실에 계속 토큰 비용을 지불하게 됩니다. 동시에 보안 리스크도 커집니다. 불필요한 데이터가 정기적으로 모델로 전송되기 때문입니다. 그래서 에이전트 시스템에는 항상 명시적 상태가 있습니다. 이는 메시지 히스토리 밖에서 살아가며 여러분이 직접 관리하는 객체(들)입니다.
2. 에이전트 상태 레이어: 컨텍스트, session, persistent
레이어로 나눠 보겠습니다. 에이전트에는 보통 최소 세 가지 “메모리” 수준이 있습니다:
- 메시지 히스토리(dialogue context).
- 세션 상태(session state).
- 장기 상태(persistent state).
이 개념들을 한데 섞지 않는 것이 중요합니다.
메시지 히스토리: “더러운 메모리”
메시지 히스토리는 LLM이 매 스텝에서 보는 것입니다: system 지시, 사용자 요청, 에이전트 응답, 툴 실행 결과 등.
장점은 여러분이 수동으로 관리하지 않아도 된다는 점입니다. Agents SDK와 플랫폼이 Session/Conversation 같은 엔터티로 이를 책임집니다.
단점은 “더러운” 메모리라는 것입니다. 잉여 단어, 반복, 사용자의 우발적 데이터가 많습니다. 토큰 비용이 많이 들고 구조화가 잘 되어 있지 않습니다. 이미 필터링된 200개 선물 리스트를 매번 plain 텍스트로 모델에 낭독하고 싶지는 않을 것입니다.
Session state: 작업용 단기 메모리
Session 상태는 하나의 에이전트 세션/대화 범위에서 살아가는 구조화된 객체입니다. 프론트엔드 개발자에게 좋은 비유는 탭이 열려 있는 동안 살아 있는 useState나 Redux store입니다.
여기에 들어갈 예시는 다음과 같습니다:
- 프로세스의 현재 단계(예: "collecting_profile" 또는 "filtering_candidates");
- 툴 결과에 대한 임시 캐시;
- 세션 파라미터: 로케일, 선택된 채널, “사용자가 약관에 동의했는지” 같은 플래그.
이 상태는 에이전트와 가까운 곳에 둘 수 있습니다. Redis, 인메모리 KV 스토어, 혹은 특정 SDK의 내장 SessionService 등을 사용할 수 있습니다. 핵심은 이런 것들을 전부 system 프롬프트에 밀어 넣지 않는 것입니다.
Persistent state: 장기 데이터
Persistent 상태는 오래 살아갑니다. 세션 간, 체크아웃 간, 기기 간에 지속됩니다. 사용자 프로필, 주문, 위시리스트, 설정 등이 여기에 해당합니다.
핵심 아이디어: 에이전트가 persistent 데이터를 “마법처럼” 기억하는 것이 아니라 툴을 통해 “읽는다”는 것입니다. 예: get_user_profile, get_past_orders 등. 에이전트 내부의 숨겨진 전역 변수는 없습니다. 항상 명시적으로 호출합니다.
비교 표
| 레이어 | 어디에 존재 | 수명 주기 | 데이터 예시 |
|---|---|---|---|
| Messages | Session / SDK / OpenAI | 단일 run / 대화 | system/user/tool 메시지 |
| Session state | KV / SessionService / Redis | 세션이 살아 있는 동안 | workflow 단계, 임시 캐시 |
| Persistent | DB(Postgres/NoSQL/ACP backend) | 세션/대화 사이 | 프로필, 주문, 저장된 리스트 |
3. Session state: 정의와 저장 방법
에이전트 GiftGenius가 다단계 프로세스를 수행한다고 상상해봅시다:
- 받는 사람의 프로필을 수집한다.
- 후보 리스트를 생성한다.
- 예산, 배송, 지역으로 필터링한다.
- 최종 추천을 준비한다.
이 과정에서 에이전트는 사용자와 계속 대화하고 툴을 호출합니다. “이번 선물 추천 세션의 진행 상태”에 해당하는 것들은 session state에 두는 것이 논리적입니다.
GiftGenius의 세션 상태 구조 예시
세션 상태 타입을 TypeScript로 기술해 봅니다:
// 하나의 "선물 추천" 범위에서의 상태
export type GiftSessionState = {
step:
| "collecting_profile"
| "generating_candidates"
| "filtering"
| "finalizing";
// 받는 사람 프로필 초안
profileDraft?: {
recipientType?: string;
ageRange?: string;
interests?: string[];
dislikes?: string[];
};
// 백엔드에서 받은 상품 후보의 id
candidateIds?: string[];
// 사용자가 선택한 선물
selectedGiftId?: string;
// 기술 플래그
locale?: string;
};
여기서는 의도적으로 상품 전체 객체를 넣지 않고 ID만 둡니다. 전체 데이터는 DB에 두고, 필요할 때 에이전트가 get_gift_details(gift_id) 툴을 호출하도록 합니다.
Agents SDK의 Session(개념)
많은 에이전트용 SDK에는 세션 추상화가 있어 메시지 히스토리를 자동으로 관리하고 여러분이 구조화된 상태를 추가로 저장할 수 있게 합니다. 의사코드로는 대략 다음과 같습니다:
import { createRunner, OpenAIConversationsSession } from "@openai/agents";
// 위 예시의 GiftSessionState 타입
const session = new OpenAIConversationsSession<GiftSessionState>({
sessionId: "chatgpt-thread-id-or-random",
});
const runner = createRunner({ agent });
const result = await runner.run({
session,
input: "동료에게 줄 $50 이하의 선물을 원해요",
});
SDK는 내부적으로 다음을 수행합니다:
- 이 세션의 메시지 히스토리를 가져온다;
- 새 사용자 메시지를 추가한다;
- 모델과 툴링에 전달한다;
- 업데이트된 상태(session.state 포함)를 다시 저장한다.
여러분은 session.state를 일반 객체처럼 다루면 됩니다.
툴에서 session state 업데이트하기
전형적인 패턴: 어떤 것을 계산하는 툴이 동시에 세션 상태를 업데이트합니다. 예를 들어, 사용자 답변에서 받는 사람 프로필을 수집하는 툴:
export async function updateProfileDraft(
session: GiftSessionState,
answers: { questionId: string; value: string }
): Promise<GiftSessionState> {
const next: GiftSessionState = { ...session };
if (!next.profileDraft) {
next.profileDraft = {};
}
if (answers.questionId === "interests") {
next.profileDraft.interests = answers.value.split(",").map((s) => s.trim());
}
// ...다른 필드
next.step = "generating_candidates";
return next;
}
여기서는 SDK의 전체 Session이 아니라 그 중 state(타입은 GiftSessionState)만 툴에 전달합니다. 실제 코드에서는 이 인자를 Session 객체와 혼동하지 않도록 currentState 같은 이름을 쓰는 것이 좋습니다.
에이전트는 이 툴을 호출해 새로운 상태 객체를 받고, 그것을 session.state에 다시 저장합니다.
4. Persistent state: 에이전트의 장기 메모리
GiftGenius는 한 개의 채팅에서만 동작하지 않습니다. 사용자는 일주일 뒤 다른 기기에서 돌아와서 이렇게 말할 수 있습니다: “지난번과 같은 친구에게 줄 선물을 골라줘, 다만 예산은 올렸어.”
이 정보는 session state가 아니라 persistent 저장소에 있어야 합니다. DB, commerce/ACP 백엔드(커머스 레이어는 별도 모듈에서 다룸) 등입니다.
persistent 모델 예시
DB에 저장되는 받는 사람 프로필 모델을(단순화하여 TypeScript 타입으로) 기술해 봅니다:
// DB에 저장되는 것
export type RecipientProfile = {
id: string;
userId: string;
label: string; // "마케팅 동료"
recipientType: string;
ageRange?: string;
interests: string[];
dislikes: string[];
lastUsedAt: string; // ISO 날짜
};
그리고 리포지토리(일단 실제로는 ORM/SQL 레이어를 쓰겠지만 여기서는 간단히 Map 사용):
const profiles = new Map<string, RecipientProfile>();
export const RecipientRepo = {
async findByUser(userId: string): Promise<RecipientProfile[]> {
return [...profiles.values()].filter((p) => p.userId === userId);
},
async save(profile: RecipientProfile): Promise<void> {
profiles.set(profile.id, profile);
},
};
에이전트는 툴을 통해 persistent에 접근
에이전트가 직접 DB에 접근하기보다 툴을 통해 작업하도록 하는 것이 중요합니다. 이렇게 하면 에이전트는 “깨끗한” 엔터티로 남습니다. 한쪽에는 LLM과 계획 로직, 다른 쪽에는 통합 구현이 위치합니다.
예를 들어, get_recipient_profiles 툴:
export async function getRecipientProfilesTool(input: {
userId: string;
}): Promise<{ profiles: RecipientProfile[] }> {
const profiles = await RecipientRepo.findByUser(input.userId);
return {
profiles,
};
}
에이전트는 툴 설명에서 “현재 사용자에 대한 저장된 받는 사람 프로필을 가져오려면 이 툴을 사용하라”고 읽습니다. 그리고 언제 호출할지는 스스로 결정합니다.
요약하면, session state는 특정 대화의 진행과 무해하게 잃어버려도 되는 임시 캐시에 관한 것입니다. persistent 데이터는 세션과 기기를 넘어 지속되어야 하는 것들(프로필, 주문, 위시리스트)입니다. 에이전트는 이를 “마법처럼 기억”하는 것이 아니라 항상 툴을 통해 읽습니다.
5. run 사이클에서 session과 persistent의 협업
이제 전체 흐름으로 합쳐 보겠습니다. 에이전트 run 사이클의 각 단계에서 짧은 순서가 있습니다:
- sessionId로 session state를 가져온다.
- 필요하다면 툴을 사용해 DB에서 관련 persistent 데이터를 로드한다.
- 모델에 줄 컨텍스트를 구성한다(messages + 구조화된 상태).
- 모델이 텍스트로 답할지, 툴을 호출할지 결정한다.
- 툴은 session state 또는 persistent 데이터를(DB를 통해) 업데이트한다.
- 새로운 session 상태를 저장하고 필요하면 checkpoint를 생성한다(뒤에서 설명).
- 사용자에게 답변을 반환한다.
Mermaid 다이어그램:
flowchart TD
A[사용자 입력 받기] --> B["세션 로드(state + messages)"]
B --> C{persistent 데이터가 필요한가?}
C -- 예 --> D[툴 호출: get_user_profile, get_recipient_profiles]
C -- 아니오 --> E[LLM용 컨텍스트 구성]
D --> E
E --> F["모델(LLM) 호출"]
F --> G{모델이 tool 호출을 원하나?}
G -- 예 --> H[tool 실행, session/persistent 업데이트]
G -- 아니오 --> I[최종 답변 준비]
H --> J[Checkpoint 생성 및 Session 저장]
I --> J
J --> K[사용자에게 응답]
이런 사이클은 에이전트의 동작을 재현 가능하게 만듭니다. 매 단계마다 모델 호출 전 어떤 상태였고 이후 무엇이 바뀌었는지를 명시적으로 알 수 있습니다.
6. Checkpoints: 에이전트 상태의 스냅샷
Checkpoints는 프로세스의 중요한 단계에서 저장된 에이전트의 “상태 스냅샷”입니다. 이는 단순한 “현재 session state”가 아니라 외부 저장소에 기록된 사실입니다: N단계에서 어떤 state였고, 툴 결과가 무엇이었으며, 사용자 입력이 어떠했는지.
필요한 이유:
- 오류와 장애 후 복구;
- 사용자의 “나중에 계속” 기능;
- 디버깅: 문제 run의 재현 가능성;
- 감사: 예를 들어 주문 생성 전에 에이전트가 정확히 무엇을 했는지.
일반적으로 checkpoint에 포함되는 것
전형적인 checkpoint에는 다음이 포함됩니다:
- 식별자: runId, userId, workflowId, stepId;
- 해당 시점의 세션 상태;
- persistent 엔터티의 핵심 식별자(예: 주문 초안 id);
- 메타데이터: 생성 시간, 에이전트 버전.
전체 대화 텍스트를 모두 끌어오지 않는 것이 중요합니다. 아래 메모리 위생 절에서 무엇을 저장하고 무엇을 저장하지 말아야 할지 다시 다룹니다.
세션에 대한 링크나 단계 요약을 저장하는 편이 낫습니다.
7. GiftGenius를 위한 체크포인트 설계
우리의 선물 추천 프로세스를 가져와서 어디에 체크포인트를 둘지 결정해 봅시다. 예:
- 받는 사람 프로필을 수집한 후;
- 후보를 생성하고 1차 필터링한 후;
- 사용자에게 최종 선택을 제안하기 직전.
checkpoint와 workflow 상태를 위한 타입
workflow 상태를 기술합니다(GiftSessionState와 매우 비슷하지만 체크포인트를 위한 “스냅샷”임):
export type GiftWorkflowStep =
| "profile_collected"
| "candidates_generated"
| "filtered"
| "final_choice_made";
export type GiftCheckpoint = {
id: string;
runId: string;
userId: string;
step: GiftWorkflowStep;
// 복구에 필요한
// session 상태의 일부
sessionState: GiftSessionState;
// 어떤 후보 id들이 생성되었는지
candidateIds: string[];
createdAt: string; // ISO
agentVersion: string;
};
체크포인트 저장소(단순화)
앞서처럼 진짜 DB 대신 간단히 Map을 사용해 봅니다:
const checkpoints = new Map<string, GiftCheckpoint>();
export const GiftCheckpointRepo = {
async save(cp: GiftCheckpoint) {
checkpoints.set(cp.id, cp);
},
async findByRun(runId: string): Promise<GiftCheckpoint[]> {
return [...checkpoints.values()].filter((c) => c.runId === runId);
},
async findLastByUser(userId: string): Promise<GiftCheckpoint | undefined> {
return [...checkpoints.values()]
.filter((c) => c.userId === userId)
.sort((a, b) => b.createdAt.localeCompare(a.createdAt))[0];
},
};
에이전트 코드에서 체크포인트 생성
중요 단계 이후 호출하는 helper를 가정해 봅시다:
import { randomUUID } from "crypto";
export async function createCheckpoint(params: {
runId: string;
userId: string;
step: GiftWorkflowStep;
sessionState: GiftSessionState;
candidateIds: string[];
}) {
const checkpoint: GiftCheckpoint = {
id: randomUUID(),
runId: params.runId,
userId: params.userId,
step: params.step,
sessionState: params.sessionState,
candidateIds: params.candidateIds,
createdAt: new Date().toISOString(),
agentVersion: "v1.3.0",
};
await GiftCheckpointRepo.save(checkpoint);
}
에이전트는 필요한 순간 다음처럼 호출할 수 있습니다:
await createCheckpoint({
runId,
userId,
step: "filtered",
sessionState,
candidateIds,
});
복구 시에는 다음을 수행합니다:
- runId 또는 userId로 마지막 체크포인트를 찾는다.
- checkpoint.sessionState에서 session.state를 복원한다.
- 필요하면 candidateIds로 DB에서 최신 데이터를 보강한다.
8. session, persistent, checkpoints를 어디에 저장할까(기술적으로)
인프라 수준에서는 보통 세 가지 범주의 저장소를 사용합니다:
- 인메모리 — 개발/데모용, 빠르지만 일시적 메모리.
- Redis(또는 다른 KV 스토어) — session 상태용.
- 관계형/NoSQL DB — persistent 데이터와 체크포인트용.
로컬 개발을 위한 인메모리 스토어
로컬 개발 모드에서는 간단한 인메모리 스토어면 충분합니다. 예를 들어, 세션을 위한 TTL 미니 스토어:
type StoredSession<T> = {
state: T;
expiresAt: number;
};
const sessions = new Map<string, StoredSession<GiftSessionState>>();
export function saveSession(sessionId: string, state: GiftSessionState) {
sessions.set(sessionId, {
state,
expiresAt: Date.now() + 30 * 60 * 1000, // 30분
});
}
export function loadSession(sessionId: string): GiftSessionState | undefined {
const stored = sessions.get(sessionId);
if (!stored) return undefined;
if (stored.expiresAt < Date.now()) {
sessions.delete(sessionId);
return undefined;
}
return stored.state;
}
이런 방식은 로컬 개발에 아주 적합하지만, 프로덕션의 수평 확장(여러 인스턴스) 환경에서는 동작하지 않습니다.
session state를 위한 Redis
프로덕션에서는 session 상태를 Redis에 두는 것이 편리합니다:
- 빠른 읽기/쓰기;
- TTL이 기본 제공됨;
- 서비스의 모든 인스턴스에서 접근 가능.
의사 예시(단순화):
// Redis 클라이언트에 대한 래퍼
export async function saveSessionToRedis(
sessionId: string,
state: GiftSessionState
) {
const json = JSON.stringify(state);
await redis.set(`session:${sessionId}`, json, "EX", 60 * 30); // 30분
}
export async function loadSessionFromRedis(
sessionId: string
): Promise<GiftSessionState | undefined> {
const json = await redis.get(`session:${sessionId}`);
return json ? (JSON.parse(json) as GiftSessionState) : undefined;
}
persistent와 체크포인트를 위한 Postgres/기타 DB
persistent 상태와 체크포인트는 트랜잭션, 마이그레이션, 인덱스 등 “진지한” 기능이 중요한 엔터티입니다. Postgres, MySQL, Firestore 등지에 저장합니다.
여기서의 아키텍처 패턴은 간단합니다:
- session은 TTL이 있는 Redis;
- persistent와 checkpoints는 TTL이 없는 DB(또는 비즈니스에 따른 보존 정책)입니다.
9. 메모리 위생: 크기, 프라이버시, 책임 분리
에이전트 메모리는 단지 “어딘가에 객체를 넣고 출발”이 아닙니다. 비용을 절약하고 마음 편히 지낼 수 있게 해주는 중요한 규칙들이 있습니다.
모든 것을 messages에 넣지 말기
메시지 히스토리는 비싼 리소스입니다:
- 길이가 모델 요청 비용에 크게 영향을 줍니다;
- 대개 “노이즈”가 많습니다.
따라서:
- 가능한 한 빨리 히스토리에서 사실을 추출해 구조화된 상태로 옮기세요;
- 오래된 히스토리 부분에는 요약(summarization)을 사용하세요;
- checkpoint에 텍스트 히스토리를 저장한다면, 모델에 보낼 것과는 분리해 두세요.
프라이버시와 PII
특히 커머스 시나리오에서는 민감 데이터를 허용되지 않은 위치에 저장하지 않는 것이 중요합니다. 메모리 아키텍처 문서는 PII 데이터를 messages나 체크포인트에 정제 없이 두지 말라고 강조합니다.
실무 규칙:
- 에이전트 동작에 꼭 필요하지 않다면 이메일/전화/주소를 session state에 직접 넣지 마세요;
- 로그와 체크포인트에는 가능한 원시 문자열 대신 userId, recipientProfileId 같은 식별자를 기록하세요;
- PII를 여러 단계에 걸쳐 전달해야 한다면, persistent 저장소의 별도 보호 필드에 두고 state에는 키만 전달하세요.
비즈니스 데이터와 대화 로그의 분리
좋은 패턴은 state를 “깨끗한” 메모리로, messages를 “더러운” 메모리로 간주하는 것입니다.
즉:
- 비즈니스 엔터티(프로필, 주문, 장바구니)는 항상 DB에 상주;
- state/체크포인트에는 복구에 필요한 최소한만 포함;
- 로그/채팅 히스토리는 별도로 저장(예: 벡터 저장소)하여 분석에 사용하되 매 요청마다 모델 컨텍스트에 섞지 않기.
10. 미니 실습: 무엇을 저장하시겠습니까?
메모리 레이어의 차이를 확실히 하기 위해, 이론에서 잠시 벗어나 구체적 케이스를 생각해 봅시다. 코드를 꼭 쓸 필요는 없습니다. 종이나 머릿속으로 구조를 그려 보는 정도면 충분합니다.
여러분의 에이전트 GiftGenius가 사용자와 다음과 같은 대화를 했다고 가정하세요:
- 사용자: “개발자 동료에게 줄 선물이 필요해요, 예산은 $50 이하이고, 보드게임과 카페인을 좋아해요.”
- 에이전트: 몇 가지 추가 질문을 한다.
- 사용자: “머그컵은 싫어하고, 공책은 이미 넘쳐나요.”
- 에이전트: 아이디어 10개를 생성하고, 사용자가 하나를 고르지만 “나중에 와서 마무리할게요”라고 말한다.
생각해 보세요:
- 30분 후 만료될 수도 있는 session state에는 무엇을 넣겠습니까?
- 사용자가 일주일 뒤에 돌아올 수 있도록 persistent 저장소에는 무엇을 넣겠습니까?
- 아이디어를 선택했지만 주문을 완료하기 전의 checkpoint는 어떻게 생겼을까요?
이 강의의 예시를 참고해 saveSessionState, savePersistentState, createGiftIdeaCheckpoint 에 해당하는 TypeScript 타입과 함수를 대략적으로 스케치해 보세요. 원한다면 위 예시를 따라 에디터에서 실제로 타입과 함수를 적어 보아도 좋습니다. 다음 강의로 넘어가기 전의 좋은 미니 체크포인트가 될 것입니다.
11. 에이전트 메모리 작업 시 흔한 실수
오류 №1: 모든 것을 메시지 히스토리에만 저장하려고 함.
개발자는 이렇게 생각합니다. “모델이 어차피 대화 전체를 보는데 굳이 state가 필요해?”. 결과적으로 수십 개 메시지 후 컨텍스트 창이 잡동사니로 가득 차고, 토큰 비용은 새 MacBook 가격이 되며, 에이전트의 동작은 불안정해집니다 — 중요한 오래된 사실을 그냥 보지 못하기 때문입니다. 이 문제는 session state와 persistent 저장소를 명시적으로 분리해 해결해야지, 한도를 늘린다고 해결되지 않습니다.
오류 №2: session과 persistent를 하나의 객체로 섞음.
가끔 AgentState라는 “큰” 엔터티 하나를 만들고 무작정 모든 것을 집어넣어 그대로 DB에 저장하고 싶어집니다. 그러면 특정 대화의 임시 데이터와 사용자의 장기 데이터 사이 경계가 흐려집니다. “배포 이후에 모든 세션이 작년 데이터에서 요상하게 복원됐다”거나 “한 사용자의 세션이 다른 사람의 persistent 프로필을 우연히 집었다” 같은 일이 벌어집니다. 수준을 의식적으로 분리하세요.
오류 №3: 체크포인트에 너무 많은 것을 저장함.
흔한 실수는 툴 응답의 전체 JSON, 전체 대화 히스토리, 통합의 원시 데이터 등을 checkpoint에 몽땅 기록하는 것입니다. 몇 주만 지나도 체크포인트 DB가 말도 안 되게 비대해지고, 백업에 한 시간이 걸리며, DB 쿼리가 느려집니다. 체크포인트에는 프로세스를 이어가기 위해 실제로 필요한 사실과 최소한의 메타데이터만 있어야 합니다.
오류 №4: session state의 TTL과 정리를 잊음.
session 상태에 수명이 없다면, 사용자의 아무 실험적 세션이든 Dev Mode에서 Redis에 영원히 남습니다. 몇 달 뒤 모니터링을 보면 잊힌 세션이 산처럼 쌓여 메모리를 잡아먹고 있을 것입니다. session 레이어는 명시적 TTL로 설계하고, persistent 레이어는 보존 정책(retention)을 신중히 설계해야 합니다.
오류 №5: 필요 없는데 PII를 state와 체크포인트에 저장함.
특히 위험한 것은 session state에 이메일, 주소, 카드 번호 등을 무심코 넣고, 그 객체가 직렬화되어 로그, 분석, 체크포인트로 흘러가는 경우입니다. 이는 규제와 보안 측면에서 심각한 리스크를 만듭니다. 안전한 식별자만 저장하고, 실제 데이터는 별도의 보호된 툴을 통해 필요 시 resolve하는 편이 좋습니다.
오류 №6: 체크포인트에서의 복구 전략 부재.
어떤 팀은 체크포인트를 성실히 기록하지만, 에이전트가 그로부터 어떻게 복구해야 하는지는 고민하지 않습니다. 결국 “무언가 잘못되었을 때” 개발자는 예쁜 JSON 표를 바라보지만, 그것으로 run을 재구성할 코드는 없습니다. 복구 시나리오가 없는 체크포인팅은 비싼 로그일 뿐, 신뢰성 도구가 아닙니다.
오류 №7: 에이전트를 특정 저장소 구현에 단단히 묶음.
에이전트 코드가 Redis/Postgres에 직접 접근하면 이식성, 테스트, 확장이 어려워집니다. 아키텍처 변경(예: MCP 리소스 도입 또는 별도 state 서비스 등장) 시 에이전트 로직을 뜯어고쳐야 합니다. 에이전트가 보는 것은 Session과 툴 집합의 추상화뿐이고, 데이터가 실제 어디에 있는지는 툴이 알도록 하는 편이 훨씬 좋습니다.
GO TO FULL VERSION