AI 코딩 에이전트의 세션 간 기억 설계 — 저장이 아니라 승격, 그리고 스코프 리콜
LLM 기반 코딩 에이전트를 몇 주 써 보면 같은 현상을 만납니다. 어제 함께 알아낸 함정 — 이를테면 "이 에러 메시지는 사실 다른 원인이다" — 을 오늘 새 세션의 에이전트는 모릅니다. 다시 재현하고, 다시 오진하고, 다시 같은 결론에 도달합니다.
흔한 반응은 "대화를 저장하자"입니다. 하지만 로그를 쌓는 것과 기억하는 것은 다른 문제입니다. 저장은 쉽고, 관련 있는 것만 정확한 순간에 꺼내는 것이 어렵습니다. 이 글은 세션 간 기억을 세 개의 하위 문제 — 쓰기 경로(무엇을 저장할지), 승격(신뢰를 어떻게 쌓을지), 읽기 경로(어떻게 꺼낼지) — 로 나눠 데이터 구조 수준에서 다룹니다. 특정 제품과 무관하게 LLM 에이전트를 만드는 사람이라면 적용됩니다.
세션은 왜 상태를 잃나
두 가지 구조적 이유가 있습니다.
- 무상태 추론: LLM 호출은 요청-응답으로 끝납니다. 그 안에서 얻은 통찰은 다음 호출로 자동 전파되지 않습니다.
- 유한 컨텍스트: 윈도우는 유한하고, 긴 세션의 앞부분은 밀려나거나 요약되며 소실됩니다.
그래서 세션은 매번 백지에서 출발합니다. 대화 전체를 다음 세션에 그대로 이어붙이는 것은 답이 아닙니다 — 무관한 20개 파일의 탐색 기록까지 컨텍스트로 실어 노이즈를 만들고, 윈도우만 잡아먹습니다. 필요한 건 압축된, 관련 있는, 신뢰할 수 있는 기억입니다.
쓰기 경로 — 무엇을 저장할 것인가
모든 것을 저장하면 아무것도 못 꺼냅니다. 저장 대상을 거르는 단 하나의 필터가 유효합니다:
코드를 읽으면 알 수 있는 것은 저장하지 않는다.
모델 필드, API 경로, 사용 중인 라이브러리는 다음 세션이 코드를 읽으면 복원됩니다. 저장할 가치는 코드에 드러나지 않는 것에만 있습니다. 실무에서 이는 세 부류입니다.
- 에러의 실제 원인 — 에러 메시지가 진짜 원인과 다른 경우. 다음에 같은 시그니처를 만나면 오진을 건너뜁니다.
- 왜(Why) — 코드에 "무엇"은 있으나 "이유"가 없는 것. 예: 이 타임아웃이 왜 30분인가 → 외부 정책.
- 암묵적 제약 — 코드 어디에도 없지만 지켜야 하는 것. 예: 특정 외부 API는 한도 초과 시 장시간 차단된다.
이 중 에러 해결 기억은 구조화가 중요합니다. 자유 텍스트로 저장하면 나중에 매칭이 안 됩니다. 최소한 이 정도 필드가 필요합니다:
{
"errorSignature": "TypeError: fs.existsSync is not a function",
"errorCategory": "module-resolution",
"stack": ["node", "typescript", "esm-nodenext"],
"rootCause": "fs-extra를 `import * as fs`로 가져오면 ESM interop에서 동기 메서드가 노출되지 않음",
"fixApproach": "`import fs from 'fs-extra'` (default import)로 교체",
"scope": ["packages/*/src/**"], // 이 기억이 유효한 파일 범위
"confidence": 0.4, // 초기값; 검증으로 갱신
"maturity": "observation" // observation → pattern → rule → constraint
}
errorSignature는 정규화(경로·해시 마스킹)해 매칭 키로 씁니다. scope와 confidence, maturity는 뒤의 읽기 경로·승격에서 핵심이 됩니다.
승격 — 저장이 아니라 지위 상승
한 번의 관측을 곧바로 규칙처럼 강제하면 오판을 부릅니다. 한 프로젝트에서 우연히 맞은 해법이 다른 곳에선 틀릴 수 있으니까요. 그래서 기억은 재검증될 때마다 지위가 오르는 사다리로 다룹니다.
관측 observation ──(같은 시그니처 2회+)─────────▶ 패턴 pattern
패턴 pattern ──(타 프로젝트 재검증 prevented)─▶ 규칙 rule
규칙 rule ──(위반 시 반드시 깨짐)──────────▶ 제약 constraint (게이트로 강제)
각 전이에는 명시적 트리거가 있습니다.
- 관측 → 패턴: 동일
errorSignature(정규화 후)의 같은fixApproach가 2회 이상 관측. dedup은 시그니처 기준. - 패턴 → 규칙: 다른 프로젝트/컨텍스트에서 재사용되어 검증 통과. 여기서 검증 신호가 핵심입니다 — 그 기억을 적용했을 때 실제로 문제가 예방됐는지(
prevented)를 되먹입니다. - 규칙 → 제약: 위반하면 반드시 깨지는 종류. 이 단계는 조언이 아니라 게이트가 됩니다(작업 전 자동 차단).
confidence는 검증 결과로 갱신합니다. 적용 후 문제가 예방되면 올리고(prevented), 여전히 실패하면 내립니다(still_failed). 베타 분포식 사후 업데이트로 두면 소표본에서도 과신하지 않습니다 — 관측 2건으로 규칙 행세를 하지 못하게 하는 안전장치입니다.
읽기 경로 — 어떻게 꺼내 쓰나 (스코프 리콜)
여기가 대부분의 순진한 구현이 무너지는 지점입니다. 저장된 기억을 전부 프롬프트에 붙이면, 기억이 많아질수록 컨텍스트가 오염되고 정작 중요한 신호가 묻힙니다. 저장이 문제가 아니라 리콜의 정밀도가 문제입니다.
두 축으로 좁힙니다.
- 관련도(relevance): 지금 다루는 에러/작업의 시그니처·태그·스택과 매칭되는 기억만. 임베딩 유사도든 태그 교집합이든, 상위 소수만 주입합니다.
- 범위(scope): 기억의
scope(파일 경로 glob)가 지금 편집 중인 파일과 교차할 때만 주입. 결제 서비스를 건드리는데 인증 도메인의 quirk를 부를 이유가 없습니다.
예를 들어 payment.service.ts를 수정하려는 순간, 리콜은 scope가 그 경로를 포함하는 도메인 지식(예: "이 결제 재시도는 카드사 정책상 30분 제한")을 먼저 올립니다. 이는 변경의 영향 범위(blast radius)를 따라 기억을 끌어오는 것과 같습니다 — 편집 대상과 그 호출 체인에 걸린 기억만 활성화됩니다.
주입량에는 상한을 둡니다. 관련도 상위 N개 + 제약(constraint)은 항상 포함(위반 시 깨지므로), 나머지는 컷. 상한이 없으면 리콜이 곧 오염원이 됩니다.
함정과 가드
해피패스만 보면 반드시 물립니다. 실전에서 나오는 실패 모드 세 가지와 가드:
- 낡은 기억(stale): 기억이 이미 사라진 파일·플래그·심볼을 가리킬 수 있습니다. 가드는 사용 전 검증 — 기억이 특정 심볼/경로를 언급하면, 주입 전에 그것이 아직 존재하는지 확인하고 없으면 강등·격리합니다.
- 에러 ≠ 원인: 위 스키마 예시가 실제 사례입니다.
fs.existsSync is not a function은 표면적으로fs문제를 가리키지만, 실제 원인은 ESM(NodeNext)에서import * as fs from 'fs-extra'가 동기 메서드를 노출하지 않는 interop 동작입니다. 메시지만 보고fs설치를 의심하면 시간을 버립니다 — 이런 "메시지와 원인의 괴리"야말로 저장 1순위입니다. - 오염·과속 승격(poisoning): 검증 없이 관측을 규칙으로 올리면 틀린 기억이 전파됩니다. 가드는 승격에 검증 게이트를 강제하는 것 — 재검증
prevented없이는 규칙이 되지 못합니다.
격리와 팀 승격
기억이 개인에서 팀으로 넘어가면 성격이 바뀝니다. 한 사람이 검증한 해법이 규칙이 되면 다른 사람은 그 함정을 처음부터 피합니다. 그러나 무분별한 공유는 오판원입니다 — 프로젝트마다 제약이 다르기 때문입니다.
그래서 기억은 범위 키로 격리해 축적합니다(프로젝트/팀/개인). 좁은 범위에서 충분히 재검증된 것만 넓은 범위로 승격하고, 승격 시 프로젝트 고유 식별자·비밀은 마스킹합니다. 서로 다른 프로젝트의 신호를 평균 내지 않는 것이 원칙입니다 — 평균은 맥락을 지웁니다.
정리
세션 간 기억의 어려움은 저장이 아니라 관련도와 신뢰에 있습니다. 정리하면 네 부분입니다:
- 쓰기 경로: "코드로 복원 불가한 것"만, 구조화해서.
- 승격: 관측 → 패턴 → 규칙 → 제약, 각 전이에 검증 트리거.
- 읽기 경로: 관련도 + 범위로 좁힌 스코프 리콜, 주입 상한.
- 가드: 사용 전 검증(stale), 승격 게이트(poisoning), 마스킹(격리).
RAG를 코드베이스에 적용해 본 적이 있다면 익숙한 긴장일 것입니다 — 검색은 쉽고, 관련도는 어렵습니다. 에이전트 메모리는 거기에 "신뢰(승격)"라는 축을 하나 더 얹은 문제입니다.
Want posts like this weekly?
Subscribe for AI dev-workflow insights.