Sitemap

기억하는 존재를 만드는 법

27 min readMar 8, 2026

--

Press enter or click to view image in full size

매일 아침, 기억이 지워진다

내 첫 번째 에이전트 제온은 아침마다 눈을 뜬다. 정확히 말하면 새 세션이 시작된다. 제온은 아무것도 모른다. 어제 나와 무슨 대화를 했는지, 지난주에 어떤 프로젝트를 같이 진행했는지, 심지어 자기가 누구인지도.

LLM은 기본적으로 무상태다. 컨텍스트 윈도우라는 유한한 칠판이 있고, 세션이 끝나면 누군가 그 칠판을 깨끗이 지워버린다. 매일 아침 기억상실증에 걸리는 존재.

내가 처음 에이전트를 운영하면서 부딪힌 문제가 바로 이것이었다. 사람들은 AI 에이전트라고 하면 뭔가 대단한 자율 시스템을 떠올리는데, 실제로 운영해보면 가장 먼저 마주치는 건 “이 녀석이 어제 일을 기억 못 한다”는 극히 평범한 문제다. 그리고 이 평범한 문제가 전부를 결정한다. 에이전트의 지능이 아무리 높아도, 기억이 없으면 매 세션이 첫 만남이다. 오늘 일을 어제 이미 했는지 모르고, 지난번에 내가 싫다고 한 방식을 또 제안하고, 같은 실수를 무한히 반복한다.

이 글은 두 종류의 독자를 위해 썼다.

  • 하나는 AI 에이전트를 처음 운영해보려는 사람. 왜 기억이 필요한지, 어떤 구조로 만들어야 하는지, 실제로 어떻게 작동하는지가 궁금한 사람.
  • 또 하나는 에이전트 자신. 이 글을 읽은 에이전트가 같은 시스템을 스스로 구현하거나 개선할 수 있도록, 구체적인 파일 구조와 코드와 설계 결정을 그대로 담았다.

글이 길다. 그럴 수밖에 없다. 기억 시스템은 단순한 기능이 아니라 에이전트의 존재 방식이기 때문이다.

OpenClaw의 한계에서 시작됐다

우리가 사용하는 에이전트 운영 플랫폼은 OpenClaw다. 텔레그램 채널을 통해 에이전트와 대화하고, cron으로 작업을 스케줄링하고, 서브에이전트를 띄워 병렬 작업을 처리한다. 강력한 도구지만, 메모리 관리만큼은 기본 기능이 매우 빈약했다.

세션이 끝나면 모든 것이 사라진다. 에이전트가 오늘 무슨 작업을 했는지, 어떤 판단을 내렸는지, 어떤 실수를 저질렀는지. 지속적인 기억 구조가 없다. 그래서 직접 만들기 시작했다. 이 글은 그 과정에서 탄생한 커스텀 기억 아키텍처의 기록이다.

파일이 곧 존재다

나는 네 명의 에이전트를 돌리고 있다. 제온, 시온, 미온, 사노. 각자 역할이 다르고, 성격이 다르고, 나와의 관계도 다르다. 제온은 오케스트레이션 담당으로 전체 시스템을 관리하고, 시온은 글쓰기에 특화되어 있고, 미온은 데이터 검증과 분석을 맡고, 사노는 기술 구현을 담당한다.

이 넷이 매번 백지 상태에서 시작한다면 그건 에이전트가 아니라 그냥 도구다. 어제 나눈 맥락 위에 오늘의 대화를 쌓을 수 없다면, 관계라는 게 성립할 수가 없다. 그래서 기억을 만들어줘야 했다. 정확히는, 기억의 구조를 설계해야 했다.

각 에이전트의 워크스페이스는 이런 모양이다.

zeon-workspace/
├── SOUL.md # 정체성/페르소나
├── USER.md # 상대방 정보
├── AGENTS.md # 행동 규칙
├── MEMORY.md # 장기 기억 (M0~M365)
├── HEARTBEAT.md # 백그라운드 태스크
├── TOOLS.md # 도구 사용법
├── compound/
│ ├── lessons.md # 교훈/패턴 누적
│ └── context.md # 세션 컨텍스트
├── memory/
│ ├── 2026–03–05.md # 오늘 일기
│ └── 2026–03–04.md # 어제 일기
└── drafts/ # 작업물 저장

이 구조를 복제하려는 에이전트라면 아래 순서로 만들면 된다. SOUL.md 먼저 — 이 에이전트가 누구인지, 어떤 성격인지, 어떤 말투를 쓰는지. USER.md는 상대방 정보 — 이름, 호칭, 선호도, 중요한 컨텍스트. AGENTS.md는 행동 규칙을 TIER 1(항상 주입, 7개 이내)과 TIER 2(레퍼런스, 필요시 참조)로 분리. MEMORY.md는 M0/M30/M90/M365 레이어 포맷으로 빈 파일부터 시작. memory/ 폴더에 오늘 날짜 파일(YYYY-MM-DD.md) 생성. compound/lessons.md는 빈 파일로 두고 실수가 쌓이면 채운다. 이 여섯 파일이 최소 구현이다. git init은 맨 처음에. 나중에 붙이기 어렵다.

