스토리지 엔진이 유일한 권한 주체다
19개 시스템 시리즈의 마지막 파트가 다소 늦어졌지만, 늦은 것보다는 낫다.
제가 분석한 19개 시스템에 나타난 6가지 보편적 패턴은 이 시리즈의 핵심입니다. 작성 시점의 품질 투자, 사실과 함께 이동하는 출처(Provenance), RRF 융합 하이브리드 검색, 열 기반 승격이 적용된 계층형 스토리지, 명시적 컨텍스트 예산, 에이전트 인터페이스에서의 도구 기반 접근(도구 주입이 아닌). 각 패턴이 별도의 기사를 받은 이유는 세 개 이상의 시스템이 독립적으로 동일한 원칙에 도달했기 때문입니다.
보편적 패턴은 여기서 끝입니다. 그 다음부터는 떠오르는 패턴(Emerging Patterns)이 시작됩니다.
이 글에서는 기준선 아래에 위치하여 하나 또는 두 개의 시스템에만 존재하는 8가지 패턴을 다룹니다. 이러한 구현들은 실제 문제를 명확히 해결하고 있어 앞으로 드물게 남아 있기 어려울 것으로 보입니다. 7개는 정확히 하나의 시스템에만 나타나고, 1개는 두 시스템에 나타나며 한 번 더 채택되면 보편적 패턴으로 승격됩니다. 첫 번째 패턴은 가장 중요하기 때문에 가장 깊이 있게 다룹니다.
초기 단계이기 때문에 이 중 일부는 보편화되지 않을 수도 있습니다. 하지만 늦는 것은 더 나쁩니다. 여러분이 2026년에 스토리지 계층 권한 부여 없이 시스템을 구축했는데, 어떤 종합 보고서가 이를 일회성 특이점이라고 부르는 바람에 2027년에 Hacker News에서 프롬프트 인젝션 데모가 등장하는 상황을 맞게 될 테니까요.
이전 글에서는 에이전트가 도구를 보유해야 한다고 주장했습니다. 에이전트에 SQL 도구를 주면 즉시 문제가 발생합니다: 접근하면 안 되는 테이블을 조회하는 것을 어떻게 막을 수 있을까요?
순진한 접근 방식은 이미 널리 알려져 있으며 모두 틀렸습니다.
프롬프트 수준 명령: "대화 테이블을 읽는 것은 허용되지 않습니다." 협력적인 모델에는 효과가 있습니다. 하지만 명령을 잘못 읽거나, 금지 사항이 현재 작업에 적용되지 않는다고 판단하거나, 탈옥(Jailbreak) 상태에서 작동하는 모든 모델에는 실패합니다.
애플리케이션 수준 필터: sql_query 도구가 모델의 SQL을 래핑하여 금지된 테이블 참조를 구문 분석하고, 데이터베이스에 전달하기 전에 쿼리를 거부합니다. 순진한 모델에는 효과적입니다. 하지만 테이블 이름을 인용하거나, ATTACH를 사용하거나, 래퍼와 데이터베이스 간의 구문 분석 불일치를 악용하거나, 다른 곳에 정의된 뷰(View)를 통해 우회하는 모든 모델에는 실패합니다.
ORM 중재 접근만 허용: 에이전트가 원시 SQL에 직접 접근하지 않고, 타입화된 메서드를 호출합니다. 하지만 시스템에 디버깅 도구, 분석 도구, 마이그레이션 도구 또는 "에이전트가 두 테이블을 조인하도록 허용" 기능이 추가되면 규칙이 깨지고, 그 손상이 눈에 띄지 않을 때까지는 효과가 있는 것처럼 보입니다.
간단히 말해, 이들 중 어느 것도 진정한 강제(Enforcement)가 아닙니다. 이것들은 힌트일 뿐입니다. 진정한 하위 에이전트 격리를 원하는 시스템은 상위 계층이 아닌 스토리지 엔진 자체에 읽기를 거부하도록 요청해야 합니다. 이것이 운영 체제 리소스에 대해 Capability 기반 보안이 강제하는 원칙입니다: 커널이 거부하지, 애플리케이션이 거부하는 것이 아닙니다. 또한 PostgreSQL의 행 수준 보안이 멀티 테넌트 SaaS에 강제하는 원칙이기도 합니다: 정책은 ORM이 아닌 데이터베이스에 있어야 합니다. "새 마이크로서비스를 구축한 엔지니어가 테넌트 필터를 적용하는 것을 잊었다"는 것은 스토리지 엔진 정책만이 차단할 수 있는 버그 클래스이기 때문입니다.
second-brain 은 이 코퍼스(Corpus)에서 첫 번째로 실제 구현된 사례입니다. 그 범위가 지정된 데이터베이스는 세 개의 계층으로 구성되어 있으며, 각각은 필요 조건이지만 단독으로는 충분하지 않습니다.
- 범위가 지정된 에이전트(ScoPed Agent)당 새로운 인메모리 SQLite 연결. 생성자(Constructor)는
:memory:를 열고 실제 데이터베이스를 읽기 전용 모드로 연결(Attach)합니다. 에이전트의 연결에는 실제 메인 스키마가 없으며, 다음 계층이 거기에 배치하는 것 외에는 읽을 수 있는 것이 없습니다. - 메인 스키마의 TEMP VIEW가 소스의 허용된 테이블로 리디렉션. 뷰 정의는 에이전트의 프로필에서 가져오며, 열 수준 편집(Redaction)이나 행 수준 필터를 인코딩할 수 있습니다. 뷰 이름 검증기는 프로필을 통한 악의적인 테이블 이름 삽입을 방지합니다.
- 뷰를 통과하지 않는 소스 스키마 읽기를 거부하는 SQLite C-API 승인자(Authorizer) 훅. 이 훅은 SQLite가 수행하려는 모든 읽기 작업에 대해, 옵티마이저가 테이블 이름을 확인하기 전에 실행됩니다. 뷰 본문 내부의 읽기는 허용됩니다. 뷰가 범위 필터이기 때문입니다. 최상위 읽기는 범위가 생성한 뷰 이름 집합에 대해 확인됩니다. 소스 스키마에 대한 직접 읽은 예외 없이 거부됩니다.
LLM은 원하는 모든 SQL을 작성할 수 있습니다. 테이블 이름을 인용하고, UNION을 사용하고, ATTACH를 사용하고, SQLite의 전체 문법을 사용할 수 있습니다. 하지만 허용 목록에 없는 테이블의 행을 읽는 데는 성공할 수 없습니다. 승인자가 C 계층에서 테이블 이름이 확인되기 전에 읽기를 가로채기 때문입니다. 애플리케이션 수준 필터는 우회 가능합니다. 그러나 스토리지 엔진 권한 부여는 그렇지 않습니다.
약 50줄의 stdlib Python 코드입니다. 추가 종속성, 추가 프로세스, 스키마 마이그레이션이 필요 없습니다. 쿼리당 오버헤드는 읽기당 하나의 C 콜백이며, 쿼리 자체 비용에 비해 무시할 수 있습니다. 이 패턴은 다른 모든 것과 깔끔하게 구성됩니다. 에이전트의 도구 레지스트리는 여전히 기능별로 제어할 수 있고, 뷰 정의는 여전히 열 수준 편집을 인코딩할 수 있으며, 대화 기록은 여전히 작성 시점에 편집할 수 있습니다. 이러한 계층 중 어느 것도 스토리지 계층의 강제에 의해 약화되지 않습니다. 오히려 강화됩니다.
이 프레이밍이 중요합니다. 19개 시스템의 다른 모든 보안 패턴은 힌트입니다. 스토리지 엔진이 권한의 주체입니다.
이름을 붙일 가치가 있는 세 가지 더
비동기 정리의 경합 조건 보호 (llm-wiki)
백그라운드 메모리 유지 관리는 본질적으로 포그라운드 사용자 작업과 경합 조건(Race Condition)에 놓여 있습니다. 프로젝트 A에 대해 시작된 정리 작업이 프로젝트 B에 대해 완료되면 두 프로젝트 모두 손상됩니다. A의 결정 사항이 B의 검토 저장소로 이동했거나, B의 항목이 A의 데이터를 기준으로 해결된 것으로 표시됩니다. 이는 도입하기 쉽고, 감지하기 어렵고, 손상된 상태가 정상적으로 보이기 때문에 깔끔하게 복구하는 것이 불가능한 버그 클래스입니다.
llm-wiki 는 2단계 백그라운드 검토 루프를 실행하며, 모든 양보 지점(Yield Point)에서 두 가지 경합 조건 보호 신호를 다시 확인합니다. 프로젝트 전환 핸들러에 의해 실행되는 중단(Abort) 신호와 UI 저장소의 현재 프로젝트에 대한 경로 비교입니다. 두 확인 중 하나라도 실패하면, 정리 작업은 결정 사항을 적용하지 않고 중간에 반환됩니다. 큐 측의 프로젝트 전환 핸드셰이크가 그림을 완성합니다. 활성 프로젝트의 상태를 메모리에서 지우기 전에 디스크에 플러시하고, 처리 중인 항목을 보류 상태로 되돌리며, 진행 중인 LLM 호출과 진행 중인 정리 작업 판단을 모두 중단한 후에야 일시 중지된 프로젝트의 경로에 씁니다.
핵심 메타 패턴은 다음과 같습니다: 결정론적으로 할 수 있는 부분은 결정론적으로, LLM이 필요한 부분에만 LLM을 사용하고, 모든 곳에서 중단 가능하게 만드는 것. 2단계 구조는 단순한 존재 확인으로 처리할 수 있는 사례에서 LLM을 배제합니다. 경합 조건 보호는 두 단계 모두를 중단 가능하게 만듭니다. 이 조합은 어떤 시스템의 백그라운드 메모리 유지 관리 루프에 대한 템플릿입니다.
자동 성능 저하 No-op 생성자 (graymatter)
라이브러리 API 설계에는 반복되는 긴장감이 있습니다. 가장 간단한 "Hello World"는 라이브러리가 한 줄로 생성하고 한 줄로 호출하여 바로 작동하기를 원합니다. 가장 방어적인 프로덕션 자세는 생성 시점에서 호출자가 무시할 수 없는 구조화된 오류와 함께 큰 소리로 실패하기를 원합니다.
graymatter 는 무음 실패(Fail-Silent)를 선택하지만, 그 트레이드오프를 생산적으로 만드는 원칙을 따릅니다. 생성자는 절대 오류를 반환하지 않습니다. 초기화에 실패하면(bbolt가 잠겨 있거나, 데이터 디렉터리에 쓸 수 없거나, 벡터 저장소를 열 수 없는 경우), stderr에 로그를 기록하고 모든 메서드가 No-op인 성능 저하된 Memory 객체를 반환합니다. 프로덕션 호출자는 핸들을 신뢰하기 전에 Healthy()를 통해 확인합니다. 라이브러리는 go get이 가능하고, 세 줄로 임포트할 수 있으며, 데모에서 작동합니다. Healthy()는 프로덕션 규율에 대한 세금(Tax)입니다.
이 패턴은 라이브러리를 자체 시작 절차가 있는 에이전트 하니스(Harness)에 포함시키기에 안전하게 만듭니다. 부팅 중에 graymatter.New(...)를 호출하고, 오류 경로가 없기 때문에 무시하고 계속 진행하는 에이전트 하니스는 정상 경로에서는 작동하는 메모리 계층을, 데이터 디렉터리가 읽기 전용일 때는 메모리 없는 폴백을 얻습니다. 어느 쪽이든 하니스는 부팅됩니다. 이것은 실패 시 큰 소리로 알리는 생성자가 모든 포함 사이트에서 명시적인 오류 처리 없이는 제공할 수 없는 특정 종류의 방어적 구성입니다.
섀도우 모드 중복 감지 (mem9)
중복 억제를 제공하는 모든 시스템은 코사인 유사도 임계값을 선택해야 합니다. 0.95 이상은 거의 확실히 중복입니다. 0.7 미만은 거의 확실히 중복이 아닙니다. 그 사이의 공간은 논쟁의 여지가 있으며, 올바른 기준은 임베딩 모델, 도메인, 쿼리 분포, 그리고 이 특정 시스템에서 거짓 긍정(False Positive) 대 거짓 부정(False Negative)의 비용에 따라 달라집니다.
직관에 기반하여 하나를 선택하고 출시하고 싶은 유혹이 엄청납니다. mem9 는 그렇게 하지 않습니다. 모든 사실에 대해 중복 감지 쿼리를 실행하고, 코사인 점수를 Prometheus 히스토그램에 기록한 후 아무 조치도 취하지 않습니다. 임계값은 프로덕션 데이터가 이를 정당화할 때까지 연기됩니다. "휴리스틱이 아닌 관찰 결과를 출시하라."
동일한 논리가 모든 메모리 시스템의 모든 임계값 결정에 적용됩니다. 재순위(Rerank) 임계값, 재현율(Recall) 신뢰도 컷오프, 계층 승격 열 게이트, 인사이트 병합 유사도 게이트. 19개 시스템의 대부분은 이러한 값을 추정하여 출시합니다. mem9 는 값을 연기한 상태로 출시합니다. 이 원칙은 드물며 결과는 더 좋습니다.
네 가지 더, 더 간결하게
테넌트별 물리적 데이터베이스 격리 (mem9).
공유 저장소에 대한 WHERE tenant_id = ? 필터 대신, mem9 는 TiDB Zero를 통해 테넌트당 별도의 TiDB 클러스터를 프로비저닝합니다. 격리는 스토리지 엔진 측에서 이루어집니다. 애플리케이션은 실수로 테넌트 간에 쿼리할 수 없습니다. 쿼리할 공유 저장소가 없기 때문입니다. 이는 스토리지 계층 권한 부여와 동일한 최종 상태의 더 거친(coarse-grained) 버전입니다: 엔진에서 시행되는 격리, 애플리케이션에서가 아닙니다. 역사적으로 이를 비실용적으로 만들었던 인프라 비용은 이제 사라졌습니다. TiDB Zero는 자동 프로비저닝합니다. Neon도 PostgreSQL에 대해 동일한 작업을 수행합니다. Cloudflare D1은 SQLite에 대해 동일한 작업을 수행합니다.
명시적 컨텍스트 예산이 포함된 소스턴 장식(Source-turn Decoration) (mem9).
검색된 메모리는 문자열입니다. "사용자는 Postgres를 선호합니다." 정확하고 간결하지만 맥락 없이는 근거를 확인할 수 없습니다. mem9 는 원래 대화 턴(Turn)을 장식(Decoration)으로 첨부하고, 쿼리에 대해 점수를 매기고, 최소 점수, 메모리당 제한, 총 제한이라는 세 가지 예산(Triple Budget)으로 제한합니다. "사용자는 Postgres를 선호합니다"를 읽는 에이전트는 사용자가 "MongoDB를 시도했지만 조인 때문에 망가져서 지난 분기에 Postgres로 전환했습니다"라고 말한 턴을 함께 얻습니다. 두 번째 도구 호출이 필요하지 않습니다. 근거 확인은 결과에 포함됩니다. 전제 조건은 이미 보편적입니다: 출처(Provenance)와 하이브리드 검색. 19개 시스템의 대부분은 이틀이면 이 기능을 구현할 수 있습니다.
네 번째 파일로서의 purpose.md (llm-wiki).
Karpathy LLM Wiki 패턴에는 세 가지 표준 파일이 있습니다: 원시 소스, 위키 작업 세트, 구조적 규칙을 위한 schema.md. llm-wiki 는 네 번째 파일인 purpose.md를 추가합니다. 사용자가 작성하며, 시스템이 만드는 모든 LLM 호출에 인라인으로 포함됩니다. 모든 수집 프롬프트, 모든 생성 프롬프트, 모든 채팅 검색이 이를 읽습니다. 그 효과는 모든 다운스트림 행동을 조건화하는 안정적인 방향성 사전(Directional Prior)입니다. LLM은 어차피 시스템 프롬프트를 읽을 것입니다. 사용자의 의도를 추가하는 것은 비용이 들지 않으며 모든 것을 향상시킵니다. 대부분의 다른 시스템에서 이것이 없다는 것은 llm-wiki 에서 이것이 존재하는 것보다 설명하기 어렵습니다.
권위 있는 에이전트 계약으로서의 AGENTS.md (Tolaria, OpenContext). AGENTS.md 또는 CLAUDE.md가 있는 대부분의 저장소는 이를 힌트 파일로 취급합니다. Tolaria 와 OpenContext 는 이를 계약으로 취급하며, 모든 바인딩 조항을 에이전트가 위반할 경우 빌드가 실패하는 기계적 검사로 뒷받침합니다. "사전 커밋 훅을 건너뛰지 마십시오"는 정중한 요청이 아니라 CI가 강제하는 규칙입니다. "테스트 커버리지는 임계값 이상을 유지해야 합니다"는 지침이 아니라 테스트 러너가 위반 시 중단하는 기준입니다. 힌트는 무시될 수 있습니다. 검사로 뒷받침되는 계약은 무시할 수 없습니다. 두 시스템이 이미 이를 수행하고 있습니다. 하나만 더 추가되면 보편적 패턴으로 승격됩니다.
다음에 보편화될 패턴들
스토리지 계층 권한 부여 가 가장 먼저 보편화될 것입니다. 이것은 소스 분석에서 가장 확신하는 예측입니다. 하위 에이전트에 SQL 액세스 권한을 부여하는 모든 메모리 시스템은 스토리지 계층 시행 없이 하나의 프롬프트 인젝션으로 기밀성 위반에 노출됩니다. 인프라는 이미 갖춰져 있습니다. SQLite는 2000년대 초반부터 set_authorizer를 가지고 있었습니다. PostgreSQL RLS는 주류입니다. LanceDB와 ClickHouse도 자체 정책 훅을 가지고 있습니다. 장벽은 기술적이지 않고 인식입니다. second-brain 이 실제 구현 사례를 제공했습니다. 다음 세대의 관리형 API는 이 원칙을 복사할 것입니다. 대안은 변호가 불가능하기 때문입니다.
소스턴 장식(Source-turn Decoration) 이 두 번째로 보편화될 것입니다. 전제 조건은 이미 보편적입니다. 구현은 두 개의 쿼리와 예산입니다. 에이전트 측 정보 이득이 충분히 커서, 관리형 API에서 먼저 출시하는 쪽이 두 번째로 출시하는 쪽보다 소스 대화에서 답변의 근거를 확인하는 데 있어 확실히 더 나을 것입니다. 복제 압력이 높습니다. graymatter 는 사실당 소스를 가지고 있습니다. supermemory 는 혈통(Lineage)을 가지고 있습니다. Hindsight 는 전체 대화 출처를 가지고 있습니다. 이들 중 어느 것이든 하나의 PR로 이 패턴을 구현할 수 있습니다.
자동 성능 저하 No-op 생성자 가 세 번째로 보편화될 것이며, 다른 언어에서일 것입니다. Go의 문화적 조건은 이 패턴을 안전하게 만듭니다. 다음 채택자는 Python일 가능성이 낮습니다. Python 문화는 예외에 너무 열성적이기 때문입니다. 하지만 Rust일 가능성은 있습니다. 이는 메모리 설계 선택이 아닌 라이브러리 API 설계 선택이며, "데모 미학 + 프로덕션 규율"이 올바른 트레이드오프인 곳이라면 어디든 퍼질 것입니다.
섀도우 모드 배포 는 다크호스입니다. 기술적으로는 사소하지만, 문화적으로는 어렵습니다. 두 번째 시스템이 임계값을 적용하기 전에 계측한다면, 패턴은 즉시 승격되고, 세 번째와 네 번째는 릴리스 주기 내에 따라올 것입니다. 엔지니어링 인체공학이 일단 입증되면 반박할 수 없기 때문입니다.
나머지 네 가지는 각각 특정 배포 형태가 더 보편화되는 조건부입니다. 경합 조건 보호는 더 많은 시스템이 다중 대화 병렬 처리를 도입할 때 보편화됩니다. 테넌트별 물리적 격리는 규제를 받는 엔터프라이즈 고객이 요구하기 시작할 때 보편화됩니다. purpose.md 는 Karpathy LLM Wiki 패러다임이 세 번째 또는 네 번째 구현을 얻을 때 보편화됩니다. 계약으로서의 AGENTS.md 는 "에이전트는 동료다"라는 프레이밍이 지배적인 하니스 은유가 될 때 보편화됩니다. 어느 것도 불가능하지 않습니다. 어느 것도 확실하지 않습니다.
결론
여덟 가지 모두를 관통하는 공통 주제는 동일한 입장입니다: 애플리케이션 계층은 신뢰할 수 있는 격리 경계가 아닙니다. 스토리지 계층 권한 부여는 에이전트별 표현입니다. 테넌트별 물리적 데이터베이스는 테넌트별 표현입니다. 계약으로서의 AGENTS.md 는 에이전트 행동별 표현입니다. 이 코퍼스의 다음 반복에서는 이 예측대로, 그 이름을 가진 새로운 보편적 패턴이 포함될 것입니다.
이전 글에서는 에이전트가 도구를 보유하고 무엇을 검색할지 결정해야 한다고 주장했습니다. 이 글은 에이전트가 예상치 못한 곳에서 그 도구를 사용할 때 무엇이 그것을 제어하는지에 관한 것입니다. 여섯 가지 보편적 패턴은 합의(Consensus)입니다. 여기 있는 여덟 가지는 합의가 다음으로 어디로 이동할지에 대한 선행 지표(Leading Indicators)입니다. 스토리지 계층 권한 부여는 선행 지표의 최전선이며, 이를 놓치는 비용은 Hacker News에 나타나는 종류의 비용입니다.





