기억 오염과 회수 신뢰성 — session_id 격리 하나로는 왜 부족한가

← 전체 목차 · 이전: 안 잊는 Agent는 왜 상용화할 수 없는가 · 다음: "쓰기 성공"과 "읽기 가능" 사이

"세션별로 기억을 나눠 저장하고, 매번 이번 세션 것만 불러온다" — 이 답은 완결돼 보이지만, session_id가 실제로 답하는 질문은 "이 기억이 누구의 어느 대화에 속하는가" 뿐이다. **"이 기억이 지금 이 질문과 관련이 있는가"**는 전혀 답하지 못한다. session_id컨테이너 차원이지 의미 차원이 아니며, 이 둘을 하나로 착각하는 순간 오염과 소실이라는 두 종류의 실패가 동시에 열린다.

오염(False Positive)과 소실(False Negative) — 회수가 틀리는 두 방향

방향증상왜 session_id로 못 막나
오염 — 무관한 기억이 끼어듦같은 세션 안에서 A 작업의 잔여 정보가 B 작업 답변에 섞임A와 B가 같은 session_id 아래 있으므로 격리 대상 자체가 아님
오염 — 다른 세션 정보가 끼어듦벡터 검색이 유사도만으로 판단해 다른 세션의 비슷한 문구를 끌어옴유사도는 의미적으로 가깝다는 뜻이지 소속이 맞다는 뜻이 아님
소실 — 있어야 할 기억이 안 불려옴대화는 매끄럽게 이어지는데, 창밖(윈도우 밖)의 사실은 조용히 사라짐대화 연속성 ≠ 기억 능력 — 연속성은 단기 기억이 살아있다는 증거일 뿐, 장기 회수가 정상 작동한다는 증거가 아님

"오염"과 "소실"은 반대 현상처럼 보이지만 원인이 같다 — 회수 로직이 "관련성"을 판정할 정교한 수단을 갖고 있지 않다는 것.

컨테이너 차원 하나로는 안 되는 이유 — 실전에서 뚫리는 4가지 경로

  1. 프런트 리셋은 우회 가능 — "세션 전환 시 초기화"는 UI 동작일 뿐이다. 클라이언트가 옛 session_id를 그대로 실어 재호출하면 초기화는 형해화된다. 프런트는 신뢰 경계가 아니다.
  2. 동일 세션 내 다중 작업 간섭 — 사용자가 한 세션에서 작업 A를 마치고 작업 B로 넘어가면, session_id는 완전히 정확한데도 A의 잔여 정보가 B의 추론에 섞여 들어간다. session_id가 옳은데도 발생하는 오염이라는 점에서 가장 치명적인 반례다.
  3. 벡터 검색의 구조적 부작용 — 유사도 검색은 "잘 작동할수록" 비슷한 다른 세션의 조각을 더 잘 찾아온다. 이것은 버그가 아니라 유사도 기반 검색이 정상 작동한 결과이므로, 필터 조건을 검색의 구조적 측(유사도 점수)이 아니라 **결정적 측(소속 태그)**에 둬야 막힌다.
  4. 핵심 기억 모듈을 고칠 수 없는 레거시 — 이미 배포된 Agent의 저장 로직을 건드릴 수 없을 때, "그럼 방법이 없다"는 답은 성립하지 않는다. 읽기 경로에만 개입하는 우회로가 필요하다.

기준선: 의미 분할 + 경계 상태 머신

session_id 하나짜리 필터를 **소속(session_id) + 관련성(semantic_tag)**의 이중 필터로 바꾼다.

필드역할
session_id세션·테넌트 단위의 하드 격리
block_id기억 단위 조각의 주키
semantic_tag이 조각이 어느 작업/주제에 속하는가 — 검색 시 두 번째 필터 조건
weight_level우선순위 — 낮으면 자동 정리 대상
boundary_mark작업 경계 표기 — 상태 전이의 판정 근거

검색은 session_id + semantic_tag가 동시에 맞을 때만 회수한다. 동일 세션이라도 다른 작업의 조각은 semantic_tag가 달라 걸러지고, 다른 세션의 비슷한 조각도 session_id가 달라 걸러진다.

기억 조각의 생애는 경계 상태 머신으로 흐른다.

활성 → 보관 → 망각 → 격리
매 상태 전이는 현재 작업의 「의미 경계」가 바뀌었는지를 근거로 판정한다

소실 쪽 대책: 회수 이중 경로

오염이 "잘못된 것을 불러옴"이라면 소실은 "필요한 것을 못 불러옴"이다. 대책은 경로를 하나 더 두는 것이다.

핵심 정보 ──┬─→ 벡터 저장소 (의미 유사도 회수)    ← 주 경로, 확률적
           └─→ 구조화 DB (사용자 ID·업무 ID 정확 일치) ← 보조 경로, 결정론적

의미 회수가 실패하거나 신뢰도가 낮을 때 → 보조 경로로 전환
→ 핵심 정보만큼은 "못 찾음"이 아니라 "정확히 찾음"을 보장