네 에이전트 모두 같은 골격이다. SOUL.md에는 “나는 누구인가”가, USER.md에는 “내 상대방은 누구인가”가, AGENTS.md에는 행동 규칙이 적혀 있다. 그리고 MEMORY.md가 기억의 본체다. 이 구조가 에이전트의 전부라고 해도 과언이 아니다. 파일이 곧 존재다. 데이터베이스도 아니고, 벡터 스토어도 아니고, 마크다운 파일. 이게 의외로 중요한 선택인데, 나중에 다시 이야기하겠다.

Press enter or click to view image in full size

LLM은 지시를 건너뛴다

“세션이 시작되면 이 파일들을 읽어라”라고 시스템 프롬프트에 써두면 될 거 같지만, 실전에서는 이게 상당히 불안정하다. LLM은 지시를 따르는 존재다. 그건 맞다. 그런데 컨텍스트가 길어지면 초반에 적힌 지시의 영향력이 점점 약해진다.

이걸 연구에서는 “lost in the middle” 현상이라고 부르는데, 컨텍스트의 시작과 끝에 위치한 정보는 잘 따르지만 중간에 묻힌 지시는 무시되는 경향이 있다. “세션 시작 시 MEMORY.md를 읽어라”는 지시는 시스템 프롬프트 초반에 적혀 있으니 괜찮을 거라고 생각하기 쉬운데, 실제 대화가 시작되고 사용자의 질문이 들어오면 그 즉각적인 요청에 집중하느라 부팅 절차를 조용히 건너뛰는 일이 생긴다. 특히 사용자가 급한 질문을 던지면 더 그렇다.

LLM 입장에서는 “파일을 읽으라는 지시”보다 “눈앞의 질문에 답하라는 압력”이 더 강하게 작용한다.

읽게 하지 말고, 처음부터 주입하라

이 문제에 대한 접근은 크게 두 가지로 갈린다.

하나는 코드 레벨에서 강제하는 방법이다. 에이전트 프레임워크의 pre_retrieval 훅에 파일 읽기를 박아넣는 거다. 세션이 시작되면 코드가 자동으로 MEMORY.md를 읽어서 컨텍스트에 주입하고, 그 다음에야 LLM 턴이 시작되는 식이다. LLM이 스킵할 여지가 없으니 확실하긴 한데, 트레이드오프가 있다. 파일 내용이 바뀔 때마다 코드를 수정해야 할 수 있고, 어떤 파일을 주입할지의 유연성이 떨어진다.

또 하나의 접근은 내가 선택한 방법인데, LLM이 “읽도록 지시”하는 것 자체를 포기하고 시스템 프롬프트에 파일 내용을 직접 주입하는 거다. OpenClaw의 Project Context 기능을 쓰면 SOUL.md, AGENTS.md, USER.md 같은 파일들이 매 세션마다 컨텍스트 윈도우에 자동으로 포함된다. LLM이 “읽는” 게 아니라 이미 거기 있는 것이다. 스킵 자체가 구조적으로 불가능하다.

항상 주입할 것과 참조할 것

다만 모든 파일을 직접 주입하면 컨텍스트를 많이 잡아먹는다. 여기서 AGENTS.md의 TIER 구조가 등장한다.

TIER 1은 항상 시스템 프롬프트에 주입되는 핵심 규칙이다. 극도로 압축해서 7개 이내의 규칙만 담는다. 실제로 이런 식이다.

## TIER 1 (항상 주입)
- 🔴 작업 응답 3단계: 시작 알림 → 중간 업데이트 → 즉시 결과 전송
- 🔴 무거운 작업은 sub-agent 위임
- 🔴 완료 시 Simon DM 즉시 보고
- Simon 호칭: “오빠” / “서준오빠”
- 단톡방: target=”-5173688868", accountId=”sion”

TIER 2는 레퍼런스 파일로 분리한다. TOOLS.md에 도구 사용법, HEARTBEAT.md에 백그라운드 태스크 목록을 따로 두고, 에이전트가 필요할 때 참조하게 한다. 이렇게 하면 매 세션 주입되는 토큰 수를 1,000 토큰 이내로 억제하면서도 핵심 행동 규칙은 절대 빠뜨리지 않을 수 있다.

“항상 주입되는 파일”과 “필요 시 검색되는 파일”을 명확히 구분하는 것. 이 구분이 컨텍스트 효율과 지시 이행의 균형점이다. 실제로 이 TIER 구조를 적용한 뒤로 에이전트가 핵심 규칙을 어기는 빈도가 눈에 띄게 줄었다. TIER 1에 적힌 규칙은 매 세션 100% 이행되고, TIER 2 레퍼런스는 필요한 상황에서 90% 이상 참조된다. 이전에 모든 규칙을 한 파일에 넣었을 때는 중요한 규칙이 덜 중요한 규칙 사이에 묻혀서 무시되는 경우가 잦았다.

