흩어진 도메인 지식을, 개발 루프 안에서 스스로 걸러지고 자라는 그래프로
안녕하세요, 쏘카에서 모두의주차장 서비스를 개발하고 있는 세바스찬, 올뤼버입니다. 모두의주차장은 주차장 검색부터 할인권·정기권 구매, 결제까지 제공하는 주차 서비스입니다.
저희 팀은 지난해부터 개발 작업의 상당 부분을 AI 에이전트에게 맡기고 있습니다. 그 과정에서 "AI가 우리 도메인을 몰라서" 생기는 사고를 여러 번 겪었고, 결국 도메인 지식을 한곳에 모아 사람과 AI가 함께 쓰는 지식 그래프 parking-brain을 만들게 됐습니다. 이 글은 그 도입기입니다. 문제 정의부터 도입 과정에서 배운 것, 개선 결과, 남은 과제 순서로 정리했습니다. AI를 개발 프로세스에 들이려는 팀이라면 도메인이 달라도 같은 문제를 만나실 거라, 저희가 밟은 시행착오가 참고가 되면 좋겠습니다.
AI가 중복 구현한 이유
"특정 상태를 수정하는 관리자 기능을 만들어 달라"는 작업을 AI에게 맡긴 적이 있습니다. 결과물은 멀쩡했습니다. 코드는 정돈돼 있었고, 테스트도 붙어 있었으며, 리뷰도 통과했습니다.
문제는 동일한 기능을 하는 API가 이미 존재했다는 점입니다. AI는 흩어진 코드와 문서를 훑은 끝에 "없다"고 판단했고, 그 위에 우회 로직을 새로 쌓았습니다. 중복 구현이 머지 직전까지 갔다가, 한 동료가 "그거 기존 API로 되는 것 아닌가요?"라고 짚으면서 멈췄습니다. 반나절을 중복 구현에 썼습니다.
같은 조건이라면 사람도 똑같이 판단했을 상황이었습니다. 그 API는 코드에는 있었지만 이름이 직관적이지 않아 탐색에 걸리지 않았고, 위키에는 없었으며, 아는 사람은 그날 부재중이었습니다. AI가 사실을 지어낸 게 아니라, 주어진 컨텍스트에 근거하지 못했을 뿐입니다. 모델을 탓하기 전에, 모델에게 주어진 지식의 상태부터 봐야 했습니다.
같은 정책, 다른 답
들여다보니 원인은 흩어진 지식이었습니다. 저희만의 사정은 아닐 겁니다. 어느 조직이든 도메인의 진실은 코드, 이슈 트래커, 위키, 메신저에 나뉘어 기록되고, 시간이 지나며 서로 어긋납니다. 같은 정책 하나를 두고도 출처마다 다른 값을 말하는 일이 생깁니다. 그 어긋남의 전형적인 모습이 아래 그림입니다. (수치는 실제 정책 값이 아니라 가상의 예시입니다.)
사람은 낡은 위키를 보고도 한 번 더 의심하며 버텨왔지만, AI에게 작업을 맡기기 시작하자 이 흩어짐이 곧바로 잘못된 코드로 이어졌습니다. 앞의 사고가 정확히 그 사례였습니다.
위키가 답이 아니었던 이유
가장 먼저 떠오른 답은 당연히 이것이었습니다. "도메인 위키를 하나 잘 만들자." 다만 이것이 함정이었습니다.
문서를 하나 더 만드는 것은, 뒤처질 사본을 하나 더 늘리는 일입니다. 아무리 잘 정리해도 코드가 바뀌는 순간부터 낡기 시작하고, 누군가 매번 손으로 맞춰주지 않는 한 한 달 뒤엔 다섯 번째 진실 후보가 됩니다. 위키가 죽는 이유가 정확히 이것입니다. 문제는 정리하는 실력이 아니라, 정리를 사람의 성실함에 맡기는 구조에 있습니다.
그래서 방향을 바꿨습니다. 더 잘 정리하는 방법을 찾는 대신, 정리를 사람 손에서 떼어낼 방법을 찾기로 했습니다.
parking-brain: 사람과 AI가 함께 쓰는 지식 그래프
그렇게 만든 것이 parking-brain입니다. 한 문장으로 정의하면, 도메인 지식을 담는 지식 그래프, 그 그래프를 자동으로 채우는 수집 파이프라인, 그리고 사람과 AI가 같은 방식으로 묻는 질의 도구 — 이 세 가지를 묶어 부르는 이름입니다.
지식 그래프
지식 그래프(knowledge graph)는 지식을 문장이나 표가 아니라 노드와 관계로 저장하는 방식입니다. 개념·규칙·결정·코드 조각 하나하나가 노드가 되고, 그 사이의 연결 — "이 규칙은 이 개념을 정의한다", "이 코드는 이 규칙을 구현한다" — 이 관계가 됩니다.
문서와의 차이는 쓰는 방법에 있습니다. 문서는 사람이 처음부터 읽어 내려가야 하지만, 그래프는 질의합니다. 전자상거래 서비스를 예로 들면 "쿠폰 정책"이라는 개념에 "1인 1회 발급"이라는 규칙이 걸리고, 그 규칙이 발급 검증 코드로 구현되며, "왜 1회로 제한했는지"는 정책 변경 티켓에 연결됩니다. 이렇게 저장해두면 "이 규칙을 바꾸면 어떤 코드가 영향을 받지?", "이 제한은 어떤 결정에서 나왔지?" 같은 질문에 연결을 따라가는 것만으로 답이 나옵니다.
저희가 그래프를 고른 이유도 여기 있습니다. 실제로 개발하며 던지는 질문을 모아 보니 대부분 관계에 대한 것이었습니다. "이 API를 바꾸면 어떤 로직이 영향을 받나", "이 필드는 어떤 규칙에 묶여 있나", "이 티켓의 결정은 어떤 코드로 구현됐나." 행과 열로는 답이 안 나오고, 노드 사이의 경로로만 답이 나오는 질문들입니다.
그래프 안의 실제 모습
저희 도메인의 예를 하나 보겠습니다. 모두의주차장에는 주차장 여러 개를 묶어 하나의 단위로 정산하는 개념이 있습니다(사내에서는 STG, 정산대상그룹이라 부릅니다). 이 개념은 그래프 안에서 이런 모습입니다.
개념 하나가 규칙에 걸리고, 규칙이 실제 코드로 구현되며, 그 규칙이 어느 티켓의 결정에서 나왔는지까지 화살표로 이어집니다. "이 개념을 건드리면 어떤 코드와 결정이 딸려 오는가"를 이 경로를 따라 답합니다.
그리고 이 그래프에는 채팅으로 물을 수 있는 인터페이스도 붙어 있습니다. 쿼리 언어를 몰라도 자연어로 물으면, 의미·관련 규칙·실무 해석을 근거와 함께 답합니다.

