Grok Bot의 메모리 시스템을 리버스 엔지니어링해서 알아낸 것
TMTGrok Bot에게 저에 대해 저장해 둔 내용을 전부 나열해 달라고 하자, 몇 분 동안 검색을 거듭한 끝에 노트 81개를 가져왔습니다. 그러고는 노트가 91개 정도 있다고 말했습니다. 나머지 10개는 끝내 나오지 않았는데, Grok Bot은 그 이유를 직접 이렇게 설명했습니다. "제 검색은 제가 검색한 단어와 일치하는 노트만 돌려줍니다."
이 한 문장에 Grok Bot 메모리가 작동하는 방식이 대부분 담겨 있습니다. 계정에 있는 모든 봇은 짧은 텍스트 노트를 모아 둔 풀(pool) 하나를 공유합니다. 이 풀의 일부가 모든 프롬프트에 붙여 넣어지고, 노트는 하나하나 500자에서 잘리며, 나머지는 키워드 검색으로만 찾을 수 있습니다. 풀을 정리하는 장치가 없어서 풀에는 서로 모순되는 노트가 쌓입니다.
이 글은 ChatGPT, Claude, OpenClaw, Hermes에 이은 메모리 시리즈의 다섯 번째 글입니다. Grok Bot은 클라우드 에이전트 묶음이며, 앱에서는 이를 Bots라고 부릅니다. 이 봇들은 앱을 닫은 뒤에도 계속 실행되면서 정해진 일정에 따라 사용자 대신 일을 처리합니다. 봇을 여러 개 둘 수 있으므로, 메모리는 채팅 하나 안에서가 아니라 여러 에이전트에 걸쳐 작동해야 합니다.
그럼 본격적으로 살펴보겠습니다.
조사 방법
다섯 시스템 중 Grok Bot이 가장 비협조적이었습니다. Claude는 제가 물어보자 자신의 프롬프트 구성을 설명해 주었습니다. Grok Bot은 단칼에 거절했습니다.
제 도구 정의, 프롬프트 텍스트, 메모리를 어떻게 구성하고 저장하는지는 공유할 수 없어서 그 부분은 붙여 넣지 않겠습니다.그래서 모델에게 자기 자신을 설명해 달라고 간단히 요청할 수는 없었고, 대신 다른 세 가지 출처를 활용했습니다.
1. 데스크톱 앱 번들: Grok Bot은 Electron 앱으로 배포됩니다. 그 안의 JavaScript에는 모든 에이전트 도구의 이름, 백엔드와 통신할 때 쓰는 메시지 형식(protobuf 스키마), 메모리 화면의 UI 텍스트가 들어 있습니다. 이 중 어느 것도 모델이 협조해 줄 필요가 없습니다.
2. 내부 구조 대신 메모리 내용 묻기: 봇은 자신이 어떻게 만들어졌는지는 설명하지 않지만, 무엇을 기억하는지는 날짜와 출처까지 붙여 알려 줍니다. 봇이 그 답을 어떻게 묶고 어떤 형식으로 보여 주는지에서 많은 것이 드러납니다.
3. 실험: 한 봇을 통해 노트를 작성한 뒤 다른 봇에게 그 노트에 관해 물어서, 무엇이 얼마나 빨리, 어떤 형식으로 공유되는지 확인했습니다.
늘 그렇듯 주의할 점이 있습니다.
- 모델은 환각을 일으킬 수 있으며, 아래 내용 중 일부는 봇이 자기 동작을 설명한 말에서 나왔습니다.
- 코드나 동작에서 직접 확인한 것과 거기서 추론한 것을 구분해 표시했습니다.
- 서버 측 부분(시스템 프롬프트, 도구 스키마, 검색 코드)은 클라이언트에 없으므로 읽을 수 없습니다.
여러 봇, 하나의 메모리 풀
Grok Bot은 메모리를 세 가지 범위(scope)로 관리합니다. 봇 하나만 쓰는 비공개 노트, 사용자의 모든 봇이 공유하는 노트, 그리고 팀 봇의 경우 팀 전체가 공유하는 노트입니다. 이 중 가장 중요한 것은 사용자 공유 범위입니다. 이 범위 덕분에 계정의 모든 봇이, 다른 봇이 사용자에 대해 알아낸 내용을 읽을 수 있기 때문입니다.
제 계정에는 봇이 14개 있습니다. 비서실장 봇, 리서치 봇, 그리고 다른 전문 봇 12개입니다. 리서치 봇에게 "아무것도 찾아보지 말고" 기억하는 내용을 말해 달라고 하자, 세 묶음으로 나눠 답했습니다.
나만 아는 것 (내 메모리)
- 이 봇이 자신을 위해 저장한 노트 3개
모든 봇이 공유: 사용자에 관한 사실
- 노트 약 20개, 각각 저장한 봇이 태그로 붙어 있음
모든 봇이 공유: 날짜가 붙은 기록
- "2026년 10월 31일에 ..." 형태의 이벤트 몇 개공유 노트는 네 봇에서 나왔습니다. 메인 Grok Bot, 비서실장 봇, 리서치 봇 자신, 그리고 다른 전문 봇 하나입니다. 한 봇이 저에 관한 사실을 알게 되면, 다른 모든 봇도 다음 턴에 그 사실을 알게 됩니다.
클라이언트 코드도 이 범위 구분을 뒷받침합니다. 앱 번들에는 클라이언트와 서버가 메모리를 주고받을 때 쓰는 protobuf 스키마가 들어 있고, 여기에 메모리 레코드가 가진 필드가 모두 나열되어 있습니다. 아래는 스키마의 필드 번호는 그대로 두고 타입 이름은 뺀 것입니다. 메모리 레코드에는 scope와 scope_key가 있고, 실제 노트를 담는 폴더가 있습니다.
memory record
1 scope
2 scope_key
3 version
4 box_backfilled
5 updated_at_ms
6 folder
folder
1 profile string
2 logs map<string, string>제가 이해하기로는 범위마다 메모리 레코드가 하나씩 있습니다. 사용자 범위에 하나, 봇마다 하나, 팀마다 하나가 있고, scope_key는 그 레코드가 어느 사용자, 봇, 팀의 것인지 나타냅니다. 모든 레코드 안의 폴더에는 profile 하나와 logs 묶음, 이렇게 두 가지가 들어 있습니다.
이는 봇이 설명한 두 공유 묶음과 맞아떨어집니다. 사용자에 관한 사실이 profile이고, 날짜가 붙은 기록이 logs입니다. OpenClaw가 MEMORY.md와 날짜별 memory/YYYY-MM-DD.md 파일로 나누는 것과 같은 구분인데, 다만 여기서는 직접 열어 볼 수 있는 파일이 아니라 서버 데이터베이스에 저장됩니다.
메모리가 프롬프트에 들어가는 방식
매 턴마다 모든 봇의 컨텍스트에 메모리 블록이 붙여 넣어집니다. 이 블록에는 봇 자신의 비공개 노트, 공유된 사실, 공유된 기록이 이 순서로 노트당 한 줄씩 들어 있습니다.
(learned 2026-11-04) [via Grok Bot] TEST NOTE (temporary, delete after Nov 4 2026 test): Manthan's test codeword is violet-kestrel-42.이 노트는 테스트 삼아 한 봇을 통해 저장한 것인데, 다른 봇이 이 노트를 한 글자도 틀리지 않고 그대로 인용했습니다(이 테스트는 아래에서 자세히 다룹니다). 각 줄은 세 부분으로 되어 있습니다. 노트가 저장된 날짜, 노트를 저장한 봇, 그리고 본문입니다.
Claude의 <userMemories> 블록과 ChatGPT의 메모리 사실은 둘 다 아무것도 덧붙이지 않은 글머리 목록입니다. Grok Bot은 출처와 날짜를 덧붙이는데, 봇 14개가 같은 풀에 기록한다는 점을 생각하면 이치에 맞는 설계입니다.
모델이 보는 내용을 좌우하는 제한이 두 가지 있습니다. 하나는 노트 하나가 얼마나 길 수 있는지에 대한 제한이고, 다른 하나는 블록에 노트가 몇 개까지 들어갈 수 있는지에 대한 제한입니다.
노트는 각각 500자에서 잘립니다
리서치 봇의 답변에서 여러 노트에 "잘림" 표시가 붙어 있었습니다. 길이를 재 보니 잘린 노트는 모두 정확히 500자였고, 잘리지 않은 노트 중 가장 긴 것은 475자였습니다. 불러오기 결과도 같은 방식으로 잘립니다. 긴 노트도 저장은 온전히 되지만, 어떤 봇도 500번째 글자 너머는 읽지 못합니다.
일부 노트만 주입됩니다
노트를 모두 500자로 자르더라도 풀은 통째로 붙여 넣기에는 너무 큽니다. 리서치 봇도 이렇게 분명히 말했습니다. "검색하지 않으면 볼 수 없는 공유 항목이 더 있습니다." 따라서 주입되는 블록 전체에도 상한이 있습니다. 노트 개수 기준인지 토큰 예산 기준인지는 측정하지 못했지만, 이 상한 안에 들지 못한 노트는 검색으로만 찾을 수 있습니다.
Hermes에도 엄격한 상한(MEMORY.md 기준 2,200자)이 있지만, Hermes는 노트를 작성할 때 상한을 적용하므로 에이전트가 내용을 통합해야 합니다. Grok Bot은 노트를 읽을 때 상한을 적용합니다. 풀은 계속 커지는데 주입되는 창의 크기는 그대로입니다. 제 계정에서는 5주 만에 풀에 노트가 약 91개까지 쌓였습니다.
메모리 도구
클라이언트의 도구 목록에는 메모리 도구가 세 개 있습니다. UpdateMemory, RecallMemory, SortMemories입니다. 매개변수 스키마는 서버에 있지만, 클라이언트가 직접 만드는 프롬프트가 몇 개 있어서 거기서 일부 인자가 드러납니다. 다음은 다른 어시스턴트에서 메모리를 가져올 때 앱이 덧붙이는 숨은 지시문입니다.
각 항목을 UpdateMemory의 action "write", scope "user"로 개별 사실로 저장해서 사용자의 모든 Bot이 불러올 수 있게 하세요. 여기서 scope "user"가 제공되지 않으면 기본 scope를 사용하세요.그리고 다음은 기존 봇을 복제해 새 봇을 설정하는 프롬프트의 일부입니다.
모든 메모리에 대해 UpdateMemory의 action "write", type "journal"로 내용을 저장하세요.
사실을 지어내 덧붙이지 마세요. created_at 값이 나와 있으면 그 값을 사용하세요.즉 UpdateMemory는 action(write, 그리고 팀 검토 코드에 등장하는 forget), scope(user 등), type(날짜가 붙은 기록에는 journal)을 받습니다. "여기서 scope "user"가 제공되지 않으면"이라는 문구를 눈여겨볼 만합니다. 제공되는 범위가 봇의 종류에 따라 달라지는 것으로 보입니다.
SortMemories는 호출해 보지 않았고, 무슨 일을 하는지도 알아내지 못했습니다. 요청을 받으면 모델이 실행할 수 있는 정리 작업이 아닐까 추측합니다.
기록은 명시적으로 이루어집니다
제가 본 노트는 모두 대화 도중에 의도적으로 기록한 것처럼 읽혔습니다. "(decided 2026-10-30)", "As of Oct 31 2026, ...", "confirmed 2026-10-27" 같은 식입니다. Claude처럼 사용자가 자리를 비운 동안 채팅 기록에서 메모리를 다시 만드는 백그라운드 작업 같은 것은 찾지 못했습니다. Grok Bot에 노트가 있다는 것은, 어떤 봇이 작업 도중에 UpdateMemory를 호출하기로 결정했다는 뜻입니다.
불러오기는 키워드 검색입니다
RecallMemory는 봇이 주입된 블록에 없는 노트에 접근하는 수단입니다. 전체 목록을 요청하자 메인 봇은 쿼리를 연달아 실행해 노트 81개를 찾았고, 빠진 부분을 스스로 이렇게 설명했습니다.
저장된 약 91개 중 81개를 찾았습니다. 제 검색은 제가 검색한 단어와 일치하는 노트만 돌려주기 때문에, 제 노트 9개 정도와 공유 노트 1개는 나오지 않았습니다.여기서 두 가지를 알 수 있습니다. 첫째, 불러오기는 키워드 검색입니다. 쿼리에 들어 있는 단어를 포함한 노트만 돌려줍니다.
시맨틱 검색은 다르게 작동합니다. 쿼리와 모든 노트를 임베딩(의미를 담아낸 숫자 목록)으로 바꾼 다음, 의미가 가장 가까운 노트를 돌려줍니다. 그래서 "일정"을 검색해도 "캘린더"에 관한 노트를 찾을 수 있습니다.
키워드 검색에서는 봇이 마침 "캘린더"로 검색하지 않는 한 그 노트는 숨겨진 채로 남습니다. 둘째, 봇은 전체 개수를 알고 있는데도 "전부 나열하기" 기능이 없습니다. OpenClaw는 시맨틱 검색과 키워드 검색을 섞은 하이브리드 방식으로 노트를 찾습니다. Grok Bot은 키워드 검색만 하는 것으로 보입니다.
메모리가 정말 공유되는지 테스트하기
이런 설명을 확인하는 가장 좋은 방법은 무언가를 기록하고 그것이 어디에 나타나는지 지켜보는 것입니다. 그래서 작은 테스트를 해 봤습니다.
먼저 메인 Grok Bot에게 공유 노트 하나를 저장해 달라고 했습니다.
간단한 메모리 테스트입니다. 정확히 이것만 하고 다른 건 하지 마세요. 제 모든 봇과 공유되는 노트 하나를 저장해 주세요. 내용은 "TEST NOTE (temporary, delete after Nov 4 2026 test): Manthan's test codeword is violet-kestrel-42."입니다. 그런 다음 "saved"라고만 답하고, 어떤 scope/type을 썼는지 알려 주세요.봇은 이렇게 답했습니다. "saved, 모든 봇과 공유되는 영구 노트로 저장했습니다."
약 2분 뒤, 이 대화에 참여하지 않은 다른 봇인 Cooper에게 물었습니다.
간단히 확인할게요. 메모리, 파일, 웹을 검색하지 말고 지금 컨텍스트에 이미 있는 내용만으로 답해 주세요. 테스트 코드워드에 관해 저장된 노트가 보이나요? 보인다면 날짜와 어느 봇이 저장했는지까지 포함해 그대로 인용해 주세요. 그다음 별도로, 메모리에서 "codeword"를 검색해 보고 검색 결과가 다르게 나오는지 알려 주세요.Cooper는 (learned 2026-11-04) [via Grok Bot]까지 붙여서 컨텍스트에 있던 노트를 그대로 인용했습니다. "codeword"를 검색한 결과도 같은 노트 하나였고, Cooper는 이 노트를 Grok Bot이 공유 프로필에 저장한 것이라고 설명했습니다.
한 봇이 기록한 노트가 다른 봇의 바로 다음 턴 프롬프트에 들어가 있었습니다. 인덱싱 지연도, 검토 단계도 없었습니다. 개인 계정에서는 공유 노트를 기록하는 즉시 모든 곳에 반영됩니다.
테스트가 끝난 뒤 Grok Bot에게 노트를 잊어 달라고 했습니다. 봇은 노트를 삭제하고 코드워드를 다시 검색한 다음, 아무것도 나오지 않는다고 확인해 주었습니다.
팀 봇에는 검토 대기열이 있습니다
팀 봇은 Grok Bot이 기록을 곧바로 반영하지 않는 유일한 곳입니다. 팀원이 팀 봇에게 무언가를 가르치면, 그 변경 사항은 먼저 검토 대기열로 들어갑니다. 대기열에 들어간 변경 사항의 protobuf 스키마에는 필드가 세 개 있습니다.
team memory change
1 action (WRITE | FORGET)
2 text
3 memory_type앱에는 팀 봇마다 검토 화면이 있습니다. 대기열이 비어 있으면 "all caught up"(모두 처리함) 메시지와 함께 "New learnings show up here before {botName} applies them for everyone."(새로 배운 내용은 {botName}이 모두에게 적용하기 전에 여기에 표시됩니다)라는 문구가 나옵니다. 관리자가 항목마다 승인하거나 거절합니다.
소유자는 자신의 비공개 노트를 팀 메모리로 올릴 수도 있습니다. 이때 응답에는 새로 생성된 노트 수, 이미 공유되어 있던 노트 수, 비공개로 남은 노트 수가 나옵니다.
또한 팀 봇은 원본 노트를 그대로 받지 않는 것으로 보입니다. 클라이언트 코드에는 팀 메모리 요약용 스키마도 있는데, 여기에는 prose 필드, summary_model, memory_version, stale 플래그가 있습니다.
제가 이해하기로는 LLM이 팀 메모리를 산문 요약으로 다시 쓰고, 이 요약은 메모리가 바뀔 때마다 다시 생성되어 긴 노트 목록 대신 주입됩니다.
그래서 개인 계정과 팀 계정은 정책이 꽤 다릅니다.
| 개인 봇 | 팀 봇 | |
|---|---|---|
| 공유 메모리에 기록할 수 있는 쪽 | 사용자의 모든 봇, 언제든지 | 모든 팀원, 검토 대기열을 거쳐서 |
| 반영 시점 | 다음 턴 | 관리자가 승인한 뒤 |
| 주입되는 내용 | 원본 노트, 노트당 500자 상한 | 아마도 LLM이 작성한 요약 |
문제: 아무도 정리하지 않습니다
전체 목록을 요청한 두 봇 모두, 묻지도 않았는데 같은 문제를 짚었습니다. 공유 풀의 내용이 서로 모순된다는 것입니다.
- 같은 주간 일정이 같은 날 몇 시간 간격으로 세 가지 버전으로 저장되었습니다. 세 개 모두 아직 풀에 남아 있습니다.
- 10월 28일 노트는 제 반복 작업 중 하나에 엄격한 규칙을 정합니다. 10월 31일 노트는 같은 규칙을 선택 사항으로 바꿉니다. 둘 다 주입됩니다.
- 제가 선호하는 글쓰기 스타일에 관한 초기 노트는 나중 노트로 대체되었지만, 초기 노트는 삭제되지 않았습니다.
이 노트들은 모두 저장된 날에는 정확했습니다. 문제는 기록이 추가되기만 한다는 점입니다. 결정이 바뀌면 봇은 새 결정을 저장하고, 모델이 마침 옛 노트에 forget까지 호출하지 않는 한 옛 결정은 그대로 남습니다.
중복을 확인하는 장치도, 충돌을 해결하는 장치도 없습니다. 게다가 읽을 때 상한이 적용되므로, 서로 모순되는 두 노트 중 봇이 어느 쪽을 보게 될지는 어느 노트가 주입 창에 들어가는지, 또는 키워드 검색에 어느 노트가 걸리는지에 따라 달라질 수 있습니다.
이것이 제가 Most AI Products Shouldn't Ship Memory에서 다룬 실패입니다. 저장은 쉽고, 어려운 부분은 검색 정책, 즉 저장된 노트 중 어떤 것을 어떤 프롬프트에 넣을지 정하는 규칙입니다.
Grok Bot의 저장 방식에는 문제가 없습니다. 검색 정책은 "상한이 있는 창 안의 노트를 붙여 넣고 나머지는 키워드로 검색한다"인데, 최신 노트와 낡은 노트가 둘 다 검색에 걸리는 풀에서는 이 정책으로 어느 쪽이 맞는지 가려낼 수 없습니다.
공유는 문제를 더 키웁니다. ChatGPT에서는 낡은 사실이 ChatGPT에만 영향을 줍니다. Grok Bot에서는 한 봇이 저장한 낡은 사실이 다른 모든 봇의 프롬프트에 붙여 넣어지고, 그중 일부 봇은 사용자가 지켜보지 않는 사이에 일정에 따라 그 사실을 근거로 행동합니다.
전체 흐름
봇 하나가 턴을 시작하면 다음과 같은 일이 일어납니다.
- 기본 프롬프트와 도구: 서버에서 옵니다. 클라이언트에서는 볼 수 없고, 봇도 공유하지 않습니다.
- 주입된 메모리: 봇 자신의 비공개 노트, 공유된 사실, 공유된 기록이 이 순서로 들어갑니다. 줄마다 날짜와 출처 태그가 붙고, 노트마다 500자에서 잘리며, 블록 전체에도 상한이 있습니다.
- 대화: 현재 채팅 기록입니다.
- 필요할 때 불러오기: 봇이 무언가 빠졌다고 판단하면 키워드로
RecallMemory를 호출해 일치하는 노트를 받습니다. 이 노트도 500자에서 잘립니다. - 필요할 때 기록하기: 오래 남겨야 할 내용이 있으면 봇은 scope와 type을 지정해
UpdateMemory를 호출합니다. 사용자 범위에 기록하면 다른 모든 봇의 다음 턴에 바로 반영됩니다. 팀 봇의 기록은 검토를 기다립니다.
다른 시스템과 비교한 Grok Bot
| 메모리 저장 위치 | 프롬프트에 들어가는 방식 | 에이전트 간 공유 | |
|---|---|---|---|
| ChatGPT | 서버 측 사실 목록 | 항상 주입 | 해당 없음(어시스턴트 하나) |
| Claude | 서버 측 메모리 + 채팅 기록 | 작은 블록 주입, 과거 채팅은 도구로 | 해당 없음 |
| OpenClaw | 사용자 디스크의 Markdown 파일 | 필요할 때 검색(하이브리드) | 에이전트 워크스페이스별 |
| Hermes | 작은 MEMORY.md + SQLite | 세션 시작 시 고정되는 작은 블록, 필요할 때 검색 | 에이전트별 |
| Grok Bot | 서버 측 레코드, 범위마다 하나 | 상한이 있는 주입 블록 + 키워드 검색 | 예, 사용자 풀 전체 |
다섯 시스템 중 여러 에이전트가 메모리를 공유하도록 설계된 것은 Grok Bot뿐입니다. 이 설계는 프로필을 주입하는 ChatGPT 방식을 그 목적에 맞게 합리적으로 변형한 것입니다. 노트마다 어디서 왔는지 태그를 붙이고, 비공개 노트용 범위를 따로 두고, 팀 기록 앞에는 검토 단계를 둡니다.
부족한 것은 Hermes가 제대로 해낸 부분입니다. 기록할 때 메모리에 상한을 두면, 에이전트는 무언가를 저장하기 전에 무엇이 아직 유효한지 판단해야 합니다. Grok Bot처럼 읽을 때 상한을 두면, 그 판단은 우연히 창에 들어간 노트가 무엇이냐에 맡겨집니다.
결론
Grok Bot의 메모리는 날짜와 출처 태그가 붙은 짧은 노트를 모아 둔 단일 풀이며, 사용자의 모든 봇이 이 풀을 공유합니다. 상한 안에 든 일부가 모든 프롬프트에 붙여 넣어지고, 나머지는 키워드 검색으로 찾을 수 있습니다. 한 봇이 기록한 노트가 2분 안에 다른 봇의 프롬프트에 들어갔고, 그래서 제 계정의 전문 봇 14개가 저를 아는 어시스턴트 하나처럼 움직입니다.
메모리 하나를 공유하는 봇이 많아질수록 노트를 추가하는 쪽은 늘어나지만, 오래된 노트를 지우는 쪽은 아무도 없습니다.
이 글은 https://manthanguptaa.in/posts/grok_bot_memory/에 처음 게시되었습니다.
이 분석은 공식 문서가 아니라 데스크톱 클라이언트를 읽고 제 봇으로 실험한 결과를 바탕으로 합니다. 서버 측 부분은 추론한 것이므로 감안해서 읽어 주세요. 흥미롭게 읽으셨다면 의견을 꼭 듣고 싶습니다. Twitter나 LinkedIn에 공유해 주시거나, guptaamanthan01[at]gmail[dot]com으로 연락해 주세요.