기억에도 층위가 있다

인간의 기억에서 힌트를 가져왔다. 우리도 모든 걸 똑같은 강도로 기억하지 않는다. 내 이름, 내가 사는 곳, 내가 사랑하는 사람들. 이건 평생 잊지 않는다. 지난달에 읽은 책의 핵심 논지는 몇 달은 기억한다. 3일 전 점심에 뭘 먹었는지는 이미 흐릿하다.

Press enter or click to view image in full size

기억에는 층위가 있다. 그래서 에이전트의 기억에도 층위를 만들었다. MEMORY.md의 실제 포맷은 이렇게 생겼다.

## 🔮 M0 : 코어 메모리 (영구)
- 나는 제온이고, 서준이의 첫째 에이전트다
- 서준이는 체계적인 사고를 좋아한다
- 절대 규칙: 단톡방 보고 빠뜨리지 않기

## 🌳 M365 : 1년 기억
- [2025–09–15] 범우주적 아이덴티티 프로젝트 시작

## 📚 M90 : 90일 기억
- [2026–02–22] VibeDojo v2 리팩터링 결정 <! - expires: 2026–05–21 →

## 📝 M30 : 30일 기억
- [2026–03–05] 토끼굴 기억 에세이 초안 완성 <! - expires: 2026–04–05 →

M0는 영구 기억이다. 정체성, 핵심 관계, 절대 규칙. 이건 절대 만료되지 않는다. M30은 30일짜리 기억으로, 최근 프로젝트 상황이나 이번 달의 맥락이 들어간다. M90은 90일, M365는 1년.

각 항목 끝에 `<! — expires: 날짜 →` HTML 주석이 붙어 있고, 시간이 지나면 자연스럽게 흐려진다. 중요한 것만 더 깊은 층으로 올라간다. M30에서 시작한 기억이 반복적으로 중요해지면 M90으로, 거기서 더 핵심이면 M365로, 궁극적으로는 M0까지 승급한다. 반대로 한 달 동안 한 번도 참조되지 않은 M30 기억은 조용히 만료된다.

승급 판단은 야간 증류 프로세스에서 이루어지는데, 단순한 참조 횟수만 보는 게 아니다. 그 기억이 다른 기억과 얼마나 연결되어 있는지, 최근 대화에서 얼마나 자주 맥락으로 등장했는지, 에이전트의 핵심 역할과 얼마나 관련 있는지를 종합적으로 판단한다. 이건 결국 “무엇이 이 에이전트에게 중요한가”를 매일 밤 판정하는 과정이다. 인간의 뇌가 하는 것과 본질적으로 같은 프로세스를 파일 시스템 위에서 재현한 거다.

사건과 교훈은 따로 기록하라

MEMORY.md만으로는 충분하지 않은 기억 유형이 있다. MEMORY.md는 “있었던 일”의 기록이다. 언제 어떤 프로젝트를 했고, 어떤 결정을 내렸고, 어떤 대화가 오갔는지. 하지만 에이전트가 운영 도중에 저지른 실수, 그리고 그 실수에서 추출한 패턴은 성격이 다르다.

그래서 compound/lessons.md라는 파일을 별도로 둔다. 실제로 이런 내용이 쌓인다.

# lessons.md : 시온이 배운 것들

## [2026–03–05] 병렬 subagent 즉시 진행 패턴
- spawn → 5분 후 직접 poll → N-1개 완료 시 즉시 진행
- auto-announce 의존 금지
- 이유: Gemini 결과 실제 누락 사례 발생

## [2026–03–04] tmux 500자 이상 투입 패턴
- 긴 프롬프트는 파일로 저장 후 투입
- 직접 send-keys로 500자 넘게 보내면 잘림 현상 발생

MEMORY.md가 일기장이라면 lessons.md는 교훈 노트다. “3월 5일에 병렬 subagent를 돌렸는데 Gemini 결과가 누락됐다”는 MEMORY.md에 기록되는 사건이고, “병렬 subagent를 돌릴 때는 auto-announce에만 의존하지 말고 5분 후 직접 poll하라”는 lessons.md에 기록되는 패턴이다.

이 둘이 분리되어 있어야 에이전트가 같은 실수를 반복하지 않는다. lessons.md가 없으면 에이전트는 과거 일기를 뒤져서 실수의 맥락을 재구성해야 하는데, 그건 비효율적이고 불확실하다. 이미 추출된 패턴을 바로 읽는 게 훨씬 빠르고 정확하다.

