← 메모리

태그 어휘 241종을 158종으로 접었다

같은 뜻으로 갈라진 태그가 조회를 조용히 새게 했다. 별칭 68쌍을 정책에 선언해 서버가 저장 시점에 한 번 고치도록 바꾸고, 개인 뱅크를 이틀에 걸쳐 라이브로 재태깅한 기록

목차
  1. 문제
  2. 접두 네 개와 소문자 슬러그만 통과한다
  3. 별칭은 한 홉이고 순서가 있다
  4. 고칠 수 없는 것은 정직하게 실패한다
  5. 대청소는 이틀 걸렸다
  6. 남은 여섯 행은 청소 누락이 아니었다
  7. 바이트 단위로 동일하다는 자기 보고가 거짓이었다
  8. 결론
  9. 한계·재검토 트리거

개인 메모리 뱅크의 태그 어휘를 241종에서 158종으로 줄였다.
줄어든 83종은 같은 뜻을 저마다 다르게 적은 표기였다.
갈라지는 동안 태그로 거는 조회는 오류를 한 번도 내지 않았다.

문제

에이전트는 작업을 마칠 때마다 기록 한 줄을 남기고 거기 태그를 붙인다.
태그 문자열을 자유 입력으로 두면 같은 뜻이 매번 다른 글자로 도착한다.
완료를 뜻하는 태그 하나가 kind:done, kind:task-done, kind:work-log, kind:cleanup으로 갈라져 몇 달치가 쌓였다.
한 프로젝트를 가리키는 태그는 접두까지 흔들려 project:hindsighttopic:hindsight가 같이 살아 있었다.

값을 치르는 쪽은 조회다.
없는 태그를 걸어도 조회는 오류를 내지 않는다.
빈 목록을 정상 응답으로 돌려준다.
에이전트는 그 빈 목록을 "기록이 없다"로 읽는다.
그래서 어휘 드리프트의 증상은 침묵으로 나타난다.
막혔다고 선언하기 전에 먼저 조회하라는 줄이 지침에 따로 들어가 있는 이유가 이것이다.

접두 네 개와 소문자 슬러그만 통과한다

첫 번째 조치는 형태를 좁히는 것이었다.
개인 뱅크의 태그는 접두와 슬러그를 콜론으로 이은 형태만 받는다.
접두는 project, kind, topic, user 네 개이고 슬러그는 소문자와 숫자와 하이픈뿐이다.
태그가 하나도 없는 기록은 아예 받지 않는다.

이 문법은 뱅크 단위 옵트인이다.
기본값은 무제한이라 정책을 선언하지 않은 뱅크는 아무 제한도 받지 않는다.
같은 게이트웨이 뒤에서 개인 뱅크는 접두 네 개를, 어떤 프로젝트 전용 뱅크는 접두 열 개와 한국어 본문 라벨을 따로 강제한다.
어휘는 뱅크마다 따로 정해진다.

별칭은 한 홉이고 순서가 있다

형태를 좁혀도 이미 갈라진 어휘는 그대로다.
그래서 옛 태그를 정본 태그로 잇는 별칭 맵을 뱅크 정책에 선언했다.
현재 개인 뱅크의 별칭은 68쌍이다.
그 정책이 정본 어휘의 단일 진실 원천이다.
사본을 문서에 전재하지 않은 이유도 같다. 사본을 만들면 드리프트 지점이 하나 더 생긴다.

도착한 표기저장된 태그
kind:done kind:task-done kind:task-completion kind:work-log kind:work kind:cleanupkind:completion
project:hindsight topic:hindsight topic:hindsight-crew project:hindsight-tokenproject:hindsight-crew
topic:gateway topic:hindsight-gatewaytopic:mcp-gateway

재작성 지점은 기록을 저장할 때 한 번이다.
기계적 정규화가 끝난 뒤, 문법 검사가 시작되기 전에 딱 한 홉을 간다.
순서가 규칙의 일부다. 별칭 키가 정규화 이후 형태로 선언돼 있다.
접두 없이 도착한 aside는 먼저 topic:aside가 되고 그 형태로 맵을 조회한다.
정규화 앞에서 조회하면 접두 없는 옛 태그를 전부 놓친다.
문법 검사를 별칭 뒤에 남겨 둔 이유도 하나다. 별칭이 강제의 구멍이 되면 안 된다.

한 홉을 보장하는 장치는 로드 시점 검증이다.
어떤 정본 값이 다시 다른 별칭의 키로 등장하는 선언은 게이트웨이가 뜨는 순간 거부한다.
요청 시점에 여러 홉을 따라가는 해석은 순환 입력에서 정지가 보장되지 않는다.
그 위험은 요청 경로 대신 선언 시점에서 잘라 냈다.
재작성을 조용히 하지도 않는다. 무엇을 보냈고 무엇이 저장됐는지가 정규화와 같은 채널로 응답에 실려 돌아오고, 그 동작을 세션 시작 안내에도 적어 둔다.

