본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 06. 17. 09:10

자작 MCP 서버에 기억을 학습시켰더니, 3개 프로젝트를 가로질러 11개의 버그를 발견했다

요약

Claude Code의 세션 간 기억 공유 문제를 해결하기 위해 MCP 서버인 'memdream'을 구축한 사례를 소개합니다. TiDB Cloud의 벡터 검색과 Ollama를 활용하여 프로젝트 간 의미적 기억을 공유하며, 이를 통해 다수의 프로젝트에서 다수의 버그를 발견하는 성과를 거두었습니다.

핵심 포인트

  • MCP 서버를 통해 Claude Code에 프로젝트 간 통합 기억 기능 부여
  • TiDB Cloud의 벡터 검색과 Ollama 임베딩을 결합한 메모리 구조
  • Codex와 Gemini를 병렬로 실행하여 코드 리뷰 자동화 구현
  • 실제 프로젝트 3개에서 다수의 크리티컬 및 하이 버그 발견

AI에게 "이전 대화 기억하고 있어?"라고 물어본 적 있어?

Claude Code를 매일 사용하며 개발하고 있는데, 세션이 바뀔 때마다 "이 프로젝트의 구성은..."이라며 다시 설명하는 것이 은근히 귀찮다. claude-mem이라는 플러그인으로 observation(작업 기록)은 쌓이지만, 프로젝트를 가로질러 "M2DX-Core에서 고친 그 버그, MIDI2Kit 측에도 영향을 주고 있지 않아?" 같은 검색을 할 수 없다.

그래서 만든 것이 memdream이다. MCP 서버로서 Claude Code에 접속하여, TiDB Cloud의 벡터 검색(Vector Search)으로 "의미적으로 가까운 기억"을 가져오는 메커니즘이다. 에코시스템 단위로 기억을 공유할 수 있는 것이 포인트다.

이번에 진행한 것은, 이 memdream 자체와 내가 개발 중인 3개 프로젝트(M2DX, M2DX-Core, MIDI2Kit)를 Codex와 Gemini에게 병렬 리뷰시키고, 그 결과를 전부 memdream에 학습시키는 실험이다.

결론부터 말하자면.

리뷰 결과: 3개 프로젝트 × 2개 에이전트 = 6회의 풀 리뷰 (Full Review) -
발견한 버그: CRITICAL 4건, HIGH 12건 이상 -
즉시 수정: 12건 (memdream 6건 + M2DX 4건 + MIDI2Kit 3건, 테스트 전원 통과) -
memdream에 축적: 110 observations (과거 이력 임포트 포함)

…한 세션에서 이 정도까지 돌아갔다는 사실에 솔직히 놀랐다.

memdream이란 무엇인가

pnpm 모노레포(Monorepo)이며, 내부 구조는 심플하다.

packages/
mcp-server/ ← 9개 도구의 MCP 서버 (stdio)
dream-agent/ ← 통합 처리 에이전트 (Phase 2, 미구현)

MCP 도구는 9개:

memory_observe
— 관측을 기록 (Ollama bge-large로 1024차원 벡터 생성) -
memory_search
— 시맨틱 검색 (Semantic Search) (TiDB의 VEC_COSINE_DISTANCE 사용) -
memory_recall
— 프로젝트의 통합 기억을 취득 -
memory_session_start
— 세션 시작 시의 컨텍스트 주입 - 나머지 5개 (stats, timeline, correct, list_projects, dream_status)

스토리지(Storage)는 TiDB Cloud Serverless이다. VECTOR(1024) 컬럼에 HNSW 인덱스를 걸고, 코사인 거리(Cosine Distance)로 벡터 검색을 수행한다. 임베딩(Embedding)은 Ollama bge-large (로컬, API 키 불필요)를 사용한다.

AI 도구 → MCP tools → memdream → TiDB Cloud
↓
Ollama bge-large (로컬)

Codex × Gemini 병렬 리뷰의 메커니즘