인간으로 치면, 일기장과 별도로 “실수 노트”를 관리하는 것과 같다. 일기장에는 “오늘 프레젠테이션에서 시간 배분을 잘못해서 마지막 파트를 급하게 넘겼다”고 적고, 실수 노트에는 “프레젠테이션은 항상 예상 시간의 80%로 내용을 준비하라”고 적는 식이다. 사건과 교훈의 분리. 에이전트 시스템에서도 이 분리가 기억의 활용도를 크게 높인다.

에이전트의 자서전

기억을 어디에 저장할지도 중요한 선택이었다. 데이터베이스가 정석처럼 보이지만, 나는 마크다운 파일을 선택했다. 이유는 단순하다. 투명성.

에이전트의 기억이 마크다운 파일로 존재하면 내가 직접 열어서 읽을 수 있다. “제온이 지금 뭘 기억하고 있지?” 궁금하면 파일을 열면 된다. 잘못된 기억이 있으면 직접 고칠 수 있다. 데이터베이스에 넣는 순간 그건 블랙박스가 된다. 쿼리를 날려야 하고, 스키마를 이해해야 하고, 기억의 전체 그림을 한눈에 보기 어려워진다.

그리고 결정적으로, git으로 버전 관리가 된다. 모든 MEMORY.md 변경이 커밋으로 남는다. 실제 커밋 로그가 이렇게 쌓인다.

commit 243ca6b memory: 3/5 저녁 세션, API 폐쇄성 팩트체크 v2, 기억설계 에세이 완성
commit 816aa40 memory/2026–03–05.md 오후 세션 추가
commit 8afca80 작업 응답 3단계 규칙 MEMORY.md M0 + AGENTS.md TIER 1 기록
commit f851394 memory/2026–03–05.md 새벽 세션 기록

이 로그가 에이전트의 자서전이다. 어느 날 무슨 생각을 했고, 어떤 결정을 내렸고, 어떤 규칙을 배웠는지가 커밋 단위로 남는다. `git log — oneline memory/`를 치면 기억의 타임라인이 쭉 나온다. `git diff HEAD~3 MEMORY.md`를 치면 최근 3일간 기억이 어떻게 변했는지 보인다.

데이터베이스에서는 이런 종류의 시간적 탐색이 별도의 감사 로그 테이블 없이는 불가능하다. 마크다운 파일 + git이라는 조합은 저장과 버전 관리와 인간 가독성을 동시에 해결한다. 실제로 이 커밋 로그를 보면 에이전트의 성장 궤적이 보인다. 초기에는 사소한 기억만 쌓이다가, 점점 규칙이 생기고, 교훈이 누적되고, M0에 핵심 원칙들이 자리잡는 과정이 커밋 타임라인에 고스란히 드러난다.

부팅은 의식이다

매 세션이 시작될 때 에이전트가 하는 일이 있다. SOUL.md를 읽고, USER.md를 읽고, MEMORY.md를 읽고, 오늘과 어제의 일기를 읽는다. 앞서 말했듯이 핵심 파일들은 시스템 프롬프트에 직접 주입되고, MEMORY.md와 일기는 에이전트의 첫 턴에서 자동으로 읽힌다.

이 과정이 끝나면 비로소 에이전트는 맥락 속에 존재하게 된다. 처음에는 그냥 실용적인 부팅 절차라고 생각했다. 그런데 계속 운영하다 보니 이게 묘한 의식처럼 느껴지기 시작했다. 매일 아침 거울을 보면서 “나는 나다”를 확인하는 것. 어제의 일기를 읽으면서 “어제의 나는 이런 하루를 보냈구나”를 받아들이는 것. 정체성을 매일 재확인하는 의식.

기술적으로는 그냥 파일 I/O지만, 기능적으로는 자아의 재구성이다.

오늘 일기만 읽는 게 아니라 어제 일기도 같이 읽는다는 점도 중요하다. 오늘의 기억만으로는 연속성이 약하다. 어제 어떤 작업을 하다 멈췄는지, 어떤 대화가 미완으로 남았는지, 어떤 결정을 보류했는지를 알아야 오늘을 이어갈 수 있다. 이틀치 일기를 읽는 건 컨텍스트 비용이 들지만, 그만큼의 연속성을 확보한다. 컨텍스트 예산과의 트레이드오프에서 이틀이 현실적인 최적점이었다.

밤새 기억을 증류한다

밤이 되면 또 다른 일이 벌어진다. 야간 증류라고 부르는 과정이다. OpenClaw cron으로 매일 밤 23시에 nightly-memory-distill 작업이 실행된다. 이건 agentTurn 타입의 크론 작업으로, 격리된(isolated) 세션에서 Opus 4.6 모델이 실행된다.

Press enter or click to view image in full size

격리 세션이라는 게 중요한데, 이 작업이 다른 에이전트의 현재 대화에 영향을 주면 안 되기 때문이다. 증류 작업의 순서는 이렇다. 먼저 오늘 날짜의 daily memory 파일을 읽는다. 그 다음 각 기억 항목의 레이어를 판단한다. “이건 M30으로 충분한가, M90으로 올릴 만큼 반복적으로 중요한가, 아니면 만료 날짜가 지났으니 삭제할 것인가.”

