본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 09. 04:50

교차점 너머의 에이전트 검색(Agent Retrieval): CodeGraph에 대한 제1원리적 분석

요약

CodeGraph의 아키텍처를 SQLite 스키마 분석을 통해 제1원리적으로 분석합니다. AST 추출 경계 설정과 벡터 DB 대신 SQLite 및 FTS5를 사용하는 설계의 타당성을 다룹니다.

핵심 포인트

  • CodeGraph는 AST 추출과 LLM 역할을 명확히 분리함
  • 벡터 DB 대신 SQLite와 FTS5를 사용하여 인덱싱 구현
  • 도구 호출 단계는 줄어들지만, 저장소 규모에 따라 비용 절감 효과는 다를 수 있음
  • SQLite 스키마를 통해 도구의 실제 아키텍처 설계 의도 파악 가능

이 시리즈의 이전 포스트인 _Agent Retrieval Is a Cost Curve Problem_에서는 실행 가능한 LLM-심볼-그래프(LLM-symbol-graph)가 여섯 가지 특정 조건을 충족해야 하며, 현재까지 이 여섯 가지를 모두 충족하는 도구는 없다고 주장했습니다. 해당 포스트는 2026-05-25에 게시되었습니다. 그로부터 7일 전, CodeGraph는 정확히 그 여섯 가지 속성을 모두 충족하며 GitHub 트렌딩에 올랐습니다.