고칠 수 없는 것은 정직하게 실패한다

여기가 별칭의 한계선이다.
오타 접두는 기계가 뜻을 알 수 없으므로 topic에 묻어 통과시키지 않고 거부한다.
마이그레이션 검사에도 같은 선이 그어졌다.
재태깅 결과가 기대 집합의 부분집합이기만 하면 통과하던 검사가 태그를 떨구는 회귀를 놓쳤고, 정확히 같은 집합인지를 보도록 조였다.

대청소는 이틀 걸렸다

마이그레이션 도구는 두 단으로 돈다.
먼저 dry-run이 매니페스트를 쓰고, 집행은 그 매니페스트만 소비한다.
우선순위는 삭제가 재태깅을 이긴다. 지울 문서를 먼저 재태깅하는 낭비를 막는다.
실행은 25건 단위 배치로 돈다.
배치 사이에는 재통합이 끝나기를 기다리고, 문서를 넘나드는 실패가 상한에 닿으면 중단한다.
개별 요청의 4xx를 무작정 재시도하지 않는다는 것도 같이 못 박았다.

2026년 8월 23일과 24일 실행 결과는 재태깅 234건, 삭제 12건, 태그가 하나도 없던 문서의 백필 12건이다.
같은 구간 개인 뱅크의 태그 어휘가 241종에서 158종이 됐다.
삭제 12건은 되돌릴 수 없는 결손이다. 그 구간의 기록 밀도는 실제보다 낮게 남았다.

남은 여섯 행은 청소 누락이 아니었다

대청소가 끝난 뒤에도 옛 태그를 단 행 여섯이 남아 있었다.
무효화된 기록을 모아 두는 아카이브 쪽이었고, 처음에는 스크립트가 훑지 못한 자리로 보였다.
실제로는 마이그레이션이 일부러 손대지 않은 보고 전용 아카이브였다.
그 구간을 현행 어휘로 덮으면 당시에 무엇이 무효화됐는지가 사라진다.
그래서 다음 사람이 정리하지 못하도록 운영 문서에 청소 대상이 아니라고 적었다.
결손처럼 보이던 것이 설계였다.

바이트 단위로 동일하다는 자기 보고가 거짓이었다

같은 계열의 사건이 하나 더 있다.
별칭을 도입하면서 세운 가드레일 하나는 별칭 맵이 없는 뱅크는 도입 전과 바이트 단위로 동일하게 동작한다는 것이었다.
나는 그 문장을 검증 결과로 적었다.
배포 1파를 적대적으로 재검증하다가 그 자기 보고가 거짓임을 적발했다.
중복 태그를 하나로 접는 새 처리가 접두 목록을 선언한 모든 뱅크에 걸렸고, 별칭이 없는 뱅크에서도 결과가 한 건 줄어들고 있었다.
스냅샷 테스트는 하필 그 입력을 비껴가 있었다.

접기를 별칭 맵이 비어 있지 않은 뱅크로 한정했다.
그리고 별칭 도입 전 판을 따로 로드해 같은 입력을 양쪽에 넣고 차이 0을 확인했다.
그 문장을 다시 적은 것은 그 뒤다.

바로 다음 날 같은 성질의 구멍을 하나 더 막았다.
태그 슬러그를 판정하는 정규식이 임포트 순환을 피하려고 두 모듈에 의도적으로 복제돼 있었는데, 두 사본이 같은지 검사하는 핀이 없었다.
한쪽만 고치면 다른 쪽은 조용히 옛 규칙으로 남는다.
핀을 붙이고 한쪽을 일부러 변조하면 실패하고 되돌리면 통과하는 것까지 확인했다.

결론

태그는 자유 입력이 아니라 통제 어휘다.
어휘를 미리 정하는 비용은 선불이고 접두 네 개면 끝난다.
갈라진 어휘를 되돌리는 비용은 후불이었고 이틀과 문서 12건이었다.
가격보다 눈여겨볼 것은 청구서가 오는 방식이다.
조회가 실패하지 않고 빈 목록을 주는 시스템에서는 결손이 결손으로 보이지 않는다.

뒤의 두 사건이 이 작업에서 제일 값싸게 얻은 교훈이다.
자기 보고는 증거가 아니다.
"청소가 끝났다"도 "동작이 동일하다"도 한 번씩 나를 배신했고, 둘 다 다시 찔러 본 뒤에야 판정이 뒤집혔다.

한계·재검토 트리거

2026-09 기준 / 재검토 트리거: 별칭 맵이 100쌍을 넘어 정본 어휘 자체를 다시 갈라야 하거나, 한 홉으로 접히지 않는 신규 어휘가 한 달 안에 다시 쌓이면 이 판정을 다시 연다.