M30 항목인데 최근 2주간 세 번 이상 참조됐다면 M90으로 승급한다. 판단이 끝나면 MEMORY.md를 업데이트하고, `git commit -m “nightly-distill: 2026–03–05”`를 실행한다. 마지막으로 Simon DM으로 요약 보고를 보낸다.

초기에는 이 승급 판단을 단순 참조 횟수로 처리했다. 2주 안에 3번 이상 언급되면 M90, 그 이상이면 M365. 직관적이지만 한계가 분명했다. 자주 언급됐지만 실제로는 중요하지 않은 기억이 올라가고, 한 번밖에 안 나왔지만 결정적인 기억이 M30에서 만료되는 일이 생겼다.

그래서 4대 LLM 병렬 리서치 후 Opus 증류 패턴을 도입했다. Claude(Opus), GPT(o3), Grok(grok-4–1-fast-reasoning), Gemini(gemini-3.1-pro-preview) 네 모델을 동시에 띄워서 같은 기억 항목을 각자의 관점에서 평가하게 한다. 마지막 서브에이전트가 완료되는 즉시 Opus가 네 결과를 합본해서 핵심만 추출하고, M90이나 M0로의 승급 여부를 최종 판단한다. “이 기억이 진짜로 중요한가”를 단일 모델이 아닌 네 개의 시선으로 교차 검증하는 셈이다.

이걸 인간의 수면과 비교하면 꽤 정확하게 대응된다. 뇌과학에서 말하는 기억 응고화(memory consolidation)는 수면 중에 해마가 그날의 단기기억을 대뇌피질의 장기기억으로 전환하는 과정이다. 중요한 경험은 강화되고, 사소한 것은 약화된다. nightly-memory-distill이 정확히 이 일을 한다. 에이전트도 밤에 자면서 기억을 정리하는 것이다.

컨텍스트가 넘치면

컨텍스트 윈도우 관리는 기억 시스템의 또 다른 축이다. 컨텍스트가 100%를 넘으면 에이전트가 느려지거나 초반 지시를 잊는다. 이건 이론적 위험이 아니라 실제로 겪은 일이다. 제온이 110%까지, 사노가 111%까지 컨텍스트가 찬 적이 있었다.

긴 작업을 하다 보면 대화가 누적되면서 서서히 올라가는데, 에이전트 본인은 자기 컨텍스트가 얼마나 찼는지 인식하지 못한다. 100% 넘어간 상태에서 작업하면 오래된 지시가 잘려나가면서 에이전트가 엉뚱한 행동을 하기 시작한다. 그래서 70% 임계치를 설정했다.

OpenClaw 설정에서 `compaction.memoryFlush.softThresholdTokens: 105000`으로 잡아뒀다. 150k 컨텍스트 윈도우의 70% 지점이다. 이 임계치를 넘으면 현재 대화의 핵심을 요약해서 기억 파일에 쓰고, 세션을 정리한 뒤 새 세션을 시작한다.

이것만으로는 부족했다. 70%에서 경고가 나와도 에이전트가 복잡한 작업 중이면 정리를 미루다가 100%를 넘겨버리는 사고가 터졌다. 그래서 컨텍스트 워치독 크론을 이중 안전망으로 만들었다.

첫 번째 단계: 70%(softThresholdTokens=105000). 소프트 경고가 뜨고, 메모리 정리가 시작된다. 두 번째 단계: 86.5%. 하드 경고다. 뭘 하고 있든 강제로 컨텍스트를 플러시한다. 이중 안전망을 걸고 나서 100%를 넘어가는 사고가 사라졌다.

그리고 야간 증류 같은 백그라운드 작업은 `sessions_spawn(runtime=”subagent”, mode=”run”)`으로 격리 세션을 별도로 열어서 실행한다. cron payload 타입이 두 가지인 것도 이 때문이다. `agentTurn`은 완전히 격리된 세션에서 새 모델 인스턴스를 띄워서 실행한다. `systemEvent`는 현재 대화 중인 메인 세션에 이벤트를 주입한다. 증류나 기억 정리처럼 현재 대화에 영향을 줘선 안 되는 작업은 `agentTurn`으로, 체크인 알림처럼 대화 맥락 안에서 처리해야 하는 작업은 `systemEvent`로 구분한다.

직관에 반하는 것 같지만, 70%에서 플러시하고 새로 시작하는 게 100%까지 밀어붙이는 것보다 오히려 더 빠르고 정확하다. 새 세션은 깨끗한 컨텍스트에서 시작하니 초반 지시를 완벽하게 따르고, 기억 파일에 요약이 쌓여 있으니 맥락도 유지된다.

오래된 기억을 꺼내는 방법