그것은 업데이트의 쉬운 버전입니다. 프레임워크가 예측했고, 누군가 그것을 출시했으며, 여기 그 존재 증명이 있다는 식이죠. 동반 포스트(I Tested CodeGraph on Hono. The Tool-Call Savings Reproduce — the Cost Savings Don't.)는 경험적 측면을 다룹니다. 즉, 40회의 검증된 연결 실행, 결정 행렬(decision matrix), 그리고 설치 여부에 대한 판단을 다룹니다. 해당 포스트의 요약은 다음과 같습니다: 도구 호출(tool-call) 절감 효과는 독립적인 저장소에서 재현되었으나(−55%), 벤더 벤치마크에서의 비용(cost) 절감 효과는 재현되지 않았습니다(Hono 규모에서 +7%). 저장소가 충분히 커지기 전까지는, 비용이 줄어드는 것이 아니라 단계(steps)가 줄어드는 것입니다.

이 포스트는 업데이트의 더 어려운 버전입니다.

흥미로운 질문은 CodeGraph가 작동하느냐가 아닙니다. 흥미로운 질문은 왜 그 특정한 아키텍처(architectural) 선택이 옳은가, 그리고 추상화(abstraction)가 필연적으로 누출되는 지점은 어디인가? 하는 점입니다. 이에 답함으로써, 매번 벤치마크를 다시 수행하지 않고도 앞으로 출시될—그리고 많이 출시될 것입니다—CodeGraph급 도구들을 평가할 수 있는 관점을 얻을 수 있습니다.

추상적으로 답변하기보다 구체적으로 답변하기 위해, 저는 CodeGraph가 작성하는 결과물인 .codegraph/codegraph.db 내의 SQLite 데이터베이스를 바탕으로 CodeGraph를 분석했습니다. 아래의 모든 구조적 주장은 Hono를 위해 실제로 구축된 인덱스를 기준으로 확인되었습니다 (CodeGraph v0.9.7: 362개 파일, 4,128개 노드(nodes), 8,225개 엣지(edges), 7.4 MB 데이터베이스). 스키마(schema)를 확인해 보니, 도구의 README에는 결코 명시되지 않았던 아키텍처에 대한 가장 명확한 설명임이 드러났습니다.

요약 (tl;dr) — CodeGraph의 아키텍처가 적절한 이유는 기능 목록만으로는 명확히 드러나지 않는 세 가지 이유 때문이며, 이 세 가지 모두 SQLite 스키마(schema)에서 확인할 수 있습니다. (1) AST 추출 경계 (The AST extraction boundary): tree-sitter는 _구문 (syntax)_이 알려주는 정보(13개 유형에 걸친 4,128개 노드, 7개 유형에 걸친 8,225개 엣지)를 가져오고 나머지는 LLM의 몫으로 남겨둡니다. 이 경계는 문자 그대로입니다. 구문을 참조할 수 없는 참조(references)는 가짜 엣지(fake edges)가 되는 대신 unresolved_refs 테이블로 들어갑니다. (2) 벡터 DB가 아닌 SQLite + FTS5: 인덱스는 일반적인 관계형 테이블(relational tables)과 심볼(symbol) 이름에 대한 전문 검색(full-text) 테이블로 구성됩니다. 임베딩(embedding) 컬럼은 전혀 없습니다. 쿼리는 B-tree 인덱스가 로그 시간(log time) 내에 응답하는 정확한 조회(exact lookups)입니다. 벡터 검색(vector search)은 워크로드에서 요구하지도 않는 더 어려운 문제를 푸는 격입니다. 이는 이전 포스트의 비용 곡선이 인덱스 도구 자체에 재귀적으로 적용된 결과입니다. (3) 구문이 런타임 의미론(runtime semantics)과 일치하지 않는 지점에서 추상화가 누출됩니다 — 매크로(macros), 메타프로그래밍(metaprogramming), 코드 생성(codegen), JIT 바인딩(JIT binding) 등이 이에 해당합니다. CodeGraph는 추측된 소수의 엣지에 heuristic 출처 플래그를 태깅하는데(Hono의 경우 8,225개 중 7개), 이는 정직한 방식입니다. 하지만 tree-sitter가 전혀 볼 수 없는 부분은 엣지도, 플래그도 부여받지 못합니다. 이 경계를 아는 것이 당신이 신뢰할 수 있는 도구와 무지성으로 따라 하는 도구(cargo-cult)를 구분하는 기준입니다.

이것이 도구 리뷰가 아닌 제1원리적 질문인 이유

CodeGraph에 대한 대부분의 보도는 _"일주일 만에 별 19k 돌파, 여기 설치 스크립트가 있습니다"_와 같이 읽힙니다. 그것은 뉴스일 뿐, 분석이 아닙니다. 향후 18개월 동안 출시될 모든 CodeGraph급 도구들에 대해서도 똑같은 보도가 작성될 것입니다. 왜냐하면 그 패턴 — tree-sitter + 로컬 인덱스(local index) + MCP 서버 + 에이전트를 해당 도구로 라우팅하는 지침 스니펫(instruction snippet) — 이 이제 입증되었고 그 구성 요소들도 잘 알려져 있기 때문입니다.

지속적인 질문은 _"CodeGraph가 좋은가?"_가 아닙니다. _"무엇이 이 부류의 도구를 아키텍처적으로 올바르게 만드는가, 그리고 나는 다음 도구를 어떻게 평가할 것인가?"_입니다. 그것이 바로 제1원리적 읽기가 만들어내는 결과물입니다. 동반 포스트의 벤치마크는 하나의 데이터 포인트일 뿐이며, 이 포스트는 동일한 영역 내의 모든 미래 데이터 포인트를 읽기 위한 렌즈입니다.

만약 CodeGraph를 구체적으로 고려하고 있다면, 동반 포스트를 읽으십시오. 만약 LLM 검색 (LLM retrieval)이라는 학문적 영역을 고민하고 있거나, 유사한 도구에 베팅하거나 구축하려는 참이라면, 이 글을 읽으십시오.

요약: 6가지 조건, 30초 만에 살펴보기

이전 포스트에서는 실행 가능한 모든 LLM-심볼-그래프 (LLM-symbol-graph)가 다음을 갖춰야 한다고 주장했습니다:

  1. 컴파일 없는 파싱 (No-compile parsing) — 분 단위가 아닌 초 단위의 콜드 스타트 (cold start)
  2. 언어 이식성 (Language portability) — 스택마다 서버를 두는 것이 아닌, 여러 언어를 지원하는 하나의 바이너리 (binary)
  3. LLM 친화적 API (LLM-shaped API) — 중첩된 LSP 계층 구조가 아닌, 모델이 소화할 수 있는 평면적이고 레코드 중심적인 (recordy) 출력
  4. 충분히 넓은 커버리지 (Broad enough coverage) — 구조로서의 코드와 그 외 모든 것에 대한 텍스트 검색 (text-search) 폴백 (fallback) 결합
  5. 재인덱싱 없는 실시간 업데이트 (Live update without reindex) — 파일 와처 (file-watcher) 기반, 수동 재빌드 불필요
  6. 설정 없는 설치 (Zero-config install) — 단일 바이너리, 에이전트(agent) 자동 설정

CodeGraph는 이 6가지를 모두 충족합니다 (각 항목별 매핑은 이 포스트의 끝부분에 있습니다). 이 매핑이 확립되었다고 가정할 때, 흥미로운 질문은 다음과 같습니다: 이 6가지를 충족하기 위해 CodeGraph가 내린 설계 선택 중, 무엇이 강제된 것이고 무엇이 다른 방향으로 갈 수도 있었던 것인가? 강제된 선택은 훌륭한 엔지니어링입니다. 강제되지 않은 선택 — 즉, CodeGraph가 실시간 대안 대신 특정 방식을 선택한 지점 — 이 바로 아키텍처가 주장을 펼치는 곳이며, 제1원리적 (first-principles) 내용이 담긴 곳입니다.

그 선택들 중 세 가지는 심도 있게 읽어볼 가치가 있습니다. 나머지 세 가지 (파일 와처 업데이트, 단일 바이너리 배포, 인스트럭션-스니펫 라우팅)는 각자의 분야 — OS 알림, 패키지 배포, 프롬프트 엔지니어링 (prompt engineering) — 에서 잘 알려진 내용이며,

CodeGraph는 tree-sitter를 사용하여 소스 코드를 파싱하고, 구문(syntax)의 특정 하위 집합을 그래프로 추출합니다. 그 하위 집합이 무엇인지 README의 설명을 그대로 믿을 필요는 없습니다. nodesedges 테이블에서 직접 열거할 수 있기 때문입니다. Hono의 경우, 4,128개의 노드는 다음과 같이 구성됩니다:

노드 종류 (Node kind)개수노드 종류 (Node kind)개수
import1,033method240
...

그리고 실제로 흥미로운 부분인 8,225개의 엣지(edges)는 다음과 같습니다:

엣지 종류 (Edge kind)개수인코딩 내용
contains2,874구조적 중첩 (file → class → method)
...

이제 여기에 없는 것들을 살펴보십시오. "type" 노드가 없습니다. 제네릭 인스턴스화 (generic-instantiation) 엣지도 없습니다. 데이터 흐름 (data-flow) 엣지도 없습니다. "이 동적 디스패치 (dynamic dispatch)가 저 구체적인 메서드 (concrete method)로 해결된다"는 식의 엣지도 없습니다. CodeGraph는 calls, references, extends, implements — 즉, 구문상에서 국소적으로 명백한 (locally apparent in the syntax) 관계들만 추출하고 멈춥니다. 이를 1차적으로 읽는다면 "tree-sitter가 타입을 해결(resolve)하지 못하기 때문"이라고 할 수 있습니다. 맞는 말이지만 순환 논리입니다. 더 깊이 읽어야 할 핵심은 왜 이러한 분업이 LLM 소비자에게 적합한가입니다.

정보 이론적 관점 (The information-theoretic case)

타입 체커 (type-checker, 또는 전체 LSP)는 LLM이 쉽게 다시 수행할 수 없는 작업을 수행합니다. 즉, obj의 정적 타입 (static type)을 바탕으로 obj.method()를 실제 메서드로 해결하거나, 제네릭을 통해 타입을 전파하거나, 상속 체인을 따라 실제로 호출된 메서드까지 탐색하는 작업입니다. 이를 위해서는 전체 컴파일 컨텍스트 (compilation context) — 모든 전이적 임포트 (transitive import), 모든 타입 정의, 모든 제네릭 인스턴스화 — 가 필요합니다. 그 비용은 높고 (빌드 환경 필요, 느린 콜드 스타트, 빌드가 깨지면 함께 깨짐) 이점은 좁습니다. 즉, 국소적 컨텍스트 (local context)로부터 재구성하기 진정으로 어려운 정밀한 의미론적 해결 (semantic resolution)에만 국한됩니다.

구문 추출기 (syntactic extractor)는 다른 작업을 수행합니다. 이는 소스(source)의 구조를 쿼리 가능하게 만들지만, 오직 국소적으로 명확한(locally apparent) 구조에만 국한됩니다: "hono-base.ts:406에 정의된 dispatch 함수가 여기서 router로부터 임포트된 match를 호출함." 타입(types)도, 제네릭(generics)도, 런타임 바인딩(runtime binding)도 없지만, 컴파일(compilation) 또한 아닙니다.

정보 이론적(information-theoretic) 질문은 다음과 같습니다: 의미론적 추론 (semantic reasoning)은 뛰어나지만 구조적 열거 (structural enumeration)에는 취약한 LLM이 주어졌을 때, 인덱스(index)가 제공하는 것과 LLM이 제공하는 것 사이의 올바른 분할(split)은 무엇인가?

CodeGraph의 해답은 이렇습니다: LLM에게 구조적 골격 (structural skeleton) — 무엇이 무엇을 호출하는지, 무엇이 어디에 정의되어 있는지, 무엇이 무엇을 임포트하는지 — 을 넘겨주는 것입니다. 왜냐하면 수천 개의 파일에 걸쳐 이를 열거하는 작업은 정확히 LLM이 못하는 부분이며, 이를 수동으로 수행하려다가는 수십 개의 도구 호출 (tool calls)을 낭비하게 될 것이기 때문입니다. 반면 의미론적 해결 (semantic resolution) — 이 호출이 동적 디스패치 (dynamic dispatch) 하에서 런타임에 실제로 무엇을 호출하는가? — 은 LLM의 몫으로 남겨둡니다. 관련 코드가 컨텍스트 (context)에 들어오면 LLM은 이를 합리적으로 처리할 수 있으며, 인덱스에 타입 리졸버 (type resolver)를 구축하는 것은 LLM이 대부분 필요로 하지 않는 복구를 위해 빌드 비용 (build cost)을 배가시키는 일이기 때문입니다.

이 경계를 명확하게 확인하는 방법은 contains + calls + references 엣지 (8,225개 중 7,059개)와 엣지가 아닌 것들을 비교하는 것입니다. 동반 벤치마크의 Q1에서 GET /users/:id 요청이 어떻게 핸들러 (handler)에 도달하는지 물었을 때, CodeGraph가 Claude Code에 제공한 것은 그래프 엣지 (graph edges)로서의 호출 체인(call chain) — fetchdispatchmatch — 이었습니다. CodeGraph가 제공하지 않았고, 제공하려고 시도조차 하지 않은 것은 Hono의 SmartRouter가 런타임에 RegExpRouter를 선택했을 때 어떤 구체적인 match 구현체가 실행되는가 하는 점이었습니다. 그래프는 플레이어들을 찾아냈고, LLM은 세 개의 파일을 읽어 디스패치 (dispatch)를 해결했습니다. 이것이 설계된 대로 작동하는 분할입니다: 인덱스로부터의 열거 (enumeration), 모델로부터의 해결 (resolution).

경계는 문자 그대로의 테이블입니다

이것이 단순한 주장을 넘어 관찰로 바뀌게 하는 세부 사항입니다. tree-sitter가 정적으로 정의(definition)를 해결할 수 없는 참조(reference)를 발견했을 때, CodeGraph는 엣지(edge)를 임의로 만들어내지 않습니다. 대신 별도의 unresolved_refs 테이블에 행을 작성합니다 — 이름, 위치, 해당 노드, 그리고 대상(target) 없음. 이 스키마(schema)는 _"여기서 사용처를 발견했으나, 이것이 무엇에 바인딩되는지 증명할 수 없다"_라는 상태를 위한 일급 객체(first-class) 공간을 갖추고 있습니다.

Hono의 경우, unresolved_refs에는 행이 하나도 없습니다 — 그리고 확인을 위해 인덱싱한 다른 모든 리포지토리(repo)들도 마찬가지였습니다 (섹션 3에 그 결과가 나와 있으며, 이는 제가 예상했던 결과가 아닙니다). 빈 테이블 자체가 흥미로운 부분은 아닙니다; 테이블이 존재한다는 것 자체가 자신의 경계를 명시하는 아키텍처(architecture)입니다. 만약 그래프를 완전해 보이게 만들기 위해 대상을 추측하여 엣지를 조작하는 도구가 있다면, 그것은 LLM에게 확신에 찬 오답을 유도하는 방식으로 거짓말을 하는 셈입니다. 해결되지 않은 참조를 해결되지 않은 상태 그대로 기록하기로 한 CodeGraph의 선택은, 좋은 캐시(cache)가 만료된 항목을 제공하는 대신 만료(stale)로 표시하는 것과 같은 규율입니다: 정직한 방법은 이를 덮어씌우는 것이 아니라 "모름"을 표현하는 것입니다.

CodeGraph를 넘어 이것이 중요한 이유

이 경계 — 인덱스를 위한 구문론적 그래프(syntactic graph), LLM을 위한 의미론적 추론(semantic reasoning) — 는 차세대 LLM 코딩 도구들이 지켜내거나 혹은 위반하게 될 선입니다. 위반 사례는 예측 가능합니다:

  • 인덱스가 의미론(semantics) 쪽으로 너무 치우친 경우: LLM을 위한 완전한 LSP-plus가 되려는 도구입니다. 높은 빌드 비용, 느린 콜드 스타트(cold start), 깨진 빌드에 취약한 구조, 그리고 LLM이 어차피 로컬 컨텍스트(local context)로부터 해당 해결(resolution)을 수행할 수 있기 때문에 미미한 이점만을 가집니다.
  • 인덱스가 원문 텍스트(raw text) 쪽으로 너무 치우친 경우: 그저 "더 나은 인덱싱을 갖춘 grep"에 불과한 도구입니다 — 빠르고 광범위하지만, LLM에게 실제로 필요한 구조적 골격(structural skeleton)을 전달하지 못합니다. 이는 이미 grep+loop가 점유하고 있는 위치이며, 그 단계에서의 인덱싱은 큰 이점을 더해주지 못합니다.

CodeGraph는 그 중간에 위치하며, 그 위치는 현재의 LLM (Large Language Model) 역량에 적합합니다. 모델의 의미론적 해상도 (semantic resolution)가 향상됨에 따라 그 경계선은 한 방향으로 이동할 것이며, 도구 루프 (tool-loop) 반복 비용이 저렴해짐에 따라 반대 방향으로 이동할 것입니다. 하지만 핵심 원칙 — 즉, 선택할 가치가 있는 정보 이론적 경계 (information-theoretic boundary)가 존재하며, 이를 선택하기 위해서는 LLM의 실제 강점과 약점을 모델링해야 한다는 점 — 은 변하지 않는 견해입니다. 새로운 LLM 검색 (retrieval) 도구를 평가하는 올바른 방법은 여기서부터 시작됩니다: 해당 도구가 무엇을 추출하기로 선택하는가, 무엇을 LLM의 몫으로 남겨두는가, 그리고 그 분할이 LLM이 실제로 잘하는 작업에 맞춰 조정(calibrated)되어 있는가?

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0