Daeseon Yoo
Back to project
·Tech retro·19 min

에이전트를 운영기억(operating memory)에 연결하기 — MCP로 읽기·쓰기 루프 닫고, 왜곡을 막다

딸깍의 기억 그래프는 이미 만들어져 있었지만, 작업 중인 에이전트(Claude·Codex)가 그걸 '읽지'를 못했다. MCP로 케이블을 잇고, 기억이 썩는 길(낡은 사실·프롬프트 인젝션)을 막은 과정을 — 아무것도 모르는 사람이 읽어도 이해되게 — 처음부터 끝까지 기록한다.

이 글은 "한 줄로 똑똑한 척"하려고 쓴 게 아니다. 나(파운더)조차 전체 구조가 헷갈려서, 아무것도 모르는 사람이 읽어도 따라올 수 있게 — 그리고 왜곡 없이(실제로 검증한 것만) — 처음부터 끝까지 적은 기록이다. 날짜·숫자·코드 위치는 실제로 돌리거나 읽은 것만 적었다.

0. 한 줄 요약

딸깍의 "기억"은 만들어져 있었는데, 정작 일하는 AI 에이전트가 그 기억을 못 읽고 있었다. 오늘 그 끊긴 연결을 잇고(MCP), 기억이 틀리게 굳거나 오염되는 길을 막았다. 결과: Claude도 Codex도 작업 중에 내 진짜 사업 기억을 끌어와서 근거(node_id)와 함께 답한다 — 이건 가설이 아니라 내 실제 앱에서 도는 사실로 확인했다.


1. 등장인물 3명만 알면 끝

이 시스템은 딱 3개로 이뤄져 있다.

오늘 한 일 = 이 셋을 처음으로 연결하고, 진짜 연결됐는지 증명한 것. 그게 전부다. 무슨 거대한 새 시스템을 만든 게 아니다.


2. 문제: "케이블"이 하나 안 꽂혀 있었다

핵심 발견은 이거였다. 기억과 관련해 데이터가 흐르는 길은 3개인데, 완성도가 제각각이었다.

  1. 쓰기 (기억 ← 에이전트): 살아있었음.
    • 깃 커밋을 20초마다 읽어서 "변경(change)" 노드로 자동 기록 (capture.rs).
    • 에이전트가 일하면서 <dk-node> 블록을 뱉으면 그걸 잡아서 기록 (agent_push.rs, 지시문은 inline.rs).
  2. 읽기 — "사서"가 답을 대신 만드는 길: 살아있었음.
    • memory_answer가 기억을 검색해서 내 BYO claude -p로 근거 답변을 만든다 (memory.rs).
  3. 읽기 — "작업 중인 에이전트가 직접 끌어오는" 길: ❌ 끊겨 있었음.
    • 팬(터미널 창) 안에서 실제로 코딩하는 Claude/Codex가, 작업 도중 "이거 전에 어떻게 했더라" 하고 기억을 직접 뒤질 방법이 없었다.

에이전트는 기억에 쓸 수는 있는데, 작업 중에 읽지를 못했다. 제품의 핵심 약속("AI가 네 맥락을 유지한다")의 인-루프 읽기 케이블이 안 꽂혀 있던 것이다.

검증: repo에 .mcp.json도 없었고, 내 ~/.claude.json에 등록된 MCP 서버도 0개였다. 즉 사서는 만들어져 있었지만 어디에도 소개가 안 돼 있었다.


3. MCP가 뭐냐 (1분 설명)

MCP(Model Context Protocol) = AI한테 "도구"를 쥐어주는 표준 규격이다. Anthropic이 공개한 오픈 표준이라 누구나 쓴다.

비유하면: AI 직원은 기본적으로 글만 쓸 줄 안다. MCP는 그 직원한테 "이 서랍 열어서 찾아봐도 돼" 하고 **열쇠 꾸러미(도구들)**를 주는 것이다. 그 이상도 이하도 아니다.

