멀티턴 메모리 에이전트
메모리는 별도 템플릿이 아니라 agent-*, flow-* scaffold에서 --memory 옵션으로 켭니다.
Registry 기반(agent-general 등)이 기본이며, 직접 모델 연결 템플릿(agent-local/agent-openai/agent-anthropic)에서도 동일하게 동작합니다.
이 페이지는 대화를 세션 단위로 이어가고 싶거나, postgres와 in-memory 중 무엇을 쓸지 결정할 때 보면 됩니다.
어떤 모드를 쓸까
섹션 제목: “어떤 모드를 쓸까”| 모드 | 용도 | 재시작 후 유지 |
|---|---|---|
off | 메모리 사용 안 함 | 아니오 |
in-memory | 로컬 테스트 | 아니오 |
postgres | 운영 기본 권장 | 예 |
sqlite | 레거시 호환 | 예 |
기본 추천은 postgres입니다.
핵심 개념
섹션 제목: “핵심 개념”- A2A 세션 키는
message.contextId - 런타임 내부에서는 이를
thread_id로 사용 - scaffold는
.env에AGENT_ID=<project_name>를 자동 기록 - 공유 DB에서는
AGENT_ID + thread_id조합으로 소유권을 구분
가장 많이 쓰는 방식
섹션 제목: “가장 많이 쓰는 방식”PostgreSQL 메모리
섹션 제목: “PostgreSQL 메모리”uv run llamon agent my-agent --template agent-general --memory postgres --yescd my-agentuv run llamon run .이 경우:
- PostgreSQL 백엔드 의존성(
psycopg[binary],langgraph-checkpoint-postgres,asyncpg)은 SDK core에 기본 포함 — 별도 extras 불필요 docker-compose.local.yml에postgres서비스가 자동 추가됨- 기본 DB 이름은
<project_name>_memory docker compose up실행 시POSTGRES_MEMORY_DSN이@postgres:5432/...로 자동으로 덮어써지므로.env값을 건드리지 않아도 동작합니다 (외부 DB 사용 시에만.env교체 필요)
in-memory 메모리
섹션 제목: “in-memory 메모리”uv run llamon agent my-agent --template agent-general --memory in-memory --yes짧은 실험에는 충분하지만 프로세스를 다시 띄우면 대화가 사라집니다.
외부 PostgreSQL 사용
섹션 제목: “외부 PostgreSQL 사용”.env에서 POSTGRES_MEMORY_DSN만 바꾸면 됩니다.
AGENT_ID=my-agentPOSTGRES_MEMORY_DSN=postgresql://user:password@db-host:5432/my-agent_memoryKubernetes나 별도 DB를 쓰는 경우도 같은 방식입니다.
in-memory에서 PostgreSQL로 바꾸려면
섹션 제목: “in-memory에서 PostgreSQL로 바꾸려면”운영으로 넘어가면서 in-memory를 postgres로 바꾸는 경우가 많습니다.
핵심은 두 가지입니다.
- 의존성은 그대로 두면 됩니다 — PostgreSQL 백엔드 패키지는 이미 SDK core에 포함돼 있습니다.
in-memory에 있던 기존 세션은 자동으로 PostgreSQL로 옮겨지지 않습니다.
1. 의존성 확인
섹션 제목: “1. 의존성 확인”dependencies = [ "llamon-agent", "uvicorn",]lock / wheelhouse 기준으로 아래 패키지들이 core 의존성으로 기본 포함됩니다.
asyncpglanggraph-checkpoint-postgrespsycopgpsycopg-binarypsycopg-pool
SQLite 레거시 백엔드를 유지해야 하는 경우에만
uv add "llamon-agent[memory]"extras가 필요합니다.
2. 설정 변경
섹션 제목: “2. 설정 변경”AGENT_ID=my-agentPOSTGRES_MEMORY_DSN=postgresql://user:password@db-host:5432/my-agent_memory코드에서 직접 주입한다면:
MemoryConfig( enabled=True, backend="postgres", postgres_dsn="postgresql://user:password@db-host:5432/my-agent_memory", window_size=20, history_aware=True, # 기본값 — 멀티턴 가이드 자동 prepend)3. 오프라인 산출물 재생성
섹션 제목: “3. 오프라인 산출물 재생성”폐쇄망 배포를 쓴다면 기존 wheelhouse를 그대로 쓰면 안 됩니다.
uv lock --refreshuv run llamon prepare-offline . --clean이후 wheelhouse에는 최소한 아래가 보여야 합니다.
asyncpglanggraph-checkpoint-postgrespsycopg-binary
4. 데이터 이관 주의
섹션 제목: “4. 데이터 이관 주의”in-memory는 프로세스 메모리라서, 이미 떠 있던 세션 데이터는 앱 종료 시 사라집니다.
즉 이 전환은 보통:
- 새 배포부터 PostgreSQL 저장 시작
- 기존 in-memory 세션은 종료
이 흐름으로 봐야 합니다.
기존 세션 기록까지 유지해야 하면 애플리케이션 레벨 export/import를 별도로 만들어야 합니다.
세션이 이어지려면
섹션 제목: “세션이 이어지려면”같은 사용자의 대화를 이어가려면 매 요청에 같은 message.contextId를 보내야 합니다.
{ "jsonrpc": "2.0", "method": "message/send", "params": { "message": { "contextId": "user-alice-session", "role": "user", "parts": [{"kind": "text", "text": "이전에 뭐 얘기했지?"}] } }}contextId가 없으면 요청마다 새 세션으로 처리됩니다.
멀티턴 대화 가이드 (history_aware)
섹션 제목: “멀티턴 대화 가이드 (history_aware)”같은 contextId 로 들어오는 후속 turn은 checkpointer가 이전 messages를 자동 복원합니다. 사용자 prompt가 이전 대화를 참고하지 않는 형태일 때 LLM이 대화 이력을 무시하지 않도록, SDK는 2턴 이상부터 system_prompt 끝에 “이전 대화 참고” 안내를 자동으로 덧붙입니다 (history_aware=True 가 기본값).
3중 가드(system_text 존재 + AIMessage 1개 이상 + history_aware=True) 모두 충족 시에만 활성. 1턴·stateless·옵트아웃은 기존 동작과 동일.
분류기·JSON-only·one-shot 검증처럼 이전 대화를 보지 않는 게 정답인 agent 는 옵트아웃:
MemoryConfig( enabled=True, backend="postgres", history_aware=False, # 이전 대화 미참고 — 옵트아웃)멀티턴 동작 진단 — 관측 로그
섹션 제목: “멀티턴 동작 진단 — 관측 로그”매 LLM 호출 직전 한 줄을 INFO 로그로 emit:
multiturn: thread_msgs=4 window=20 sent_to_llm=4 has_prior=true| 신호 | 해석 |
|---|---|
has_prior=false 인데 멀티턴 기대 | contextId가 매 turn 다름 또는 thread_id 미전달 |
thread_msgs > sent_to_llm | window가 작아 잘림 → window_size 상향 또는 summarize=True |
has_prior=true 인데 응답에 history 미반영 | prompt 자체 문제 — history_aware 가이드가 보조함 |
운영 메모
섹션 제목: “운영 메모”checkpoints,checkpoint_blobs,checkpoint_writes는 LangGraph가 관리persistent_memory는 SDK의 장기 기억 저장용 테이블llamon memory prune으로 오래된 thread를 정리 가능
예:
uv run llamon memory prune --dsn postgresql://... --older-than-days 30uv run llamon memory prune --dsn postgresql://... --thread-id user-session-abc --applyPgBouncer 호환성
섹션 제목: “PgBouncer 호환성”운영 환경에서 PostgreSQL을 PgBouncer 뒤에 두는 경우 SDK는 session 모드 전제로 동작합니다. 이는 AsyncPostgresSaver(LangGraph 체크포인터)와 asyncpg(영구 메모리 CRUD) 모두 prepared statement / 장기 트랜잭션을 사용하기 때문입니다.
SDK 내부 풀 옵션 (수정 불가, 참고용)
섹션 제목: “SDK 내부 풀 옵션 (수정 불가, 참고용)”PostgresBackend는 다음 값을 모듈 상수로 하드코딩합니다 — 모든 에이전트가 동일 SDK로 만들어지는 전제이므로 운영팀이 인스턴스별로 튜닝하지 않습니다.
| 풀 | min | max | lifetime | idle 회수 |
|---|---|---|---|---|
psycopg_pool (체크포인터) | 0 | 2 | 1800s | 300s |
asyncpg (영구 메모리) | 0 | 3 | — | 300s |
min_size=0: idle 에이전트가 PgBouncer 슬롯을 점유하지 않음 → 다수 에이전트 운영 핵심max_lifetime=1800: PgBouncerSERVER_LIFETIME=3600의 절반 → SDK가 먼저 conn 회전, 죽은 conn 잡지 않음max_idle=300: PgBouncerSERVER_IDLE_TIMEOUT=600의 절반 → SDK가 먼저 idle conn 회수- 컨테이너당 peak conn = 5개 (psycopg 2 + asyncpg 3)
권장 PgBouncer 설정
섹션 제목: “권장 PgBouncer 설정”pgbouncer: environment: POOL_MODE: session DEFAULT_POOL_SIZE: 250 # 50개 활성 에이전트 = 5 conn × 50 MIN_POOL_SIZE: 0 # idle 슬롯 점유 X RESERVE_POOL_SIZE: 30 # 트래픽 스파이크 대응 SERVER_LIFETIME: 3600 SERVER_IDLE_TIMEOUT: 600 MAX_CLIENT_CONN: 2000
postgres: command: - postgres - -c - max_connections=300 # 250 + 관리용 50 여유 - -c - shared_buffers=1GB운영 가능 시나리오
섹션 제목: “운영 가능 시나리오”| 동시 활성 에이전트 | 필요 PgBouncer pool | Postgres max_connections |
|---|---|---|
| ~50개 | 250 | 300 |
| 50-70개 | 280 | 300 (한도) |
| 70개 초과 | 400+ | 500+ (증설 필요) |
idle 에이전트는 PgBouncer 슬롯을 점유하지 않으므로 위 표의 “활성”은 동시에 트래픽 받는 컨테이너 수입니다. 전체 에이전트 수와는 무관합니다.
transaction 모드 전환
섹션 제목: “transaction 모드 전환”PgBouncer를 transaction 모드로 운영하려면 SDK 수정이 필요합니다. src/llamon_agent/outbound/memory/backends.py의 _ASYNCPG_STATEMENT_CACHE_SIZE를 0으로 변경해야 합니다 (prepared statement 비활성화). psycopg 측은 이미 prepare_threshold=0이라 그대로 동작합니다.
관리 API timeout 정책
섹션 제목: “관리 API timeout 정책”일반 CRUD(체크포인트 read/write, persistent 저장)는 풀 기본값 _ASYNCPG_COMMAND_TIMEOUT=10s를 사용합니다. 관리 API(DELETE /memory/threads?all=true, DELETE /memory/threads/{id}, POST /memory/migrate-agent-id, GET /memory/threads)는 대량 데이터 처리 가능성이 있어 호출별로 _ASYNCPG_ADMIN_COMMAND_TIMEOUT=120s를 적용합니다.
천 개 이상 thread를 일괄 삭제하면 120초도 부족할 수 있어 배치 처리(thread_id 100개씩 분할 호출)를 권장합니다.
모니터링
섹션 제목: “모니터링”배포 후 다음 쿼리로 SDK 풀 사용을 추적합니다.
SELECT state, count(*)FROM pg_stat_activityWHERE application_name = 'llamon-agent-sdk'GROUP BY state;active 수가 PgBouncer pool의 70%를 넘으면 DEFAULT_POOL_SIZE 증설 시점입니다.
관련 문서
섹션 제목: “관련 문서”- 관리 API: 관리 API
- 로컬 실행/점검: 로컬 실행/점검 (llamon run/doctor/prepare-offline)
- Registry 기반 구성: Registry 기반 에이전트 구성