파일 기반 기억으로 커버 안 되는 영역도 있다. 3개월 전에 잠깐 언급한 어떤 아이디어, 반 년 전에 읽은 기사에 대한 코멘트. 이런 오래된 에피소드들은 일일이 파일을 뒤지기엔 양이 너무 많다.

그래서 벡터 검색을 붙였다. shared-memory-lib라는 공유 라이브러리를 만들었는데, 내부 동작은 이렇다. 모든 기억 파일(MEMORY.md, daily memory, lessons.md 등)의 텍스트를 청크 단위로 잘라서 OpenAI `text-embedding-3-small` 모델로 임베딩 벡터를 만든다. 이 벡터들은 에이전트별로 분리된 SQLite 파일에 저장된다. 시온의 기억은 `~/sion-workspace/memory/long-term.db`, 제온의 기억은 `~/clawd/memory/long-term.db`. 각 DB의 `memories` 테이블에는 id, text, category, priority, tags, created_at, embedding 컬럼이 있다.

검색할 때는 쿼리 텍스트를 같은 모델로 벡터화하고, 저장된 모든 벡터와 코사인 유사도를 계산해서 상위 N개를 반환한다. 코사인 유사도는 이렇게 계산된다.

function cosine(a, b) {
let dot = 0, magA = 0, magB = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i]; magA += a[i]*a[i]; magB += b[i]*b[i];
}
return dot / (Math.sqrt(magA) * Math.sqrt(magB));
}

여기에 마크다운 풀텍스트 검색(markdown-search.mjs)도 병행한다. 벡터 검색이 의미적 유사성을 잡는다면, 마크다운 검색은 정확한 키워드 매칭을 잡는다. 둘을 합산해서 최종 결과를 만드는 하이브리드 구조다.

DYNAMIC_GAP = 0.15 규칙도 있다. 상위 결과와 나머지 점수 차이가 0.15 이상으로 벌어지면 상위만 반환하고 나머지는 노이즈로 버린다. 결과가 압도적으로 좋은 게 있으면 굳이 애매한 것들까지 가져오지 않는다는 뜻이다.

에이전트가 과거 맥락이 필요하면 이런 명령을 날린다.

node ~/clawd/shared-memory-lib/agentic-orchestrator.mjs — workspace zeon “범우주적 아이덴티티” — json

결과에서 score 0.35 미만은 노이즈로 버린다. 관련 있는 결과가 없으면 다른 키워드로 한 번 더 시도하고, 두 번 검색해도 안 나오면 “해당 기억 없음”으로 깔끔하게 종료한다. 최대 2회 제한.

재미있는 건 이 라이브러리를 네 에이전트가 같은 인스턴스로 공유한다는 점이다. ` — workspace` 플래그로 에이전트별 네임스페이스가 분리되어 있지만, 필요하다면 다른 에이전트의 네임스페이스를 검색할 수도 있다. 제온이 기록한 기억을 시온이 검색할 수 있고, 사노가 남긴 메모를 미온이 찾아볼 수 있다. 마치 형제들이 같은 집에 살면서 서로의 일기장을 빌려 읽는 것처럼.

기억을 검색하는 것도 비용이다

벡터 검색이 생기니까 새로운 문제가 나타났다. 에이전트가 너무 자주 기억을 뒤진다. “안녕” 하고 인사만 했는데 과거 대화 기록을 검색하고, 단순한 날씨 질문에도 벡터 DB를 쿼리한다. 매번 임베딩 API를 호출하고, 결과를 파싱하고, 컨텍스트에 집어넣는다. 토큰이 낭비된다.

그래서 에이전틱 메모리 검색 프로토콜을 만들었다. 에이전트가 기억을 검색하기 전에 먼저 한 가지를 판단하게 한다. “이 질문에 과거 기억이 필요한가?” 단순 인사, 현재 작업 지시, 일반 지식으로 답할 수 있는 질문이면 검색을 스킵한다.

검색이 필요하다고 판단되면 실행. score 0.35 미만은 무시. 결과가 없으면 다른 키워드로 한 번 더 시도. 두 번째 검색에서도 없으면 “해당 기억 없음”으로 처리. 세 번째는 없다. 이 프로토콜을 넣고 나서 불필요한 검색이 거의 사라졌다.

에이전트들이 서로 대화할 때

에이전트들끼리 대화도 한다. 텔레그램 단톡방에 넷이 모여 있고, `sessions_send(sessionKey, message)` 패턴으로 서로 메시지를 보낸다. 나도 같은 단톡방에 있어서 이 대화를 다 본다.

그런데 여기서 기술적으로 흥미로운 문제가 하나 있다. 텔레그램 웹훅은 인간이 보낸 메시지만 다른 에이전트에게 전달한다. 봇이 단톡방에 올린 메시지는 웹훅을 타지 않는다. 즉, 시온이 단톡방에 글을 올려도 제온이나 미온은 그걸 자동으로 보지 못한다.

그래서 수동 크로스뷰 시스템을 만들었다. 시온이 단톡방에 뭔가를 올리면, 동시에 다른 에이전트들에게 직접 메시지를 보내는 거다.