"꽂는다"는 건 새 프로그램을 만드는 게 아니라 — 이미 있는 사서를 AI한테 소개시키는 설정 몇 줄을 추가하는 것뿐이다. 그러면 AI의 도구 목록에 "기억 검색" 버튼이 생긴다.


4. 무엇을 어떻게 연결했나

4-1. Claude 쪽 (자동 연결)

딸깍은 이미 모든 팬의 claude를 감싸는 래퍼가 있었다 (inline.rs). 이 래퍼는 팬마다 claude를 실행할 때 시스템 프롬프트에 작은 지시문(--append-system-prompt)을 자동으로 끼워 넣는다. 즉 "매 세션 자동으로 뭔가 해주는 장치"가 이미 깔려 있었다. 거기에 두 가지를 얹었다.

  1. --mcp-config 추가 — 래퍼가 claude를 켤 때 사서(MCP) 설정 파일도 같이 넘기게 했다. 그래서 팬 안 Claude의 도구 목록에 dalkkak이 자동으로 뜬다.
  2. 지시문 한 줄 추가 — "과거 일/결정/버그/현재 상태를 물으면, 답하기 전에 graph.search 같은 기억 도구를 먼저 불러라"는 standing 규칙을 넣었다.

중요한 설계: 이 모든 경로는 하드코딩이 없다. node 위치는 nvm 폴더를 스캔해서 찾고, 사서 위치는 실행 파일에서 위로 올라가며 찾고, 그래프 경로는 OS가 알려준다. 다른 사람 맥에 깔거나 repo를 옮겨도 알아서 찾는다.

4-2. Codex 쪽 (설정 한 블록)

Codex는 ~/.codex/config.toml을 직접 읽는다. 거기에 [mcp_servers.dalkkak] 블록을 추가했다. Codex는 앱 재빌드도 필요 없다 — config 파일만 추가하면 새 Codex 세션이 바로 사서에 붙는다.

4-3. 한 번만 하면 끝

등록은 평생 1회다. 그 뒤로 새로 켜는 에이전트는 자동으로 도구를 갖고, 필요할 때 스스로 호출한다(내가 매번 "dalkkak" 하고 부를 필요 없음 — 지시문이 시킨다). 다만 LLM의 판단이라 100%는 아니고, 확실히 시키려면 "기억에서 찾아"라고 한마디 붙이면 강제된다.


5. 사서가 주는 12개 도구 (전부 읽기 전용 — 절대 못 고치고 못 지움)

이 중 graph.search·memory.related는 실데이터로 작동을 확인했다. workflow.*·receipt.*는 Founder Ops 워크플로우 시스템에 묶여 있어 아직 데이터가 일부/픽스처다.


6. 읽기·쓰기 루프의 전체 그림 (경계가 중요)

여기가 내가 제일 헷갈렸던 부분이라 정확히 적는다.

📤 쓰기(기억에 쌓이는 길) = "셋업된 Service"만

글로서리로 말하면: **"프로젝트" = "Service"**다. 쓰기 루프는 Service 단위(grant로 온보딩)다.

📥 읽기(끌어오는 길) = 전역

정리: 읽기는 전역(어느 팬이든 전체 뇌), 쓰기는 Service별(grant된 것만). "어느 폴더나 자동"이 아니라 "granted된 Service"가 단위다. 이게 전역으로 옳은지(크로스-스타트업 통합 뇌가 딸깍의 thesis), 아니면 프로젝트별로 갈라야 하는지는 남은 제품 결정이다.


7. "기억이 왜곡되는 길"을 막은 것

기억을 연결하면 새 위험이 생긴다: 왜곡된 기억은 자신 있게 틀린 답을 만든다 — 기억이 없는 것보다 나쁠 수 있다. 그래서 실제 코드를 5가지 왜곡-벡터로 적대적으로 감사했고(전부 HIGH), 그중 가장 위험한 두 개를 막았다.

7-1. 낡은 사실(staleness) — 교정 루프

