HandbookBrain: 모든 규칙이 왜 존재하는지 아는 회사 지식 기반 (FalkorDB 기반)
요약
HandbookBrain은 회사 지식의 휘발성을 해결하기 위해 버전 관리되는 지식 그래프를 구축한 시스템입니다. 문서뿐만 아니라 회의 기록, 채팅 스레드, 머지 요청 등 모든 결정 과정을 연결하여 '왜'라는 질문에 답합니다. 이를 통해 변경 사항으로 인해 깨지는 부분을 감지하고 의사결정과 문서화 사이의 격차를 해소합니다.
핵심 포인트
- 단순 검색을 넘어선 '경로(path)' 추적 가능
- 버전 관리되는 지식 그래프가 핵심 구조
- 변경 시 영향을 받는 모든 답변 자동 감지 및 알림 기능 제공
마야를 만나보세요. 그녀는 마케팅 오퍼레이션스(Marketing Ops) 부서에 온 지 3주가 되었고, 매니저에게 질문을 받습니다. '왜 7월 말에 트라이얼 MQL이 감소했나요?'
그래서 그녀는 파헤치기 시작합니다. 채팅 스레드 40개. 58분짜리 회의 녹화본. 두 번 수정된 핸드북 페이지. 여전히 25점을 언급하는 공유 드라이브의 오래된 PDF 파일. CRM의 리드 점수 필드. 대시보드. 머지 요청(merge request). 일곱 가지 도구를 거쳐도, 그녀는 누가 무엇을 결정했는지, 혹은 왜 그렇게 했는지 여전히 확신하지 못합니다.
이것이 제가 HandbookBrain을 만든 이유입니다. Graph Hacks 해커톤(트랙 3: 회사 지식 기반)을 위해 만들었습니다.
코드: github.com/danhe6/handbookbrain
문제점: 결정이 문서보다 빠르게 움직인다
비즈니스 규칙은 끊임없이 바뀝니다. 리드 점수 임계값(lead-scoring threshold)이 바뀌고, 딜 데스크 검토(Deal Desk check)가 추가되고, 파트너 갱신 규칙이 뒤집힙니다. 결정은 회의나 Slack 스레드에서 이루어집니다. 문서는 몇 주 후에 업데이트되거나 아예 안 될 때도 있습니다. 그리고 이전 규칙에 의존하던 모든 것들—사람들에게 제공된 답변, 다른 문서, CRM 필드, 대시보드—이 조용히 잘못되기 시작합니다.
검색으로는 이것을 해결할 수 없습니다. 검색은 문서를 찾을 뿐입니다. 마야의 질문은 '경로(path)'입니다: 회의 → 결정 → 누가 결정했는지 → 그것을 구현한 변경 사항 → 바뀐 섹션 버전 → 그것에 의존했던 모든 것.
이것은 그래프 문제이므로, 저는 이를 그래프 형태로 구축했습니다.
HandbookBrain이 하는 일
HandbookBrain은 FalkorDB에 하나의 **버전 관리되는 지식 그래프(versioned knowledge graph)**를 유지합니다. 여기에는 회사의 문서, 결정이 이루어진 회의 및 채팅 기록, 그것들을 구현한 머지 요청, 그리고 그 문서들이 접촉하는 시스템, CRM 필드, 메트릭 및 대시보드가 포함됩니다. 이 위에 에이전트가 작동합니다:
- 'X가 어떻게 작동하고, 왜 그런지?'에 대한 답변 제공 시, 해당 섹션이 사용된 정확한 _버전_을 인용하고, 그 버전으로 거슬러 올라가 회의 내용, 결정권자, 그리고 이를 기록한 머지 요청(merge request)까지 추적합니다.
- 변경 사항으로 인해 깨지는 부분을 감지합니다. 섹션이 변경되면, 이전 버전을 인용한 모든 저장된 답변을 찾아내고, 차이점(diff)을 통해 해당 답변이 현재 잘못되었는지 판단한 후, 페이지 소유자에게 알림을 보내 재답변하게 합니다.
- 의사 결정과 문서화 사이의 격차를 해소합니다. 3주가 지나도 어떤 문서에도 반영되지 않은 결정 사항은 초안 업데이트로 작성되어 페이지 소유자에게 라우팅되고 승인을 기다립니다.
데모는 GitLab의 공개 마켓 핸드북(Marketing Ops, Deal Desk, Sales Systems, Channel Ops, Marketing Analytics)을 사용하며, 2026년 4월부터 10월까지 날짜 순서로 74개의 실제 병합된 MR을 재현합니다. 이 MR들을 둘러싼 회의 및 Slack 스레드는 합성 데이터입니다. 저는 이를 LLM으로 생성했으며, 실제 변경 사항에 기반을 두었고, 모든 합성 파일에는 그 내용이 명시되어 있습니다.
데이터 모델
전체 디자인은 세 가지 아이디어에 의해 구현됩니다.
1. 문서가 아닌 섹션 단위로 버전 관리. 페이지는 여러 섹션으로 나뉩니다. 변경 사항이 발생할 때마다 이전 섹션을 SUPERSEDES(대체)하는 새로운 Section 노드가 생성되며, 여기에 valid_from(유효 시작일)과 valid_to(유효 종료일)가 부여됩니다. 아무것도 덮어쓰지 않기 때문에, 어떤 날짜를 기준으로 하더라도 그래프를 읽을 수 있으며, 삭제된 규칙도 여전히 찾아낼 수 있습니다.
2. 답변은 노드(node)입니다. Answer는 자신이 사용한 정확한 섹션 버전을 CITES(인용)합니다. 이 하나의 연결(edge)이 나중에 발생한 변경 사항이 어떤 답변을 깨뜨리는지 찾을 수 있게 해줍니다.
3. 결정 사항은 일급 객체(first-class)입니다. 각 결정 사항은 회의, 채팅 또는 티켓에서 발화자 및 원문 인용구와 함께 추출되며, 해당 섹션에 연결되고, 차이점(diff)을 읽어 이를 구현한 커밋(commit)과 연계됩니다.
Conversation <-DECIDED_IN- Decision -DECIDED_BY-> Person
Decision -IMPLEMENTED_BY-> Commit <-MERGED_AS- MergeRequest
Commit -CHANGED-> Section -SUPERSEDES-> older Section
...
"이것은 왜 이런가요?"는 섹션에서 회의로 돌아가는 하나의 경로입니다:
MATCH (s:Section {sid:$sid})<-[:CHANGED]-(c:Commit)<-[:IMPLEMENTED_BY]-(d:Decision)-[:DECIDED_IN]->(cv:Conversation)
OPTIONAL MATCH (d)-[:DECIDED_BY]->(by:Person)
RETURN d.statement, d.decided_at, cv.title AS meeting, by.name AS decided_by, c.ts AS implemented_at
"이 변경사항은 어떤 답변들을 깨뜨렸나요?"는 커밋에서 시작하여 각 답변이 인용했던 버전으로 되돌아가고, diff 기반의 판단을 위해 이전 텍스트와 새로운 텍스트를 반환합니다:
MATCH (c:Commit {sha:$sha})-[ch:CHANGED]->(s:Section)
OPTIONAL MATCH (s)-[:SUPERSEDES]->(prev:Section)
WITH ch, s, CASE WHEN ch.kind = 'removed' THEN s ELSE prev END AS cited
...
"무엇이 변경된 것에 의존하고 있나요?"는 변경사항에서 언급하는 필드와 메트릭으로 이동한 다음, 현재의 모든 다른 문서와 그 소유자들로 이동합니다:
MATCH (:Commit {sha:$sha})-[:CHANGED]->(s:Section)-[:MENTIONS]->(e)
MATCH (e)<-[:MENTIONS]-(o:Section)<-[:HAS_SECTION]-(p:Page)
WHERE o.valid_to IS NULL AND o.path <> s.path
...
검색은 하이브리드 방식으로, FalkorDB 내에서 모든 것이 이루어집니다. 섹션 임베딩에 대한 **벡터 인덱스(vector index)**와 헤딩 및 텍스트에 대한 **전체 텍스트 인덱스(full-text index)**가 상호 순위 결합(reciprocal-rank fusion)을 통해 융합되고 현재 버전으로 필터링됩니다. 벡터, 전체 텍스트, 그래프 탐색 기능을 하나의 데이터베이스에서 처리할 수 있게 되면서 동기화해야 할 두 번째 저장소가 필요 없어졌습니다.
에이전트 자체는 10개의 매개변수화된 읽기 전용 Cypher 도구(tool)를 사용하는 호출 루프입니다. 모든 주장은 실제로 도구가 반환한 인용을 반드시 포함해야 하며, 이는 코드에서 확인됩니다. 다른 모델로 실행되는 검토자는 질문과 증거, 초안만을 보고받으며, 초안을 다시 보낼 수 있습니다.
Maya의 질문에 대한 답변
재생(replay) 과정에서 트라이얼 임계값(trial-threshold) 이야기가 전개되는 방식은 다음과 같습니다:
- 5월 21일: real MR !19600이 Marketo 페이지에 '트라이얼 임계값 점수제(Trial Threshold Scoring)'를 추가했습니다. HandbookBrain은 이를 13일 전의 (합성된) Marketing Ops 회의와 연결합니다.
- 6월 1일: 한 신규 분석가가 "트라이얼 사용자가 몇 점일 때 MQL이 되나요?"라고 질문했습니다. 답변은 해당 섹션 버전을 인용하며 25점입니다.
- 7월 17일: (합성된) Marketing Ops 주간 회의에서 이 수치를 상향 조정하기로 결정합니다.
- 7월 23일: real MR !20534가
25를31로 변경했습니다. 6월 1일 답변은 diff(변경 사항 비교)를 통해 **오래된 정보(stale)**로 판단되었고, CODEOWNERS에 명시된 담당자들에게 알림이 전송되었으며, 회의 내용, 담당자, MR이 첨부되어 답변이 31로 업데이트되었습니다.
Maya는 하나의 질문을 던져 전체 경로를 파악했습니다.
독립적인 출처와 그래프 확인하기
검증할 수 없는 답변 엔진은 위험 요소이기 때문에, 저는 에이전트가 절대 읽지 않는 출처들과 사이트가 보여주는 내용을 대조하여 감사(audit)하는 작업을 작성했습니다:
| 항목 | 진실의 출처 (Source of truth) | 결과 |
|---|---|---|
| 변경 날짜, MR 번호, 버전 체인 | git log | 2562 / 2562 |
| ... |
이 감사 작업은 제 역할을 다했습니다. 제가 처음 사용했던 결정-MR 판단 기준은 너무 관대하여 세 가지 결정을 나중에 관련 없는 변경 사항과 연결지었습니다. 이제는 실제로 diff에서 변경된 내용만 계산합니다.
에이전트 평가, 솔직하게
이 부분이 제가 가장 많이 배운 부분입니다.
제 첫 번째 헤드라인은 일반 RAG(검색 증강 생성) 기준선 대비 '왜(why)' 질문에 대한 "89% 대 0%"였습니다. 매우 좋아 보였지만 공정하지 않았습니다. 사실 하나하나를 확인했을 때, 단순 검색 방식도 제가 사용한 에이전트만큼 회의 날짜와 결정자를 찾아냈습니다. 점수가 0%로 나온 것은 병합 요청(merge request) 자체가 데이터에 아예 없었기 때문이었습니다.
그래서 저는 Anthropic의 AI 에이전트를 위한 평가 이해하기(Demystifying evals for AI agents)를 따라 평가 시스템을 재구축했습니다:
- 가능한 한 코드 기반 채점기(code graders)를 사용하고, 개방형 답변에 대해서만 모델 심사관(model judge)을 사용합니다.
- 답변해야 하는 질문과 거절해야 하는 질문을 구분합니다.
- 질문당 3번의 트라이얼을 수행하며 LLM 캐시(LLM cache)는 비활성화합니다.
저는 또한 전체 병합 로그(merge log)를 텍스트로 가져오는 더 강력한 기준선인 RAG + git log도 추가했습니다.
29개 질문 × 3회 시도:
| HandbookBrain | RAG + git log | Plain RAG | |
|---|---|---|---|
| 모든 질문 | 87% | 66% | 59% |
| ... | |||
| 무엇이 이를 의미하는가: |
- 회의를 찾는 것은 검색(retrieval) 문제입니다. 그리고 검색이 이 문제를 해결합니다.
- 그래프가 추가하는 것은 결정에서 그것을 구현한 변경 사항으로의 연결과 삭제된 규칙을 포함한 버전 기록입니다.
- 전체 병합 로그를 컨텍스트에 담아도, 모델은 자체적으로 회의와 올바른 MR(Merge Request)을 일치시키는 경우가 드물었습니다.
- 단순 검색(plain lookups)에서는 그래프가 아무것도 추가하지 못하며, 에이전트가 더 느리고 비용이 많이 듭니다.
주의사항은 게시글에도 포함되어야 합니다. 질문들은 제가 만든 것이며, 하나의 데이터셋에서 가져온 것이고, 저는 이 동일한 질문들에서의 실패를 읽은 후에 에이전트를 수정했습니다. 새로운 질문들에 대해서는 낮은 수치를 예상하십시오.
평가 과정에서 실제 버그들이 발견되었습니다:
-
_Trial Threshold Scoring_이라는 제목의 섹션이 일반적인
-
답변을 노드(node)로 만드세요. 답변이 정확한 버전을 인용하는 경우, '무엇 때문에 이것이 깨졌는가?'는 추측이 아니라 탐색 과정이어야 합니다.
-
섹션 레벨에서 버전 관리하세요. 문서 전체의 버전은 어떤 답변에 변경 사항이 영향을 미치는지 알려주기에는 너무 거칩니다.
-
사실(fact)별로, 공정한 기준선과 비교하여 측정하세요. 집계된 점수는 그래프가 도움이 되는 부분과 그렇지 않은 부분을 숨겼습니다. 솔직한 표가 아첨하는 표보다 더 설득력이 있습니다.
-
독립적인 무언가를 기준으로 LLM이 구축한 엣지(edge)를 감사하세요. 링크를 추출하는 모델이 그것을 평가해서는 안 됩니다.
다음 계획 (What's next)
- 내보내기(exports) 대신 Slack, Zoom, Drive와 같은 직접 연결 커넥터(direct connectors), 그리고 녹음 파일에 대한 음성 인식(speech-to-text) 기능 도입.
- CRM 및 BI 메타데이터(Salesforce 필드, Power BI / Tableau 대시보드)를 일급 노드(first-class nodes)로 활용.
- 단순히 서명 승인(sign-off)을 기록하는 것이 아니라, 승인된 업데이트에 대한 병합 요청(merge request)을 열기.
만약 팀의 규칙이 다섯 개의 도구에 분산되어 있고 아무도 그 규칙들이 왜 존재하는지 기억하지 못한다면, 여러분은 어떻게 처리하는지 듣고 싶습니다.
FalkorDB, Python, FastAPI 및 LiteLLM으로 제작되었습니다. 코드 대부분은 제가 지휘하고 검토한 AI 코딩 어시스턴트(Claude)를 사용하여 작성되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기