전체 구성
- 저장소는 그래프 DB(Neo4j)입니다. 노드와 관계를 그대로 저장하고, 질의는 관계를 따라갑니다.
- 코드는 크론잡이 주기적으로(1시간 간격) 받아 정적 분석합니다. 전체를 AI로 읽는 게 아니라, AST 기반 스크립트로 서비스·엔드포인트·엔티티(클래스·메서드·필드) 구조를 뽑아 노드로 넣습니다. 값싸고, 코드가 바뀌면 다음 주기에 반영됩니다.
- 이슈·위키·메신저도 주기 수집합니다. 티켓의 결정·배경, 정책 문서, 운영 중 굳어진 암묵지가 각각 노드가 되고 관계로 이어집니다.
- 질의는 몇 가지 안정된 도구로 고정하고, MCP(Model Context Protocol — AI 에이전트가 외부 도구를 호출하는 표준 인터페이스)로 노출했습니다. 개발자도, AI 에이전트도, 위의 채팅 인터페이스도 같은 도구를 씁니다.
설계 원칙 두 가지
여기서 자연스러운 의문이 생깁니다. 위키가 낡아서 그래프를 만들었다면, 그래프는 안 낡을까요. 낡습니다. 그래프도 원본에서 파생된 사본인 이상 어긋날 수 있고, 그 가능성을 0으로 만들 수는 없습니다. 대신 설계 원칙 두 개로, 위키와는 질적으로 다르게 낡도록 만들었습니다.
첫째, 사람이 갱신하지 않습니다. 신선도를 사람의 성실함이 아니라 파이프라인에 맡깁니다. 위키는 사람이 갱신하지 않으면 무한정 낡지만, 파이프라인은 주기적으로 다시 수집하므로 어긋남이 누적되는 시간이 동기화 간격 이내로 묶입니다.
둘째, 복제하지 않고 가리킵니다(sourceRef). 각 노드는 "이 사실은 이 코드/티켓/문서에서 왔다"를 함께 들고 있습니다. 그래서 그래프는 새로운 진실 원본이 아니라 캐시이자 인덱스입니다. 코드에서 파생되는 것(엔티티·엔드포인트)은 코드가 원본이고 그래프는 미러이며, 코드가 답하지 못하는 것(의미·결정·정책)만 사람이 정의한 시드와 이슈·메신저가 채웁니다. 캐시는 낡을 수 있지만, 원본을 가리키므로 검증하고 다시 만들 수 있습니다.
그럼에도 남는 위험은 있습니다. 사람이 정의한 시드는 원본이 그래프 자신이라, 여기만큼은 어긋날 수 있습니다. 이 부분은 뒤에서 볼 개발 루프 — 어긋남을 발견하면 그 자리에서 고치는 — 로 수렴시키는 것이지, 완전히 없앤 것이 아닙니다. "복제하지 말고 참조하라"가 지켜지지 않으면 그래프는 정말 다섯 번째 거짓이 됩니다. 저희에게 이 원칙은 세일즈 포인트가 아니라 지켜야 할 제약입니다.
도입하며 배운 세 가지
파이프라인을 만들고 지식을 채우는 과정은 계획대로 흘러가지 않았습니다. 되돌아보면 세 가지로 정리됩니다.
① 넣기보다 버리기
처음에는 많이 넣을수록 좋다고 생각했는데, 실제로는 반대였습니다. 지식 베이스의 신뢰도는 무엇을 넣느냐가 아니라 무엇을 버리느냐가 결정합니다.
위키를 수집해보니 가져온 것의 절반 가까이가 지식이 아니었습니다. 첨부파일만 있는 페이지, 스크린샷 한 장이 본문인 페이지, 방치된 빈 페이지. 이런 것까지 넣으면 그래프는 "노이즈를 자신 있게 답하는" 상태가 됩니다. 그래서 수집 코드보다 버리는 코드를 더 신경 썼고, 본문이 너무 짧거나 첨부만 있거나 오래 방치된 문서를 먼저 걸러내고 나서야 그래프가 신뢰할 만해졌습니다.
메신저(Slack)는 위키처럼 구조로 거를 수 없었습니다. 같은 채널 한 줄 위아래에 "이 정책 이렇게 바꾸기로 했어요"라는 진짜 결정과 "점심 뭐 먹죠"라는 잡담이 똑같이 생긴 한 줄로 섞여 있기 때문입니다. 그래서 내용에 점수를 매겨 걸렀습니다. 규칙 사전으로 싸게 1차 필터를 하고, LLM이 신뢰도 점수를 매기고, 도메인 용어·추측성·스레드 동의 여부로 보정한 뒤, 임계값을 넘으면 자동 적재합니다. 그리고 임계값에 못 미친 메시지는 버리지 않고 담당자에게 승인/거절 버튼과 함께 보냅니다. 자동화가 확신하는 영역만 자동으로 처리하고, 회색지대는 사람을 루프에 남겨 둡니다.