수행한 작업은 간단하다. Codex와 Gemini를 백그라운드에서 동시에 실행하는 것이다.

Claude Code (부모)
├── Agent(codex:rescue) → Codex에 풀 리뷰 의뢰
└── Bash(gemini -p ...) → Gemini CLI에 소스 전체를 파이프(Pipe)로 전달

Codex는 Agent tool에서 subagent_type: "codex:codex-rescue"를 지정한다. Gemini는 CLI의 -p 플래그로 비대화형(Non-interactive) 실행을 한다. 둘 다 run_in_background: true로 병렬 처리한다.

여기서 처음에 막혔던 것이 Gemini CLI의 구문이다.

# ❌ 이렇게 하면 에러 ( -p 뒤에 인자가 없음)
cat source.ts | gemini -p
# ✅ 이렇게
...

-p는 "비대화형 모드로 실행한다"는 플래그이지만, 프롬프트 문자열을 직접 인자로 받는다. stdin(표준 입력)만으로는 작동하지 않는다.

memdream 자체 리뷰에서 발견된 것들

Gemini의 평가

"매우 깔끔하고 완성도 높은 코드. TiDB Vector의 실전적인 도입 사례로서 뛰어나다"

…기쁘긴 하지만, Codex가 더 엄격했다.

Codex가 찾아낸 지적 사항 (48건)

11개 카테고리, 파일 경로와 행 번호 포함. 특히 뼈아팠던 부분은 다음과 같다.

리뷰 중 발견한 실제 버그:

// memory-search.ts — LIMIT ? 가 TiDB의 prepared statement에서 작동하지 않음
ORDER BY distance ASC LIMIT ?
// → "Incorrect arguments to LIMIT" 에러

pool.execute()는 MySQL의 prepared statement를 사용하지만, TiDB(MySQL 호환)에서 LIMIT ?에 파라미터를 전달하면 "Incorrect arguments to LIMIT" 에러와 함께 중단된다. 알려진 문제인 듯하다. limit은 이미 검증(1-50 사이의 정수)을 마쳤으므로 직접 임베딩(embedding) 방식으로 수정했다.

// 수정 후
ORDER BY distance ASC LIMIT ${limit}

리뷰를 요청했더니, 리뷰 대상인 도구 자체에 버그가 있어 검색이 작동하지 않는 상황.

수정한 5건

  • dream-agent의 빈 src가 pnpm build를 깨뜨림 → stub 생성
  • config.test.ts가 OpenAI/1536을 전제로 함 → Ollama/1024로 수정
  • memory_observe의 INSERT 시 embedding_model 미저장 → 컬럼 추가
  • memory_recall에서 에코시스템 스코프의 기억을 가져오지 못함 → OR 조건 추가
  • setup-db.ts002_vector_indices.sql을 적용하지 않음 → 추가

모두 빌드 + 테스트 통과.

M2DX 리뷰에서 발견된 CRITICAL

M2DX는 MIDI 2.0 FM 신디사이저(DX7 호환)이다. Swift 6로 작성되었으며, 오디오 렌더링 스레드는 "할당(allocate)하지 않는다, 블로킹(block)하지 않는다"가 절대 규칙이다.

…인데 말이다.

os_unfair_lock을 사용하고 있었음

FXParamBox가

