
RootCause: 숙취 없는 기억력을 코드베이스에 부여하기
요약
Cognee를 활용하여 코드베이스의 커밋 히스토리에 인과 관계 체인을 구축하는 RootCause 프로젝트를 소개합니다. 벡터 유사도 검색의 한계를 극복하기 위해 하이브리드 그래프-벡터 메모리 계층을 사용하여 에러와 근본 원인 간의 관계를 추적합니다.
핵심 포인트
- 단순 벡터 검색의 한계를 그래프 탐색으로 해결
- 에러-근본 원인-수정-관련 버그로 이어지는 인과 관계 체인 구축
- Cognee 기반의 하이브리드 그래프-벡터 메모리 계층 활용
- 코드베이스에 장기적인 맥락과 기억력을 부여
**Bhavesh Patil**과 **Shreya Shelar**가 **WeMakeDevs × Cognee의 해커톤, "The Hangover Part AI: Where's My Context?"**을 위해 제작했습니다.
요약 (TL;DR)
-
문제점: 여러분의 코드베이스에는 기억력이 없습니다. 오늘 수정한 버그가 3주 전의 버그와 단지 에러 메시지만 다를 뿐, 동일한 근본 원인(Root Cause)을 가지고 있다는 사실을 알지 못합니다.
-
우리가 만든 것: RootCause — 어떤 GitHub 리포지토리(repo)를 지정하더라도, Cognee를 기반으로 한 하이브리드 그래프-벡터 메모리 계층(hybrid graph-vector memory layer)에 커밋 히스토리(commit history)를 주입하여, 모든 수정 사항에 대한 인과 관계 체인(causal chain)을 구축합니다:
에러(Error) → 근본 원인(Root Cause) → 수정(Fix) → 수정된 파일(Files Touched) → 관련 버그(Related Bugs). -
왜 벡터 DB(vector DB)만이 아닌 그래프(graph)가 필요했는가: 근본 원인은 공유하지만 표현 방식이 완전히 다른 두 버그를 찾아내는 것은 그래프 탐색(graph-traversal) 문제입니다. 벡터 유사도(Vector similarity)만으로는 이미 비슷하게 들리는 것들만 잡아낼 수 있습니다.
-
반전: 이것을 만드는 동안 데모를 망칠 뻔한 세 가지 조용한 버그를 발견했습니다. 표면을 넘어 파고들기 전까지는 각각 모두 괜찮아 보였습니다. 더 유용한 기록이 될 것이기에 이 모든 내용을 여기에 포함합니다.
-
🔗 GitHub:
[https://github.com/bhaveshpatil093/rootcause](https://github.com/bhaveshpatil093/rootcause) -
🎥 데모 영상:
[https://youtu.be/kj4eBlZlj9g](https://youtu.be/kj4eBlZlj9g) -
🌐 라이브 데모:
[https://rootcause-cognee.vercel.app/](https://rootcause-cognee.vercel.app/)
우리 모두가 인지하는 문제
버그를 수정하고 안도의 한숨을 내쉬었는데, 3주 후에 완전히 다른 메시지를 가진 다른 에러가 나타났지만, 사실은 동일한 근본 원인(root cause)이었던 디버깅 세션을 경험해 본 적이 있나요?
당신의 코드베이스는 첫 번째 수정 사항에 대한 기억이 없습니다. 두 버그가 관련되어 있다는 것을 알지 못합니다. 검색창도, 이슈 트래커도, 그리고 결정적으로 커밋 히스토리를 대상으로 하는 단순한 벡터 검색조차도 마찬가지입니다. 벡터 유사성(Vector similarity)은 _단어_가 비슷한 버그를 찾아냅니다. _원인_이 비슷한 버그에 대한 개념은 없습니다.
바로 이것이 RootCause가 해결하려는 정확한 문제입니다. 코드베이스를 위한 메모리 계층으로, '무엇이' 발생했는지(the what)뿐만 아니라 그 이면에 있는 '왜' 그랬는지를 기억하여, 같은 그림자를 두 번 쫓아다니며 막히지 않게 해줍니다.
RootCause가 실제로 하는 일
RootCause를 어떤 GitHub 리포지토리에도 적용하면, Cognee가 구동하는 하이브리드 그래프-벡터 메모리 계층(hybrid graph-vector memory layer)으로 커밋 히스토리를 주입합니다. 단순히 '무엇이 변경되었는지'만 저장하는 것이 아니라, 모든 수정 사항에 대해 인과적 사슬(causal chain)을 구축합니다:
Error → Root Cause → Fix → Files Touched → Related Bugs
그러면 키워드 검색으로는 답변할 수 없는 질문들을 할 수 있습니다:
"이 정확한 실패 모드가 다른 이름으로 이전에 발생한 적이 있나요?"
그리고 수정 사항이 실제로 유지되지 않아 버그가 나중에 재발할 때, RootCause의 메모리 계층은 이전 수정 사항을 더 이상 신뢰해서는 안 된다고 표시할 수 있습니다. 그래프는 단순히 커지는 것이 아니라 시간이 지남에 따라 더 정직해집니다.
마지막 부분이 Cognee를 특별히 필요로 하는 이유 전체가 됩니다. 근본 원인은 공유하지만 단어 선택이 완전히 다른 두 버그는 유사성 검색 문제가 아니라 그래프 순회(graph-traversal) 문제입니다. 단순한 벡터 DB는 이미 소리가 비슷한 것들만 포착할 수 있습니다.
개요로 보는 아키텍처
- GitHub 커밋 수집 (GitHub Commit Ingestion) ↓
- 분석 및 메타데이터 추출 (Analysis & Metadata Extraction) (파일, diff, 문맥화된 편집 사항) ↓
- 인과 관계 체인 구축 (Causal Chain Building) (인과 그래프 생성) ↓
- 버그 및 수정 커밋 연결 (Link Bug & Fix Commits) ↓
- 벡터 및 그래프 임베딩 (Vector & Graph Embedding) ↓
- 그래프-벡터 메모리(Cognee)에 저장 (Stored in Graph-Vector Memory (Cognee)) ↓
- 새로운 버그 보고 → 관련 정보 검색 → 근본 원인 식별 → 버그 회상 보고서 생성 (New Bug Report → Retrieve Relevant Info → Identify Root Cause → Bug Recall Report)
팀의 업무 분담 방식
저희 두 명은 해커톤이라는 압축된 일정 속에서 이를 구축했기에, 아키텍처의 자연스러운 경계선을 따라 업무를 나누었습니다.
- 수집 (Ingestion) — 저장소(repo)를 클론하고, 커밋 로그를 탐색하며, diff를 추출하고, 이를 공유 스키마(
Commit,File,Bug,Fix)에 매핑한 뒤, 구조화된 엔티티(entities)를 Cognee로 푸시하는 작업입니다. - 회상 + 인터페이스 (Recall + Interface) — 평범한 영어 질문을 훌륭한
recall()호출로 변환하는 쿼리 계층(query layer)을 설계하고, 이를 실제로 사용할 인터페이스(CLI + 웹 UI)를 구축하며, "재발된 버그(resurfaced bug)" 탐지가 엔드 투 엔드(end-to-end)로 작동하도록 만드는 작업입니다.
저희의 계획은 시드 데이터(seed data)를 바탕으로 병렬로 작업하여 어느 한 쪽이 다른 쪽을 가로막지 않게 하는 것이었으며, 실제로 그렇게 진행되었습니다. 수집 파이프라인이 아직 연결되는 동안에도 회상 계층은 Cognee의 예제 데이터를 대상으로 구축 및 테스트되었고, 이후 실제 파이프라인이 준비되었을 때 두 부분이 통합되었습니다.
구축 과정, 그리고 프로젝트를 거의 침몰시킬 뻔했던 버그들
대부분의 프로젝트 기록에서 생략되지만, 실제로는 가장 중요했던 부분입니다. 세 가지 버그가 있었는데, 각각은 소리 없이 존재했으며, 경고 한 번 없이 조용히 데모 전체를 망가뜨릴 수 있는 완벽한 능력을 갖추고 있었습니다. 전형적인 숙취 증상과도 같았습니다. 문제가 터지기 전까지는 모든 것이 괜찮게 느껴지는 것 말입니다.
버그 #1 — 존재하지 않았던 설정
저희의 수집 코드는 인자 없이 new Cognee()를 사용하여 Cognee를 초기화했습니다. 코드를 작성한 머신에서는 작동했는데, 그 머신에는 이전 테스트 과정에서 남겨진 환경 변수(environment variables)들이 이미 있었기 때문입니다. 하지만 깨끗한 상태에서 체크아웃(clean checkout)을 하면, 인증 오류와 함께 즉시 실패했습니다.
원인을 찾고 나면 해결 방법은 간단했습니다. 하지만 이는 데모에서는 멀쩡해 보이다가, 다른 누군가가 저장소(repo)를 클론(clone)하는 순간 바로 깨져버리는 그런 종류의 문제입니다.
버그 #2 — 모든 것을 조용히 망쳐놓았을 수도 있었던 문제
이것이 결정적인 문제였습니다. 밤새 모든 일을 기억한다고 생각하지만, 실제로는 처음 5분만 기억하고 있는 상황과 같았습니다.
Cognee의 remember() 파이프라인(pipeline)은 데이터셋을 한 번 처리한 후 "완료(completed)" 상태로 표시하며, 이후 동일한 데이터셋에 대한 모든 호출에서 재처리를 조용히 건너뜁니다. 우리의 인제스션(ingestion) 코드는 루프 내에서 커밋(commit)당 한 번씩 remember()를 호출하고 있었습니다.
그 의미는 다음과 같습니다. 커밋이 하나 이상 있는 실제 저장소의 경우, 실제로 지식 그래프(knowledge graph)에 들어간 것은 오직 최초의 커밋뿐이었습니다. 그 이후의 모든 것은 스토리지(storage)에는 기록되었지만, 우리가 필요로 하는 엔티티(entities), 관계(relationships), 또는 인과 관계 체인(causal chain)으로 추출되지 않았습니다. 이 버그는 에러를 발생시키지 않았습니다. 그저 완성된 것처럼 보이지만 프로젝트 이력의 99%가 누락된 그래프를 조용히 건네줄 뿐이었습니다. 답변할 수 있어야 할 질문을 던졌을 때 답변 대신 침묵을 마주하기 전까지는 결코 알 수 없었을 것입니다.
해결 방법: 모든 커밋을 인제스션당 단 한 번의 remember() 호출로 배치(batch) 처리하여, cognify가 단 한 번만 실행되고 완료되도록 합니다.
버그 #3 — 저장소 간의 데이터 유출 (Data Bleeding)
테스트 중에 여러 저장소를 인제스션하고 나니, 한 저장소에 대한 질문이 다른 저장소의 데이터로 오염된 답변을 반환하기 시작했습니다. Cognee의 데이터셋 수준 필터링(dataset-level filtering)은 문서 청크(chunks)를 안정적으로 격리하지만, 데이터셋 간의 그래프 엔티티 해상도(entity resolution)는 실제 환경에서 일관되지 않았습니다. 따라서 동일한 로컬 스토어에 있는 두 데이터셋의 결과가 필터가 적용된 상태에서도 섞일 수 있었습니다.
해결 방법: 모든 인제스션에 고유한 타임스탬프가 찍힌 데이터셋 이름을 부여하여, 질문이 어떤 저장소의 그래프 범위(scope)에 속하는지에 대해 모호함이 없도록 합니다.
이러한 버그들은 전혀 이례적인 것이 아니었습니다. 첫 번째 테스트를 통과했고, 단독으로는 문제가 없어 보였으며, 오직 우리가 경계면(seams)을 압박했을 때만 그 모습을 드러냈습니다. 이것이 바로 무언가가 "작동하는 것처럼 보일" 때를 훨씬 지나서도 테스트를 계속해야 하는 정확한 이유입니다. 결과적으로 AI에게 건망증을 부여하는 가장 빠른 방법은, 스스로 건망증을 구축하면서도 그것을 알아차리지 못하는 것이었습니다.
Cognee가 실제로 AI에게 기억을 부여하는 방법
그 메커니즘은 들리는 것보다 더 단순하며, 그것이 핵심입니다.
Cognee의 remember()는 가공되지 않은 입력값(우리의 경우 커밋에 대한 일반 텍스트 설명)을 받아 청킹 (chunking), 엔티티 추출 (entity extraction), 관계 추출 (relationship extraction), 그리고 임베딩 (embedding) 과정을 하나의 파이프라인으로 실행합니다. 그 결과물은 단순히 벡터 인덱스(vector index)에 저장된 텍스트 덩어리가 아닙니다. 그것은 **그래프 (graph)**입니다. 엔티티(파일, 함수, 버그, 수정 사항)를 위한 노드(nodes), 엔티티 간의 관계(이 수정 사항이 이 파일에 영향을 주었음, 이 버그가 저 버그와 동일한 증상을 공유함)를 위한 엣지(edges), 그리고 유용한 곳에서 의미론적 검색 (semantic search)이 여전히 작동할 수 있도록 그 위에 레이어링된 벡터 임베딩 (vector embeddings)으로 구성됩니다.
그다음 recall()은 단순히 최근접 이웃 (nearest-neighbor) 탐색만 수행하지 않습니다. 이 기능은 해당 그래프를 순회하며, 순수한 유사도 검색 (similarity search)으로는 절대 드러나지 않을 연결된 컨텍스트를 끌어오고, 그 결과를 LLM에 전달하여 소스 커밋(source commits)에 대한 실제 인용을 포함한 실제 답변으로 합성합니다.
이 프로젝트를 단순한 흥미 위주가 아닌 실질적인 가능성을 가진 프로젝트로 만든 부분은 바로 **improve()**입니다. 만약 나중에 특정 수정 사항이 효과가 없었던 것으로 밝혀지면, 메모리 레이어에 이를 알릴 수 있으며, 해당 주제에 대한 향후 회상 (recall) 시 이를 반영합니다. 교정될 수 있는 기억이야말로 "발생했던 일들의 데이터베이스"를 실제 이해에 더 가까운 무언가, 즉 무엇을 시도했는지뿐만 아니라 무엇이 실제로 효과가 있었는지까지 아는 시스템으로 바꾸어 놓습니다.
이것이 바로 해커톤(hackathon) 프레이밍의 핵심 아이디어입니다. "라스베이거스에서 깨어났지만 어젯밤의 기억이 전혀 없는 AI"는 자신의 이력에 대한 인과적 이해 (causal understanding) 없이, 그저 서로 연결되지 않은 사실들의 더미일 뿐인 AI를 의미합니다. RootCause는 코드베이스의 전체 디버깅 이력에 대해 이러한 문제를 해결하는 것이 어떤 모습인지 보여주는 작고 구체적인 데모입니다.
왜 "숙취 없음"이 적절한 비유인가
제목에 등장하는 "숙취"는 단순한 농담이 아닙니다. 이는 대부분의 디버깅 세션이 끝난 후에 발생하는 상황을 정확하게 묘사한 것입니다. 버그를 찾아냅니다. 그리고 수정합니다. 하지만 근본 원인 (root cause)을 찾았는지 100% 확신할 수 없기 때문에 찝찝하고 안개 낀 듯한 느낌이 남습니다. 어쩌면 여러분은 그저 증상 (symptom)만을 패치했을지도 모릅니다. 어쩌면 진짜 문제는 여전히 더 깊은 곳 어딘가에 숨어 있을지도 모릅니다. 그것이 바로 숙취입니다. 즉, 즉각적인 고통이 사라진 후에도 남아 있는 불확실성입니다.
대부분의 디버깅 도구는 그림의 일부분, 즉 에러 (error)만을 보여줄 뿐 그 주변의 생태계 (ecosystem)를 보여주지 않기 때문에 여러분에게 이러한 기분을 남깁니다. 그래서 여러분은 보이는 것만 수정하고 최선의 결과가 나오기를 바랄 뿐입니다. 이것이 바로 동일한 버그가 자주 다시 발생하는 이유입니다. 팀들이 운영 환경 (production) 이슈를 두고 마치 두더지 잡기 게임을 하듯, 약간씩 다른 위치에서 같은 종류의 문제를 반복해서 수정하게 되는 이유이기도 합니다.
RootCause는 그러한 숙취를 없애기 위해 설계되었습니다. 코드베이스에 체계적이고, 검색 가능하며, 문맥 (context)이 풍부한 제대로 된 기억력을 부여함으로써, 단순히 보이는 것을 패치하는 대신 실제로 무슨 일이 일어났는지 "이해"할 수 있게 해줍니다. 전체 문맥을 파악한 상태에서 버그를 수정하면, 확신을 가지고 수정할 수 있습니다. 무엇을 변경했는지, 왜 고장이 났었는지, 그리고 무엇이 추가로 영향을 받을 수 있는지를 알게 됩니다.
안개 낀 듯한 다음 날 아침의 느낌은 없습니다. 오직 명확함만이 존재할 뿐입니다.
FAQ
Q: 왜 단순히 이를 위해 벡터 데이터베이스 (vector database)를 사용하지 않나요?
벡터 유사성 (vector similarity)은 오직 표현 방식이 유사한 버그만을 포착하기 때문입니다. 근본 원인은 동일하지만 에러 메시지, 스택 트레이스 (stack traces), 또는 증상이 완전히 다른 두 버그는 임베딩 공간 (embedding space)에서 함께 클러스터링되지 않습니다. 하지만 관계를 명시적으로 모델링한다면 (동일한 파일, 동일한 함수, 동일한 수정 커밋), 그래프 (graph) 상에서는 이들이 연결될 것입니다. 이것이 단순한 벡터 DB가 아닌 하이브리드 그래프-벡터 저장소 (hybrid graph-vector store)가 필요했던 핵심 이유입니다.
Q: 만약 RootCause가 특정 버그가 "재발(resurfaced)"했다고 잘못 판단하면 어떻게 되나요?
그것이 바로 improve() 함수가 존재하는 이유입니다. 메모리는 정적이지 않습니다. 플래그가 지정된 관계가 잘못된 것으로 밝혀지거나, 유지되지 않는다고 표시되었던 수정 사항이 실제로 유지된 것으로 판명될 경우, 그래프를 수정할 수 있습니다. 그러면 향후 회상 (recall) 시 동일한 실수를 무한히 반복하는 대신 업데이트된 내용을 반영하게 됩니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기