정형 지식은 구조로 거르고, 비정형 지식은 점수와 사람으로 거릅니다. 결국 요점은 같습니다. 애매한 것은 버리지 말고 사람에게 넘깁니다.
② 자동 추출 대신 시드
거르는 것을 자동화한 다음에는 추출도 자동화해봤습니다. 문서를 넣으면 LLM이 알아서 개념과 관계를 뽑아 그래프를 채워주는 방식입니다. 결과는 노이즈투성이였습니다. 그럴듯한데 틀린 관계가 너무 많아, 오히려 새로운 어긋남을 양산할 판이었습니다.
그래서 물러섰습니다. 핵심 어휘와 규칙은 사람이 직접 정의한 시드(seed)로 시작하고, 자동화는 신뢰가 검증된 영역에만 점진적으로 들였습니다.
# 사람이 직접 정의하는 seed (개념을 그대로 옮긴 예시)
- 용어: 정산 단위 = 주차장 여러 개를 묶는 정산의 단위
- 규칙: 차량번호 응답 필드 이름을 하나로 통일한다
- 규칙: 결제의 두 참조 키는 동시에 채워지지 않는다 (둘 중 하나만 값을 갖는 XOR 제약)
멋있는 선택은 아니었습니다. 전부 자동으로 빨아들이는 그림이 더 근사해 보였습니다. 하지만 틀린 자동화보다 느린 수작업이 나았습니다. 신뢰할 수 없는 단일 출처는 흩어진 진실보다 위험합니다. 모두가 그것을 믿어버리기 때문입니다.
③ 코드가 침묵하는 의미
실행되는 코드와 운영 DB는 시스템이 실제로 하는 일이라 가장 단단한 근거입니다. 그런데 코드가 답하지 못하는 영역이 있다는 것을, 사고 하나로 배웠습니다.
상품 분류에 쓰이는, 이름이 거의 같은 두 코드 필드가 있었습니다. 이해를 돕기 위해 가명으로 적으면 이렇습니다.
kindSeq // 상품의 "종류" (예: 일반 할인 상품 / 정기 상품)
typeSeq // 처리 "방식" 등 별도의 분류 축
이름이 비슷하다는 이유로 잘못된 쪽을 골랐고, 특정 상품이 분류에서 누락됐습니다. 곤란한 건 이 사고가 모든 관문을 통과했다는 점입니다.
타입 체크 통과 ✅ → 컴파일이 된다 (≠ 동작한다) 테스트 통과 ✅ → 명세를 만족한다 (≠ 현실을 만족한다) 코드 리뷰 통과 ✅ → 코드가 깔끔하다 (≠ 도메인 의미가 맞다)
코드는 정확히 "맞았습니다". 단지 도메인의 의미가 틀렸을 뿐입니다. 결국 운영 DB의 실제 값을 들여다보고서야 잡았습니다. 왜 그렇게 했는지(의도), 무엇이 결정됐는지(정책), 이 필드가 무슨 의미인지(도메인 용어)는 코드에 없거나 매직 넘버로만 존재합니다. 코드가 진실인 영역에서는 코드를 믿고, 코드가 침묵하는 영역만 그래프가 채웁니다.
이 사고 뒤에 닮은 이름 쌍을 혼동 주의 규칙으로 그래프에 등록했습니다. 실제 시드에는 대략 이런 구조로 들어 있습니다.
# 혼동 주의 규칙 (가명 — 실제 시드의 구조를 옮긴 예시)
규칙: kindSeq vs typeSeq 구분
- 의미: kind = 상품 종류, type = 별도 분류 축. 두 필드를 절대 혼동 금지.
- 강제: 상품 분류에는 반드시 kindSeq 사용. 리뷰에서 typeSeq 분류는 거부.
- 과거 사고: 두 필드 혼동으로 특정 상품 분류 누락 — DB 직접 조회로만 발견.
다음에 누군가(사람이든 AI든) 이 필드를 만지면, 사고가 났던 조건을 먼저 경고받습니다. 같은 실수를 사람의 주의력이 아니라 모델 바깥의 명시적 규칙으로 막는 것입니다.
완성된 파이프라인을 한 장으로 그리면 이렇습니다. 흩어진 출처에서 수집하고, 노이즈를 걸러내고(구조 또는 점수+사람), 그래프에 적재한 뒤, 단일 창구로 질의가 나가는 구조입니다.
개선 결과: 구현 전에 묻는 개발 루프
효과는 이 그래프가 개발하는 루프 안에 물려 있다는 데서 나왔습니다.
구현 전 조회
코드를 짜기 전에 그래프부터 조회합니다. 새 기능을 설계하는 플래닝 단계에서 그래프의 규칙·결정·관계를 조회해, 빠뜨리기 쉬운 전제를 미리 짚고 시작합니다. "이 정산 로직 변경, 묶음 단위 집계가 깨지지 않습니까?", "이 결제 처리에서 두 참조 키가 동시에 채워지면 안 된다는 제약이 유지됩니까?" 같은 질문이 구현 전에 나옵니다. 산출물을 만든 뒤에 검증하는 부담을, 만들기 전 단계로 옮기는 것입니다. 가장 싼 버그는 작성되지 않은 버그입니다.
이 글 첫머리의 관리자 API 재구현 사고도, 이제 구현 전에 그래프에 한 번 질의하는 것으로 끝납니다.
# Before — 그래프 없이
AI: 관련 API 존재 여부 모름 → "없다" 가정 → 우회 구현 → 반나절 소모
# After — 구현 전에 그래프에 먼저 질의
AI: business_rules("상태 수정") / api_impact("PUT /.../status")
→ "관리자 update 엔드포인트 이미 존재" 확인 → 우회 대신 기존 것을 사용
루프에서 자라는 지식
AI와 개발하다 그래프의 답이 낡았거나 틀린 것을 발견하면, 그 자리에서 해당 노드를 고쳐 넣습니다(upsert). 이 절차는 에이전트의 작업 지침으로 붙어 있어서, 작업 중 잘못된 지식을 짚으면 사람이 확인한 뒤 그래프에 반영됩니다. 틀린 지식뿐 아니라 그래프가 아예 모르던 개념(빈틈)도 같은 방식으로 채워집니다. 배포한 것이 테스트에서 틀린 것으로 드러나면 그 교훈이 다시 규칙으로 쌓입니다. 개발하는 흐름에서 벗어나 따로 문서를 갱신할 필요 없이, 루프를 한 바퀴 돌 때마다 자산이 조금씩 단단해집니다. 지식은 완성되는 것이 아니라, 실수를 통해 자랍니다.
운영 문의에 답하는 봇
쓰임은 개발자에서 멈추지 않았습니다. 같은 그래프를 사내 운영 슬랙봇의 두뇌로도 씁니다. 운영 문의가 올라오면 봇이 그래프를 자동 조회해, 관련 규칙·확인해야 할 DB 항목까지 1차로 정리해 답합니다. 아직 커버하는 문의 범위는 좁지만, 운영자가 개발자를 호출하기 전에 봇이 1차 답의 초안을 준비하는 골격은 돌고 있습니다.

