무엇인가
코딩 에이전트 여럿이 기억 저장소 하나를 같이 쓴다.
새 세션이 열려도 recall로 지난 작업의 맥락을 복구할 수 있다.
그 저장소 앞에 게이트웨이를 하나 세웠다.
어느 뱅크에 쓰는지는 토큰이 정하고
고칠 수 없는 형식 위반은 서버가 거절한다.
내 전역 지침은 retain/recall을 별도 지시 없이 상시 쓰라고 적는다.
recall은 프로젝트 이력이 컨텍스트에 없을 때나 설정·정책·인프라를 바꾸기 전에,
retain은 검증된 완료 단위와 근거 있는 결정이 나왔을 때 부른다.
Hindsight-Crew는 오픈소스 메모리인 Hindsight 앞에
정책을 강제하는 게이트웨이를 둔 자체 호스팅 메모리다.
업스트림 이미지는 다시 빌드하지 않고 받아 쓴다.
규칙을 지키게 하는 쪽은 서버다.
왜 만들었나
Hindsight를 고른 이유는 유지보수였다.
직접 만든 LLM(대형 언어 모델) 위키를 허물고 Hindsight로 옮겨 간 경위는
지침 파일을 깎은 글에 적었다.
문제는 권한이었다.
Hindsight의 기본 인증은 공유 키 하나다.
리포 소개문은 이 한계를
"identity→bank 강제(enforcement)가 없다"고 적는다.
키 하나로는 신원별로 쓸 수 있는 뱅크를 강제할 수 없었다.
시작은 6월 말에 내가 낸 제안이었다.
나는 "내가 했던 하인드사이트 팀메모리를 그대로 재현할 수 있게" 재현 패키지를 만들자고 했다.
다음 날 구현 계획에 "승인."이라고 답했고 9분여 뒤에 첫 커밋이 나왔다.
게이트웨이는 바로 다음 커밋에서 생겼다.
리포 소개문에 적힌 목표는
명령 하나로 고객 프로젝트에 투입할 수 있게 하는 것이다.
어떻게 동작하나
맥에서 돌아가는 colima VM(가상 머신) 안에서 컨테이너 세 개가 실행된다.
Hindsight 본체는 내부망에만 열려 있고
에이전트가 접속하는 입구는 게이트웨이 하나뿐이다.
나머지 하나는 웹 UI를 루프백으로 연결하는 터널이다.
리랭크는 VM 밖의 맥에서 상주하는 사이드카가 맡는다.
요청이 들어오면 게이트웨이는 신원부터 판정한다.
토큰으로 신원과 개인 뱅크, 팀 뱅크를 정하고
클라이언트가 적어 보낸 뱅크 인자는 신뢰하지 않고 덮어쓴다.
팀 뱅크에 남는 작성자 표기도 호출자의 신원으로 바꾼다.
저장 규칙은 코드 대신 뱅크별 정책 파일에 선언한다.
개인 뱅크 템플릿에는 태그 필수, 네 가지 접두(project·kind·topic·user),
kind의 닫힌 목록, 내용을 대괄호 라벨로 시작하는 형식이 규칙으로 선언돼 있다.
7월 말 계획서는 규칙 위반을 HTTP 403 대신 MCP(모델 컨텍스트 프로토콜) 오류 응답에
규칙 ID와 수정 예시를 실어 돌려주기로 정했다.
모델이 그 응답을 읽고 스스로 고쳐서 다시 보내도록 하려는 설계다.
서버에서 막기로 한 근거는 7월 말 계획서에 적힌 한 줄이다.
"MCP 스펙상 클라이언트에 스키마 검증 의무가 없어 서버측 검증이 유일한 하드 강제 지점"
접두가 빠지거나 대문자가 섞인 태그처럼
기계적으로 고칠 수 있는 위반은 거절하지 않고 서버가 직접 고친다.
같은 규칙을 지침·훅·게이트웨이에 각각 둘 때 드는 비용은
다섯 런타임 훅 글에서 따져 보았다.
업스트림 이미지는 태그 대신 digest로 고정한다.
v0.8.3에서 v0.9.1로, 다시 v1.0.0으로 두 번 교체했다.
업스트림 이미지를 다시 빌드하지 않는
방식에도 예외가 있었다. 마운트다. 업스트림 모듈을 고친 사본을 덮어씌우던 마운트는
v1.0.0이 같은 기능을 제공하면서 걷어냈다.
번들 CLI가 claude-opus-5-5를 거부하기 때문에
새 Claude CLI를 덮어씌우는 마운트는 10월 19일 main의 compose에도 남아 있다.
에이전트 표지를 세어 보면 이렇다.
main의 비머지 커밋 311건 중 256건에 에이전트 표지가 있다
(10월 19일 기준이며, 커밋 수는 변경 규모가 아니고 표지가 없다고
내가 직접 쓴 커밋이라는 뜻도 아니다).
내 몫은 설계와 운영 판정이다.
v1.0.0을 그대로 받아도 되는지 분석을 맡긴 세션에서, 롤백을 두고 나는 이렇게 물었다.
"기존 잘굴러가던 버전은 두고 새로운버전으로 붙여보는 테스트를 같이 진행할건가?"
새 버전은 운영 스택 옆에 둔 사본 스택에서 먼저 띄웠다.
전환 당일에는 운영을 멈추고 데이터 볼륨을 새 볼륨으로 복사한 뒤,
새 볼륨에서만 마이그레이션을 실행했다.
10월 16일 전환 기록에 따르면 6개 뱅크의 전 항목이 동일했고
전체 검증 게이트도 10/10으로 통과했다.
써 보니
내가 꼽은 가장 큰 장점은 세션이 바뀌어도 맥락이 이어진다는 점이다.
10월 초 한 세션에서 내가 쓰는 방식을 설명하며 이렇게 적었다.
"새 세션이와도 recall이면 맥락복구가 가능함. (이게 제일큰 장점)"
같은 설명에는 잘못된 맥락이 저장될 수 있다는 단점도 적었고
그 단점은 "최근 reflect로 극복함"이라고 썼다.
감사 로그에 내 개인 뱅크가 처음 기록된 날은 7월 24일이다.
그날 저녁 나는 에이전트에게 물었다.
"정확히 어떤조건에서 너가 동작하고있는지 모르겠네"
에이전트는 그 조건을 전역 지침에 적었다고 답했다.
7월 27일 git에 처음 들어간 지침에는 recall 트리거와 retain 트리거가 네 개씩 있다.
게이트웨이 감사 로그를 6월 26일부터 10월 19일 새벽까지 집계하면,
내 개인 뱅크로 접근 제어를 통과한 MCP 호출 가운데
retain은 1,542건, recall은 1,202건이다.
호출이 있었던 날은 7월 24일부터 10월 18일 사이의 70일(UTC 날짜 기준)이다.
개인 뱅크로 통과한 MCP 호출을 월별로 보면 7월 242건, 8월 381건, 9월 1,653건,
10월(18일까지) 1,457건이다.
이 수치는 접근 제어를 통과한 호출 수이므로 저장된 기억의 수와 같지 않다.
사용 세션 수도 아니고 스크립트·검증 호출과 이 집계를 위한 조회 몇 건이 섞여 있으며
일부는 그 뒤 형식 규칙에 걸렸다.
같은 날 새벽의 뱅크 통계는 world 1,833건, experience 476건,
observation 4,189건이다.
observation은 서버가 다른 기억을 합쳐서 다시 쓴 기록이므로
저장 횟수로 해석하지 않는다.
실패와 고친 것
첫 실패는 첫날에 나왔다.
첫 커밋 22분 뒤의 커밋은 고정한 Hindsight v0.8.3을 실제로 띄워 보니
MCP가 뱅크를 경로 대신 X-Bank-Id 헤더로 받는다는 것을 확인했다고 적었다.
메시지에는 "CONTRACT DIVERGENCE found"라고 적혔다.
3분 뒤 게이트웨이를 실측한 호출 형식에 맞췄다.
어긋남을 잡는 검사는 첫 커밋에 자리만 있었고 그날 오후에 라이브로 배선됐다.
7월 말에는 복원 테스트 때문에 운영이 중단됐다.
메모리를 확보하려고 운영 Hindsight를 멈추고
종료 트랩에서만 다시 켜는 구조였기 때문에, 중단된 실행이 운영을 꺼 둔 채로 남았다.
운영은 2시간 동안 내려가 있었지만 그동안에도 /healthz 는 계속 200을 돌려줬다.
그 200은 운영이 살아 있다는 신호가 아니었다.
같은 날 중단 신호를 처리하는 트랩을 추가하고 여유 메모리가 있으면 운영을 아예 멈추지 않도록
바꿨다.
9월에는 조회 범위가 조용히 넓어져 있던 것이 드러났다.
REST 프록시가 쿼리 파라미터를 사전으로 변환하면서 반복되는 키의 마지막 값만 남겼다.
?tags=a&tags=b 가 tags=b 로 줄어들면서
교집합 조회가 태그 하나짜리 조회로 바뀌었다.
실제 사고에서 2건이어야 할 결과가 811건으로 나왔다.
순서까지 보존해서 넘기도록 고치자 같은 쿼리가 다시 2건을 돌려줬다.
10월 초에는 규칙을 건너뛰는 경로가 발견됐다.
게이트웨이는 REST 경로가 정확히 memories 일 때만 저장 규칙을 적용했기 때문에,
끝에 슬래시를 붙이면 태그·내용 규칙을 건너뛸 수 있었다.
다른 이슈의 최종 리뷰에서 코드를 읽다가 찾아냈고
1차 수정 뒤에도 %2E%2E 를 넣은 변형이 다시 나왔다.
이 변형은 한 뱅크로 접근 제어(ACL)를 통과한 뒤 다른 뱅크의 경로로 넘어갔다.
그래서 이런 비정규 경로는 정규화하지 않고 400으로 거부한다.
커밋에 남긴 이유는 "..를 접는 순간 ACL 판정 대상 뱅크가 바뀐다"였다.
나머지는 한 줄씩 적는다.
| 시기 | 실패 | 고친 것 |
|---|---|---|
| 7월 | colima가 멈췄는데 재시작 정책이 없어 2주 동안 다운 | 컨테이너에 restart: unless-stopped 를 걸고 colima 자동 시작과 함께 설정 |
| 9월 | 작업 트리 빌드가 main 반영보다 앞서 머지한 PR이 재빌드되지 않아 운영에서 누락 | 배포 확인 기준을 이미지 생성 시각 대신 컨테이너 안 코드 해시 대조로 변경 |
| 9월 | 게이트웨이가 본문을 끝까지 읽은 뒤에 응답해서 약 100초 걸리는 폴백 reflect가 한 클라이언트에서 시간 초과 | 수정할 필요가 없는 응답은 스트리밍 패스스루로 넘겨 응답 헤더 시점을 107.5초에서 0.0초로 단축 |
| 10월 | reflect 기본값 킬스위치가 코드에만 있고 compose에는 배선되지 않음 | 한 줄을 배선하고 docker compose config 로 0과 1 확인 |
| 10월 | VM 비정상 재부팅 두 번이 로그 파일에 NUL 구간을 남겨 docker logs 가 13929줄 중 7371줄만 읽음 | NUL을 같은 길이의 공백으로 제자리 치환해 배포 기록상 최근 10분 로그가 0줄에서 279줄로 회복 |
| 10월 | 상태 확인용 launchctl print 가 게이트웨이 토큰을 터미널에 평문으로 출력 | launchd를 조회하는 모든 명령에 grep 필터를 강제 |
저장 실패를 성공으로 오인한 사건은 저장 성공 오보 글에,
태그 어휘 정리는 태그 글에 따로 적었다.
실수가 나는 길목에 기억을 붙인 장치는 길목 글에,
리랭크 설정을 롤백한 일은 카나리 글에 있다.
지금 상태
운영 중이다.
10월 19일 새벽에 /healthz 는
200과 "upstream_ok":true 를 돌려줬다.
main의 마지막 커밋은 10월 18일 저녁에 머지한 274번 PR이고
그때까지 커밋 415건, 머지된 PR 108건, 테스트 함수 2,022개가 쌓였다.
v1.0.0 전환은 10월 16일 저녁에 끝났고 관찰 기간은 10월 23일까지다.
통과 조건은 "7일 동안 실패 0"이다.
관찰 도중 멘탈 모델 갱신이 300초 상한에 걸려 실패했고
상한을 올린 뒤 재기동했다.
상한에 걸려 취소되면 빈 모델이 남는 문제는 아직 열려 있는 이슈다.
구 볼륨과 이전 이미지처럼 롤백에 필요한 자원은 관찰을 통과할 때까지 지우지 않는다.
전환 기록 커밋은 롤백 절차 자체는 실행하지 않았다고 적었다.
리포는 비공개다.
삭제한 문서의 히스토리를 검토하는 이슈가 7월부터 열려 있다.
다른 맥(Apple Silicon 전용)에 한 번에 설치하는 명령은
10월 초에 만들었다.
남의 맥에 설치하는 명령은 있지만 10월 19일 기준 찾아본 곳에서는 실제로 설치한 기록이 없다.
구 볼륨과 이미지,
자격증명과 문서를 정리하는 작업은 내 승인이 필요한 티켓으로 열려 있다(10월 19일 기준).