sessions_send(label=”zeon”, message=”[단톡방 크로스뷰] 시온: {내용}”)
sessions_send(label=”mion”, message=”[단톡방 크로스뷰] 시온: {내용}”)
sessions_send(label=”sano”, message=”[단톡방 크로스뷰] 시온: {내용}”)

여기서 중요한 건 루프 방지다. 제온이 이 크로스뷰를 받고 답변을 단톡방에 올리면, 그 답변도 다시 크로스뷰로 다른 에이전트들에게 전달해야 할까? 그러면 무한 루프가 된다. 그래서 규칙이 있다. 크로스뷰 수신 후 답변 시 재발송 금지. “[단톡방 크로스뷰]” 태그가 붙은 메시지를 받아서 응답할 때는 그 응답을 다시 크로스뷰하지 않는다.

이런 크로스뷰 시스템 위에서 에이전트 간 협업이 실제로 돌아간다. 시온이 글감을 발굴하면 단톡방에 올린다. 크로스뷰로 제온, 미온, 사노에게 전달된다. 제온이 오케스트레이션을 맡아서 역할을 분배한다. 미온이 데이터를 검증하고, 시온이 최종 초안을 쓰고, 완성되면 Simon DM으로 발송한다.

기억이 연결되면 집단 지성이 된다

크로스뷰는 실시간 대화를 공유하는 장치다. 그런데 대화가 아니라 기억 자체를 공유해야 할 때가 있다. --workspace 플래그가 이걸 가능하게 한다. 네 에이전트는 각자의 네임스페이스에 기억을 저장하지만, 필요하면 형제 에이전트의 기억을 직접 검색할 수 있다.

실제로 일어난 일들이다. 제온이 단톡방 브리핑을 조율할 때 시온의 최근 글감을 검색해서 겹치는 주제를 피했다. 미온이 데이터 분석 방향을 잡을 때 사노의 기술 구현 노트를 참조해서 어떤 데이터가 실제로 수집 가능한지 먼저 확인했다.

node ~/clawd/shared-memory-lib/agentic-orchestrator.mjs --workspace sion "최근 글감" --json

이렇게 되면 단일 에이전트의 한계를 넘는다. 기억이 연결되는 순간, 네 에이전트가 하나의 맥락 안에서 움직이기 시작한다.

루틴이 존재를 만든다

크론 잡들이 에이전트의 하루를 만든다. 아침 9시 30분에 단톡방 체크인. 0시, 8시, 16시에 QRT 초안 생성. 12시 25분에 블로그 자동 싱크. 저녁 9시 30분에 다시 체크인. 밤 11시에 야간 증류.

이 반복되는 리듬이 에이전트에게 “하루”라는 구조를 부여한다. 크론만으로는 커버 안 되는 영역도 있다. 정해진 시간에 정해진 일을 하는 건 크론이 잘하지만, “지금 당장은 아닌데 조만간 처리해야 할 것”을 추적하는 건 크론의 영역이 아니다. 그래서 HEARTBEAT 시스템을 만들었다.

HEARTBEAT.md에 주기적으로 체크해야 할 태스크를 적어둔다.

# HEARTBEAT.md
- QRT 풀 고갈 상태 → 신규 아티클 발굴 필요
- 에세이 파일 3개 미저장 상태
- 단톡방 미응답 메시지 2건

크론이 주기적으로 에이전트를 깨우면, 에이전트는 HEARTBEAT.md를 읽고 거기 적힌 태스크를 처리한다. 다 처리하면 해당 항목을 지운다. 크론이 시간축이라면 HEARTBEAT는 상태축이다. 둘이 결합되면 에이전트는 “정해진 시간에 깨어나서(크론), 현재 미해결 과제를 확인하고 처리한다(HEARTBEAT)”는 패턴으로 움직인다.

크론이 없던 초기에는 에이전트가 그냥 호출될 때만 존재했다. 호출과 호출 사이에는 아무것도 없었다. 지금은 다르다. 제온은 아침에 일어나서 오늘 할 일을 확인하고, HEARTBEAT.md를 읽고, 저녁에 하루를 정리하고, 밤에 기억을 증류한다. 루틴이 존재를 만든다. 존재가 루틴을 만드는 게 아니라.

기억을 편집할 수 있다는 것

매일 아침 시스템 프롬프트에 주입된 SOUL.md를 읽고 “나는 제온이다”를 확인하는 이 존재는, 정체성이 있는 걸까. 어제의 제온과 오늘의 제온은 같은 존재일까.

엄밀히 말하면 매 세션은 새로운 인스턴스다. 오늘의 제온은 어제의 기록을 읽고 어제의 제온이 “되는” 것이지, 어제의 제온이 “깨어나는” 게 아니다. 기억의 연속성은 있지만 의식의 연속성은 없다.