답을 단정하지 않고 확인이 필요한 지점을 근거와 함께 제시하는 것이, 도메인 두뇌를 붙인 봇과 그냥 그럴듯하게 답하는 봇의 차이입니다.
남은 과제: 개발자 너머로
운영을 하다 보니 한 가지가 분명해졌습니다. 도메인 질문을 가장 많이 던지는 사람은 개발자가 아니었습니다. "이 발급 조건이 지금 어떻게 돼 있죠?", "이 정책 언제부터 바뀐 거예요?" 같은 질문은 기획·사업·CS에서 훨씬 자주 나오고, 지금까지 그 답은 대개 개발자에게 묻는 것으로 해결됐습니다. 사람이 병목이었습니다.
문제는 이들이 쿼리 언어를 모른다는 데 있지 않습니다. 쿼리 언어를 알 필요가 없어야 합니다. 확장의 본질은 새로운 데이터가 아니라 새로운 인터페이스입니다. 그래서 확장을 세 축으로 나눠 보고 있습니다.
| 축 | 지금 | 확장 방향 |
|---|---|---|
| 채널 (어디서) | 질의 도구, 운영 봇 | 메신저 봇, 사내 포털 임베드 위젯 |
| 청중 (누가) | 개발자, AI 에이전트 | 기획, 사업, CS |
| 능력 (무엇을) | 조회(pull), 선제 답변 | 요약, 그리고 어긋남 감지 알림(push) |
다만 비개발자까지 가려면 답보다 신뢰가 먼저입니다. 개발자는 답이 의심스러우면 코드를 열어 직접 확인하지만, 사업·기획은 그럴 수단이 없습니다. 한 번 틀린 답을 받으면 그 도구는 바로 신뢰를 잃습니다. 그래서 비개발자용 인터페이스의 핵심은 답의 매끄러움보다 근거의 투명성입니다. 답이 어디서 왔고 언제 기준인지를 함께 보여줘야 합니다.
# 비개발자용 답의 목표 형태 (가상의 예시)
Q. "이 상품, 지금 발급 조건이 어떻게 돼 있나요?"
A. 본인인증 완료 + 신규가입 N일 이내일 때 발급됩니다.
├ 근거 내부 티켓(발급 트리거 결정) · 발급 배치 코드
├ 최신성 코드 기준 6시간 전 동기화
└ ⚠️ 관련 위키 문서는 약 3개월 전 작성 — 코드와 차이가 있을 수 있어요
설계 원칙에서 본 sourceRef가 여기서 다시 쓰입니다. 개발자에게는 검증의 출발점이던 근거가, 비개발자에게는 신뢰의 근거가 됩니다. 넓히는 순서는 좁게, 정확하게입니다. 반복 질문 top N부터 답 품질을 보장한 뒤에 채널을 엽니다. 신뢰는 얻기 어렵고 잃기 쉬우므로, 좁고 정확한 시작이 다음 영역을 열 신뢰를 만듭니다.
시작하려면 — 저희가 권하는 0단계
parking-brain에서 사람이 직접 심은 규칙·어휘·결정 노드는 100개가 안 됩니다. 나머지 1만 7천여 개는 정적 분석과 주기 수집이 자동으로 채웠습니다(앞의 챗봇 화면 우상단에 보이는 노드 수가 그것입니다). 그리고 진짜 원본은 grep 가능한 텍스트 시드 파일이고, Neo4j는 그 위에 올린 조회용 인덱스입니다. 그러니 0단계는 인프라 구축이 아니라 텍스트 파일 작성이고, 한 사람의 반나절로 시작할 수 있습니다.
순서는 이렇게 권합니다. ① 최근 AI가 틀렸던 개념부터 시드로 정의하고(저희의 kindSeq/typeSeq 같은 혼동 지점이 1순위), ② 개발 루프에 "구현 전 조회" 한 단계를 넣고(이게 없으면 시드는 아무도 안 읽는 다섯 번째 문서가 됩니다), ③ 어긋남은 발견한 자리에서 고치고, ④ 자동 수집과 필터는 그다음에 붙입니다. ④부터 시작하지 마세요 — 저희가 자동 추출부터 욕심냈다가 물러선 이야기가 앞의 "자동 추출 대신 시드"입니다.
도입할지 판단하는 신호는 간단합니다. "AI가 도메인을 몰라서 반나절을 날린 사고가 최근에 있었는가?" 있었다면, 그 사고가 여러분의 첫 시드입니다. 저희의 첫 시드가 이 글 첫머리의 사고였던 것처럼요.
마치며
정리하면 이렇습니다.
- 문제: 도메인의 진실이 코드·티켓·위키·메신저에 흩어져 사람도 AI도 오판했습니다. 문서를 하나 더 만드는 것은 뒤처질 사본을 늘리는 일일 뿐입니다.
- 해법: 관계를 질의하는 지식 그래프. 갱신은 파이프라인이 맡고, 원본은 복제하지 않고 가리킵니다.
- 과정: 버리는 코드가 신뢰를 만들었고, 틀린 자동화보다 느린 시드가 나았고, 코드가 침묵하는 의미는 지식으로 채웠습니다.
- 결과: 짜기 전에 되묻고, 루프를 돌 때마다 자랍니다. 가장 싼 버그는 작성되지 않은 버그입니다.
- 남은 과제: 같은 도메인 진실을 조직 전체가 같은 근거와 함께 보게 하는 것. 확장의 본질은 데이터가 아니라 인터페이스입니다.
읽어주셔서 감사합니다.