의미 검색은 본질적으로 확률적이라 반드시 새어나간다. 사용자 ID·업무 식별자 같은 정확 일치 키로 우회할 수 있는 경로를 별도로 두면, 핵심 정보의 소실만큼은 구조적으로 막을 수 있다.

레거시 대응: 기억 대리 계층

핵심 저장 로직을 못 고칠 때는 읽기 경로에만 개입하는 독립 계층을 새로 얹는다. 모든 입출력을 가로채 구조화 상태를 추출하고, 필터링을 거친 뒤 프롬프트에 재조립해 주입한다 — 저장 구조는 건드리지 않는다. 원 시스템에 손대지 못해 "방법이 없다"로 끝나는 것과, 대리 계층으로 관리 능력을 확보하는 것이 실무 경험치가 갈리는 지점이다.

핵심 통찰

  1. "기억 오염"의 올바른 정의는 "세션이 섞였다"가 아니라 "무관한 이력 정보가 추론 사슬에 들어왔다"이다 — 정의를 컨테이너 기준으로 잡으면 해법도 컨테이너(session_id) 하나로 수렴하고, 그 즉시 동일 세션 내 다중 작업 간섭이라는 가장 흔한 반례에 무력해진다.
  2. session_id는 "누구 것인가"를 답하고, semantic_tag는 "지금 필요한가"를 답한다 — 서로 다른 질문에 답하는 두 축이므로 하나가 다른 하나를 대체할 수 없다. 검색 필터는 반드시 이 둘의 곱이어야 한다.
  3. 프런트엔드 상태 초기화는 방어가 아니라 UX다 — 신뢰 경계는 서버 측 결정적 필터에만 있을 수 있다. 클라이언트 동작에 의존한 격리는 의도적이든 아니든 반드시 우회된다.
  4. 유사도 검색이 잘 작동할수록 교차 오염 위험도 커진다 — 이것은 모순이 아니라 구조다. 그래서 "검색 정확도를 올린다"는 오염 문제의 해법이 아니고, 결정적 필터(소속 태그)를 별도로 두는 것만이 해법이다.
  5. 대화가 매끄럽다는 것은 단기 기억이 살아있다는 증거일 뿐, 장기 회수가 정상이라는 증거는 아니다 — 이 둘을 혼동하면 회수가 조용히 실패해도 사용자 체감상 "괜찮아 보이는" 구간이 길게 이어지다가 어느 날 갑자기 티가 난다.
  6. 회수는 이중화해야 신뢰할 수 있다 — 확률적 경로(의미 검색) 하나만 두면 핵심 정보조차 "때때로 못 찾는" 상태를 벗어날 수 없다. 결정론적 경로(정확 키 매칭)를 병행해야 핵심 정보의 소실만큼은 구조적으로 방지된다.
  7. 레거시 제약은 "포기의 이유"가 아니라 "설계력의 시험대"다 — 저장 로직을 못 고치는 상황은 예외가 아니라 프로덕션의 상수다. 읽기 경로만 가로채는 대리 계층을 설계할 수 있는지가 초급과 실전 경험의 경계선이다.
  8. 기억의 생애는 상태로 관리해야지 매번 즉흥 판단하면 안 된다 — 활성/보관/망각/격리 네 상태와 그 전이 조건을 미리 정의해 두면, "이 기억을 지금 보여줘도 되는가"가 매 요청마다 새로 판단할 문제가 아니라 조회 가능한 상태값이 된다.

엔지니어링 실전 Tips

  • 필터는 언제나 이중으로: session_id(하드 격리) + semantic_tag(관련성 필터) 둘 다 걸어야 한다. 하나만 걸면 반드시 한쪽 방향으로 뚫린다.
  • 경계 표기와 기억 본문은 같은 트랜잭션에 쓸 것: 따로 쓰면 "본문은 있는데 경계 표기가 없는" 고아 조각이 생기고, 상태 머신이 그 조각을 영원히 잘못 판정한다.
  • 동시 쓰기 대비 멱등을 걸어둘 것: 같은 조각이 중복 기록될 때 영향 행수가 0이 되도록 유니크 제약을 걸면, 별도 정합성 검사 없이 중복을 즉시 감지할 수 있다.
  • 레거시 개조가 불가할 때는 읽기 전용 대리 계층부터: 쓰기 구조를 손대지 않고 필터링·재조립만 담당하게 하면 롤백 비용이 가장 낮다.
  • 정확 매칭 경로의 키(사용자 ID·업무 ID)는 쓰기 시점에 반드시 함께 저장할 것 — 사후에는 복원 불가능하다.
  • 오염률과 소실률을 별도 지표로 잡을 것: "기억 관련 사고율" 하나로 뭉치면 원인(오염인지 소실인지)을 알 수 없어 튜닝 방향을 못 잡는다.
  • 의미 분할 서비스가 죽었을 때의 강등 경로를 미리 정의할 것: 슬라이딩 윈도우 강제 절단 + 키워드 필터 + 표본 점검 같은 저비용 대체 수단을 평시에 준비해 둔다.