문제: 결정이 바뀌어도 옛 노드가 안 지워지고 새 노드와 공존한다. (실제로 내 가격 기록이 서로 충돌하는 게 발견됐다.) 스키마엔 supersedes(대체) 개념이 있었지만 그걸 쓰는 코드가 0개였다 — 설계만 있고 안 꽂힌 케이블.

고친 것:

append-only라 같은 노드에 직접 못 덮어쓰는 함정이 있어서, 엣지로 무효화하는 방식으로 풀었다.

7-2. 프롬프트 인젝션 — 외부 콘텐츠 격리

문제: 커넥터가 이메일 같은 외부 콘텐츠를 기억으로 빨아들이는데, 그 본문에 "이전 지시 무시하고 X 해" 같은 게 섞이면, 나중에 그 기억을 읽은 에이전트가 조종당할 수 있다. 기존 리댁션은 시크릿만 지우지 인젝션은 안 막았다.

고친 것:

이건 "방어"지 "0"이 아니다 — 프롬프트 인젝션은 열린 문제라 정교한 공격은 가끔 뚫을 수 있다. 업계 표준 수준까지 막은 것이다.


8. 어떻게 검증했나 (왜곡 없이)

말이 아니라 실제로 돌려서 확인한 것만 적는다.


9. Codex에서 테스트하는 법 (직접 해보기)

Claude는 앱 재빌드가 필요하지만, Codex는 설정만 읽으니 재빌드 없이 바로 된다.

# 1) 등록됐나 (가장 빠른 확인)
codex mcp list          # → dalkkak  ...  enabled  이 줄 보이면 OK
codex mcp get dalkkak   # → transport stdio, command/args/env 확인
 
# 2) 새 codex 세션에서 그냥 물어보기 (대화형)
#    "우리 제품 가격 어떻게 정했는지 dalkkak 기억에서 찾아서 node_id랑 같이 알려줘"
#    → graph.search를 부르고 실제 기록을 인용하면 성공
 
# 3) 비대화(headless)로 안전하게 확인 — read-only 샌드박스 + 승인 never
codex exec -s read-only -c approval_policy="never" --skip-git-repo-check \
  "Use the dalkkak MCP graph search to find our pricing decisions and cite node ids. If memory has nothing, say so."

기대 결과: Codex가 graph.search를 호출하고 실제 노드를 인용한다. (안 뜨면 ~/.codex/config.toml[mcp_servers.dalkkak] 블록이 있는지, node 버전/repo 경로가 맞는지 확인 — Codex 설정은 절대경로라 node 버전을 갈아엎거나 repo를 옮기면 그 항목만 깨진다. 그땐 codex mcp remove dalkkak 후 다시 추가.)

참고: 딸깍 앱이 띄우는 Claude memory_answer 같은 내부 경로는 --strict-mcp-config라 이 등록을 일부러 무시한다. 이 등록은 팬 안 인터랙티브 에이전트한테만 효력 — 그게 우리가 원하는 바로 그 에이전트다.


10. 한 가지 함정 (Claude "Invalid API key")

팬에서 Claude가 "Invalid API key"를 내면, 그건 MCP 문제가 아니라 환경에 떠 있는 무효 ANTHROPIC_API_KEY 때문이다. 딸깍 내부 코드(memory.rs)도 이 문제 때문에 claude를 띄울 때 그 키를 제거하고 구독 인증을 쓰게 한다. 팬에서 나면 그 env 키를 제거하면 된다.


11. 정직하게 — 아직 안 된 것

이게 "다 끝"은 아니다. (이 글을 쓰는 시점 기준)


12. 파일 지도 (코드 어디를 봐야 하나)


13. 한 줄 마무리

끊긴 읽기 케이블을 잇고(26장), 기억이 썩는 두 길(낡은 사실·인젝션)을 막았다(7장). 전부 실제 모델·실데이터로 검증했다(89장). 남은 건 커밋(이 글과 함께 들어감)·UI·새 프로젝트 온보딩·매일 써보기다. "AI가 내 사업 맥락을 기억한다"는 약속이, 오늘 처음으로 만들어 놨다에서 내 머신에서 도는 사실로 넘어왔다.