// FXParamBox.swift — 렌더 패스에서 호출됨
func snapshot() -> FXSnapshot {
os_unfair_lock_lock(&lock) // ← 이것
...

Gemini도 Codex도 같은 지점을 지적했다. os_unfair_lock은 Darwin에서 가장 가벼운 락(lock)이지만, 엄밀히 말하면 우선순위 역전(Priority Inversion)의 위험이 있다. M2DX-Core의 SnapshotRing(Atomic 기반의 triple buffer)과 동일한 패턴으로 lock-free화 했다.

Downsampler의 짧은 버퍼 OOB 크래시

// Downsampler.swift L179 — tailStart가 음수가 됨
let tailStart = oversampledCount - tailSize
// frameCount * 2 < taps - 1 일 때 → 음수 → srcL[tailStart + i]에서 OOB(Out of Bounds)

이것은 Codex만이 찾아낸 실제 버그다. Gemini는 주파수 계산의 정밀도 문제를 지적했지만, 크래시가 발생하는 버그는 놓치고 있었다.

수정한 4건

  • FXParamBox → Atomic triple buffer (렌더 wait-free)
  • midiRing 용량 256→1024 + Note Off 재시도 (stuck note 대책)
  • Downsampler 짧은 버퍼 가드 추가
  • oversampling >1024 frame의 청크 분할 렌더링

M2DX-Core 108개 테스트 모두 통과 + M2DX 앱 xcodebuild BUILD SUCCEEDED.

MIDI2Kit 리뷰에서 발견된 CRITICAL 3건

MIDI2Kit은 Swift 6 기반의 MIDI 2.0 라이브러리다. 4개 모듈 구성(Core/Transport/CI/PE).

Gemini의 평가:

"Swift 6 시대의 MIDI 2.0 개발 표준이 될 수 있다"

…라고 하더니, Codex가 CRITICAL을 3개나 내놓았다.

#문제위치
#45
PEManager.deinit가 continuation을 resume하지 않고 파괴 → 영구 행(hang)
PEManager.swift:240
#46
CoreMIDITransport의 가변 상태(mutable state)가 잠금(lock) 없이 사용 → 데이터 레이스 (data race)
CoreMIDITransport.swift:322
#47SysEx7 수신 버퍼가 무제한으로 증가 → 메모리 고갈CoreMIDITransport.swift:262

3건 모두 수정하여 705개 테스트/100개 스위트(suite) 전체 통과.

Codex vs Gemini, 어느 쪽이 더 강력할까?

6회의 리뷰를 통해 발견한 패턴.

관점GeminiCodex
첫인상칭찬한 뒤에 지적함갑자기 CRITICAL부터 시작함
...
어느 한쪽만 사용하면 놓치게 된다. 양쪽 모두 실행하여 대조하는 것이 가장 신뢰할 수 있다.

claude-mem → memdream 일괄 임포트 (import)

과거 세션의 관찰(observation)을 memdream으로 이전하는 스크립트도 작성했다.

// import-m2dx.mjs — 각 observation을 Ollama로 벡터화하여 TiDB에 INSERT
for (const o of obs) {
const res = await fetch("http://localhost:11434/api/embed", {
...

memdream 49건 + M2DX 15건 + M2DX-Core 30건 = 94건을 일괄 투입. 리뷰 결과와 합쳐 110 observations가 TiDB에 들어갔다.

숫자로 되돌아보기

지표
전체 리뷰 실시6회 (3개 프로젝트 × Codex + Gemini)
...
1 세션, 몇 시간. 3개 프로젝트의 전체 리뷰 → 버그 수정 → 이슈(issue) 생성 → 기억 축적까지 완료되었다.

다음에 할 일

dream-agent

(Phase 2) — observations를 통합하여 consolidated_memories를 생성하는 야간 배치 -
memory_search

의 시맨틱 검색(semantic search)이 작동하게 되었으므로, 다음 세션부터는 "전에 이 버그 본 적 있어?"라는 질문이 가능하다 - MIDI2Kit #48(비트 레플리케이션) 대응

…Codex와 Gemini를 토론시키는 것도 재미있을 것 같다. "이 설계에 대해 어떻게 생각해?"를 양쪽에 던지고, 의견이 갈리는 부분만 인간이 판단하는 방식 말이다.

이것은 memdream을 만들기 시작했을 무렵(2026-05)의 기록이다. 그 후 dream-agent(야간 통합 배치)도 구현하여 8주간 운용한 전체 모습은 별도 기사에 정리했다.

본 기사는 Zennfes Spring 2026 × TiDB 응모 작품입니다.

Discussion

AI 자동 생성 콘텐츠

본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0