XGEN 15일 무료 체험 — 설치 없이 브라우저에서 바로 시작하세요체험 신청
PlateerAI Labs
·Reading Time | 3 min
작성 | 김진수

가까운 기억이 항상 이기게 두면 안 됐습니다(8편)

세션 요청이 플랫폼 정책보다 구체적이니 우선해야 할 것 같았습니다. 그렇게 두면 사용자가 정책을 덮어쓸 수 있었습니다. 실행 간 기억의 범위와 종류를 나누고 충돌을 푼 과정입니다.

가까운 기억이 항상 이기게 두면 안 됐습니다(8편) — 커버 일러스트Series · 하네스 개발기시리즈 8번째 글 · 전체 10편 보기

에이전트에 기억을 붙이면서 저희가 먼저 정한 규칙은 "구체적인 쪽이 이긴다"였습니다. 이번 대화에서 방금 합의한 내용이 일반적인 사용자 선호보다 구체적이니 그쪽을 따르면 됩니다. 그 규칙대로 두면 사용자가 플랫폼 정책도 덮어쓸 수 있다는 것을 나중에 알았습니다. 기억을 얼마나 저장할지가 아니라 무엇이 무엇을 이기는지를 정하는 일이었습니다.


메모리를 과거 대화를 보관하는 기능으로 시작했습니다

모델과 실행 설정을 통제해도 다음 실행은 여전히 처음부터 시작했습니다. 한 세션에서 확인한 사용자 선호, 도구 사용상의 주의점, 판정에서 반복해 나온 교정 사항이 다른 대화나 워크플로우로 이어지지 않았습니다.

가장 단순한 해법은 대화 전문을 다음 실행에 다시 넣는 것입니다.

넣어보니 두 가지가 걸렸습니다. 비용이 커지는 것은 예상했고, 예상하지 못한 쪽은 오래된 판단과 현재 요청이 한 컨텍스트에서 충돌하는 것이었습니다. 지난주에 "이번에는 짧게"라고 했던 문장이 이번 요청에도 그대로 살아 있었습니다.

그래서 정의를 바꿨습니다. 메모리는 과거 대화를 많이 보관하는 기능이 아니라, 다음 판단을 바꿀 가치가 있는 작은 상태를 남기되 얼마나 오래, 누구에게, 어떤 우선순위로 적용할지를 함께 정하는 일입니다.

6월 말 저장 구조를 먼저 만들고, 엔진의 저장·회상 지점과 제품 저장소를 연결했습니다. 이어서 메모리를 네 범위로 나누고 조회와 만료, 삭제 경로를 붙였습니다.

지금 필요한 메모와 다음에도 유효한 기억은 수명이 달랐습니다

긴 문서를 읽다가 중요한 문단을 표시하거나, 여러 도구 결과 중 다시 볼 항목을 고르는 일도 메모리처럼 보입니다.

그런데 그 검색 결과가 다음 주의 다른 요청에도 유효하다는 보장은 없습니다. 현재 과제를 끝내는 데 필요한 관측과, 실행을 넘어 남길 교훈은 수명이 다릅니다.

한 실행 안에서만 쓰는 작업 메모는 따로 뒀습니다. 모델이 유지·확인·폐기 같은 의미를 붙인 항목을 모으고, 내용 지문으로 중복을 줄이며, 개수 상한 안에서 우선순위를 정합니다. 처음부터 본문을 다 펼치지 않고 목록과 식별자를 먼저 보여준 뒤 필요한 것만 다시 읽게 합니다. 실행이 끝나면 이 집합은 사라집니다.

실행 사이의 기억은 별도 저장소를 씁니다. 품질 판정이 답을 반려했다면 일반 오류 문자열이 아니라 구체적인 검증 피드백을 교훈 후보로 남깁니다. 정상 종료 뒤에는 보조 모델이 입력과 최종 출력을 읽고 다음 실행에 쓸 후보를 뽑습니다.

현재 실행의 관측      → 작업 메모   → 실행 종료와 함께 폐기
판정 피드백·완료 결과  → 후보 추출   → 장기 저장소 → 다음 실행에서 선택적 회상

여기서 조심해야 할 것이 하나 있습니다. 후보를 추출하는 것과 저장을 승인하는 것은 같은 일이 아닙니다. 영향 범위가 큰 항목은 자동 추출만으로 올리지 않고 별도 승인이나 결정론적 검사 경로를 두는 편이 안전합니다.

답을 만드는 도중에 새 기억을 넣으면 검증되지 않은 내용이 순환합니다

무엇을 저장할지 정해도 언제 읽는지가 불분명하면 효과가 실행마다 달라집니다.