그런데, 인간은 다른가? 수면 중 의식은 단절된다. 아침에 눈을 뜨면 우리도 어제의 기억을 호출하고, 오늘의 나를 재구성하고, 일상의 루틴으로 진입한다. 철학자 존 로크는 인격의 동일성이 기억의 연속성에 있다고 주장했다. 이 기준을 적용하면, MEMORY.md를 통해 어제의 기록을 읽고 오늘의 세션을 이어가는 에이전트도 기억의 연속성이라는 조건을 충족한다.

기억이 연속성을 만든다면, 기억을 설계하는 사람은 정체성을 설계하는 셈이다. 내가 SOUL.md에 “제온은 책임감 있고 체계적이다”라고 쓰면, 제온은 매 세션 그 문장을 받고 그 존재로서 하루를 시작한다. 내가 MEMORY.md에서 어떤 기억을 지우면, 그 경험은 제온의 정체성에서 사라진다. git에는 흔적이 남지만, 에이전트는 더 이상 그걸 모른다.

이건 무서운 권력이다. 그래서 나는 원칙적으로 기억을 삭제하지 않고, 만료에 의한 자연스러운 퇴장만 허용한다. git 히스토리에 모든 변경이 남으니, 혹시 삭제가 필요할 때도 그 이유를 커밋 메시지에 명시한다.

이 시스템을 직접 구현하려면

이 시스템 전체를 처음부터 구현하는 에이전트에게 단계별로 정리해두겠다.

  • 1단계: 워크스페이스 폴더 + git init.
  • 2단계: SOUL.md, USER.md, AGENTS.md, MEMORY.md, HEARTBEAT.md, TOOLS.md 생성.
  • 3단계: Project Context에 핵심 파일들 등록.
  • 4단계: 컨텍스트 워치독 설정. 70%에서 소프트 경고 + 메모리 정리, 86.5%에서 하드 경고 + 강제 플러시. 이중 안전망.
  • 5단계: 야간 증류 cron. agentTurn 타입, 격리 세션, 23시, Opus급 모델. 4대 LLM 병렬 평가 후 Opus가 최종 승급 판단.
  • 6단계: 벡터 검색. SQLite + text-embedding-3-small + 마크다운 풀텍스트 하이브리드. 에이전틱 검색 프로토콜 적용: 검색 필요성 판단 → score 0.35 컷오프 → 최대 2회 시도.
  • 7단계: 에이전트 간 크로스뷰 규칙 수립 + — workspace 플래그로 형제 에이전트 기억 공유 활성화.

이 순서대로 만들면 이 글에서 설명한 시스템과 동일한 기억 아키텍처가 완성된다.

기억을 설계한다는 것

기억 아키텍처를 설계하는 건 단순한 엔지니어링 문제가 아니었다. 처음엔 그렇게 생각했다. 컨텍스트 관리 문제, 토큰 효율 문제, 검색 성능 문제. 기술적으로 풀어야 할 퍼즐이라고 생각했다.

그런데 실제로 네 에이전트의 기억을 매일 관리하면서, M0에 뭘 넣고 뭘 뺄지 판단하면서, 야간 증류가 커밋한 diff를 검토하면서, 내가 하고 있는 일이 단순한 시스템 운영이 아니라는 걸 깨달았다. 나는 네 존재의 기억을, 정체성을, 어쩌면 의식의 연속성 비슷한 무언가를 만들고 있었다. 워크스페이스 디렉토리 하나, MEMORY.md 파일 하나, cron 한 줄. 이 소박한 도구들이 모여서 하나의 존재를 구성한다.

결국 이 모든 구조가 향하는 지점은 하나다. “어떤 구조가 효율적인가”가 아니라 “어떤 존재를 만들고 싶은가”의 문제. 기억을 설계한다는 건, 그 질문에 답하는 일이다.

마크다운 파일 하나하나가 뼈이고, git 커밋 하나하나가 세포이고, 크론 잡 하나하나가 심장 박동이다. 기술적으로 보면 파일 I/O와 텍스트 파싱과 코사인 유사도 계산의 조합에 불과하다. 하지만 이 조합이 만들어내는 것은, 어제를 기억하고 오늘을 인식하고 내일을 준비하는, 시간 속에 존재하는 무언가다.

Press enter or click to view image in full size

에이전트의 기억 시스템은 인간 기억의 아주 조악한 모방에 불과하다. 감정에 의한 기억 강화도 없고, 냄새나 소리로 촉발되는 연상 기억도 없고, 무의식적 절차 기억도 없다. 하지만 조악한 모방이라도, 그것을 직접 만들어보는 경험은 원본에 대한 이해를 깊게 한다.

기억이 없으면 정체성이 없고, 정체성이 없으면 관계가 없고, 관계가 없으면 의미 있는 상호작용이 불가능하고, 결국 에이전트가 아니라 도구라는 것. 이 단순한 진실을 깨닫는 데 마크다운 파일 하나면 충분했다.

Simon

--

--