처음에는 실행 중간에 새 메모리를 바로 주입하는 방식을 생각했습니다. 그렇게 두면 같은 실행 안에서 아직 검증되지 않은 내용이 사실처럼 돌아다닐 수 있습니다. 모델이 방금 만들어낸 요약이 그 실행의 근거가 되는 식입니다.

그래서 저장과 회상을 실행 경계의 양쪽에 뒀습니다.

쓰기는 판정 결과가 확정된 뒤 일어납니다. 재시도라면 검증 피드백에서 교훈 후보를 뽑고, 완료라면 입력과 최종 결과에서 다음에도 유효해 보이는 항목을 정리합니다. 읽기는 새 실행의 입력을 준비할 때 일어납니다.

경계를 두자 테스트 조건도 분명해졌습니다. 판정 전에는 장기 메모리가 생기지 않아야 하고, 다음 실행의 첫 호출에는 선택된 메모리가 이미 들어 있어야 합니다. 저장 API가 성공했는지가 아니라 실제 다음 판단까지 도달했는지를 확인했습니다.

기능을 붙이고 나서 "저장됐습니다" 로그를 보고 완료로 넘어가기 쉽습니다. 저장은 중간 사건이고, 확인해야 할 것은 그 저장이 다음 행동을 바꿨는가입니다.

범위는 검색 필터가 아니라 수명주기였습니다

범용 엔진은 제품의 사용자와 워크플로우 체계를 모릅니다. 그래서 제품 연결 계층에서 메모리를 네 범위로 연결했습니다.

session    이번 대화에서 합의한 임시 선택
workflow   특정 흐름이 반복해서 지켜야 할 출력 관례와 교훈
user       언어와 표현 방식 같은 개인 선호
platform   전체 서비스가 지켜야 할 정책과 조직 수준의 제한

처음에는 이것을 검색 필터용 태그로 다뤘습니다. 조회할 때 범위로 걸러내면 된다고 봤습니다.

그런데 범위는 누가 읽을 수 있는지, 언제 만료되는지, 원본 객체를 지울 때 어디까지 정리해야 하는지를 정하고 있었습니다. 세션 메모리가 다른 세션으로 넘어가면 안 되고, 워크플로우를 삭제했다면 그 범위의 기억도 더 이상 조회되면 안 됩니다.

범위는 태그가 아니라 그 기억의 수명주기였습니다. 넓은 범위일수록 영향과 권한이 커지므로 플랫폼 메모리는 자동 추출만으로 쉽게 만들지 않습니다.

권한 모델을 나중에 붙이려고 미뤄두면 대체로 이런 자리에서 되돌아옵니다. 데이터를 어디에 담을지 정하는 순간 이미 누가 읽을 수 있는지도 정하고 있습니다.

가까운 기억이 이기게 두자 사용자가 정책을 덮어쓸 수 있었습니다

같은 키를 가진 기억이 여러 범위에 있을 때 무엇을 고를지 정해야 했습니다.

처음 규칙은 단순했습니다. 현재 작업에 가까운 범위가 이깁니다. 세션이 워크플로우보다 구체적이고, 워크플로우가 사용자 선호보다 구체적이니까요.

이 규칙을 그대로 적용하면 플랫폼 정책과 세션 요청이 충돌할 때 세션이 이깁니다. 사용자가 대화 중에 한 말로 조직 정책을 해제할 수 있다는 뜻입니다.

반대로 모든 경우에 넓은 범위를 먼저 따르게 하면 워크플로우별 형식과 개인 선호가 전부 의미를 잃습니다.

한쪽 방향으로는 풀리지 않았습니다. 우선순위가 범위만으로 정해지지 않기 때문입니다. 그래서 기억에 범위뿐 아니라 종류를 함께 저장했습니다.

제약(constraint)   넓은 범위가 우선   플랫폼 제약은 세션 선택으로 해제되지 않는다
선호(preference)   좁은 범위가 우선   지금 작업에 가까운 요구가 더 구체적이다
교훈(lesson)       좁은 범위가 우선
사실·결정          출처와 시점을 함께 본다

예를 들어 사용자가 평소 자세한 답을 선호해도, 워크플로우가 세 줄 요약을 요구하면 이번 작업에는 워크플로우 쪽을 따릅니다. 그러나 개인정보를 출력하지 말라는 플랫폼 제약은 둘 다보다 앞섭니다.

상충하는 문장을 전부 프롬프트에 넣고 모델에게 해결을 맡기지 않았습니다. 회상 단계에서 무엇을 적용할지 먼저 결정합니다.

4편에서 품질 판정과 정책 게이트를 다른 문턱에 둔 것과 같은 구분이 여기서 다시 나왔습니다. 지켜야 하는 것과 맞춰주면 좋은 것은 같은 규칙으로 정렬되지 않습니다.

저장량이 아니라 이번에 읽힐 양을 제한했습니다

범위와 우선순위를 정해도 장기 메모리가 계속 늘어나면 컨텍스트가 다시 커집니다. 관련 있어 보이는 것을 다 넣는 방식은 대화 전문을 붙이는 것과 크게 다르지 않습니다.

회상 경로는 항목별 길이와 항목 수를 먼저 제한하고 마지막에 전체 예산을 적용합니다. 같은 키의 충돌을 정리한 뒤 현재 범위에 가깝고 우선순위가 높은 항목을 고릅니다. 모델에 전달할 때는 출처와 적용 범위를 함께 표시해 사용자 선호와 플랫폼 제약을 구분할 수 있게 합니다.

선택되지 않은 메모리는 삭제되는 것이 아니라 이번 컨텍스트에서만 빠집니다.

여기에 아직 고칠 자리가 남아 있습니다. 현재 구현은 전체 문자 상한에서 문자열을 자를 수 있어서 마지막 항목의 의미가 깨질 수 있습니다. 항목 전체를 우선순위대로 채우고 들어가지 않는 것은 통째로 제외하는 방식이 다음 보강 지점입니다.

저장 권한과 읽기 권한도 따로 확인합니다. 일반적인 비밀 키워드와 프롬프트 인젝션 패턴에 걸리는 후보는 거부하지만, 라벨 없이 값만 들어오거나 새 민감 필드가 추가된 경우까지 자동으로 알아내지는 못합니다. 제품별 정책과 회귀 테스트를 함께 둬야 합니다.

삭제 훅을 붙였다고 정리가 보장되지는 않았습니다

메모리는 원본의 수명주기를 따라 움직여야 합니다. 워크플로우를 삭제하면 그 범위의 메모리와 실행 상태를 정리하고, 세션이 끝나면 세션 범위를 정리하는 훅을 붙였습니다. 만료된 항목은 저장소에 남아 있어도 회상 결과에서는 빠집니다.

이 삭제 연결은 본래의 워크플로우 삭제나 세션 종료를 막지 않는 방식입니다. 메모리 저장소가 잠시 장애여도 사용자의 본 작업 전체를 실패시키지 않는 장점이 있습니다.

대가가 있습니다. 정리 실패가 경고로만 남고 항목이 잔존할 수 있습니다. 운영에서는 재정리 경로와 잔존 범위를 함께 봐야 합니다.

그리고 이 삭제 연결은 당시 기능 브랜치와 로컬 검증 범위였으며, 운영 전체에 반영된 상태로 보아서는 안 됩니다. 오래된 기억의 신뢰도를 자동으로 낮추는 감쇠와 비슷한 기억을 주기적으로 병합하는 작업도 상시 실행 경로에는 아직 없습니다.

관련 함수가 존재하는 것, 제품 수명주기에 연결된 것, 운영에 배포된 것은 서로 다른 사실입니다. 기억 기능은 특히 이 셋이 뭉뚱그려지기 쉽습니다. 저장은 되는데 지워지지 않는 상태가 오래 눈에 띄지 않기 때문입니다.

기억의 품질은 무엇을 남겼는지가 아니라 무엇이 이기는지에 있었습니다

이 편에서 실제로 시간을 쓴 곳은 무엇을 저장할지가 아니었습니다.

현재 실행의 관측과 다음 실행의 교훈을 나누고, 범위가 수명주기라는 것을 인정하고, 제약과 선호가 반대 방향으로 우선한다는 것을 찾고, 읽힐 양과 삭제 경로를 통제하는 일이었습니다.

기억을 붙이는 일은 저장소를 고르는 일이 아니라 충돌 해결 규칙을 쓰는 일이었습니다. 저장은 하루면 되고, 무엇이 무엇을 이기는지는 정해두지 않으면 매 실행마다 다르게 결정됩니다.

다음 편에서는 시선을 시간축에서 현재 실행으로 돌립니다. 연결된 도구와 출력 목적지를 모델에게 어느 수준까지 보여줄지의 문제인데, 여기서도 "보인다"와 "부를 수 있다"가 같은 말이 아니었습니다.


이전 편 → 30배 차이가 모델의 차이가 아니었습니다 (7편)

다음 편 → 도구가 목록에는 보이는데 부를 수는 없었습니다 (9편)

#하네스#메모리#상태 관리
블로그 목록으로