Aider의 레포 맵이 정적 분석 진영, 즉 코드를 파싱하고 그래프 중심성으로 순위를 매기며 그 과정에 모델을 넣지 않는 쪽이라면, Continue는 다른 진영이다. 레포를 청크로 나누고, 청크를 임베딩한 다음, 유사도로 검색한다. 그러면 의사 결정자가 비공개 모노레포를 연결하기 전에 실제로 묻는 질문이 생긴다. 내 독점 코드가 임베딩 API로 전송되는가? 나는 Continue를 **v2.1.0, 커밋 b238c1f**에서 읽었고, 솔직한 답은 기본값으로는 아니다. 아래의 모든 주장은 코드의 해당 줄을 가리킨다.
짧게 말하면
기본적으로 VS Code에서 Continue는 코드베이스를 로컬에서 임베딩한다. 번들된 all-MiniLM-L6-v2가 transformers.js를 통해 프로세스 안에서 실행되고, 벡터는 디스크의 로컬 LanceDB에 저장된다. 코드에 관한 어떤 것도 네트워크를 거치지 않는다. 원격 임베딩 제공자(OpenAI, Voyage 등)를 설정하는 순간 이 상태가 달라진다. 그때는 청크 텍스트와 검색 쿼리가 해당 제공자에게 POST된다. 프라이버시 경계는 “Continue”가 아니라 어떤 임베딩 제공자를 선택하느냐다.
내 코드가 컴퓨터를 떠날까?
이것이 가장 중요한 질문이므로 다른 무엇보다 먼저 코드에서 답하겠다.
임베딩 제공자를 설정하지 않으면 Continue의 설정 로더는 내장 제공자로 폴백한다. VS Code에서는 명시적이다. 임베딩 설정이 비어 있으면 new TransformersJsEmbeddingsProvider()를 반환한다(load.ts:424. 타입 문서도 index.d.ts:1798에서 그렇게 설명한다). 그 제공자는 all-MiniLM-L6-v2이며(TransformersJsEmbeddingsProvider.ts:44), 오프라인 상태를 유지하기 위해 특별히 애쓴다.
env.allowLocalModels = true;
env.allowRemoteModels = false; // vendored default is TRUE (huggingface.co)
env.localModelPath = .../models;
이것이 TransformersJsEmbeddingsProvider.ts:20이다. 함께 들어 있는 transformers.js 라이브러리는 huggingface.co를 가리키는 allowRemoteModels의 기본값이 true지만(env.js:98), Continue는 일부러 이를 끈다. ONNX 가중치는 확장 자체에 패키징되므로(prepackage.js:470), 이것은 “모델을 한 번 다운로드한다”는 뜻조차 아니다. 가중치는 VSIX에 함께 들어가고 추론은 로컬 ONNX 호출이다(TransformersJsEmbeddingsProvider.ts:58).
이제 스위치를 바꿔보자. 인덱서는 embeddingsProvider.embed(chunks.map((c) => c.content))를 호출해 임베딩한다. 이때 넘기는 것은 원본 청크 텍스트다(LanceDbIndex.ts:203). 원격 제공자의 .embed()는 HTTP POST다. OpenAI의 _embed는 { input: chunks }를 <apiBase>/embeddings로 보내고(OpenAI.ts:717), Voyage는 apiBase: "https://api.voyageai.com/v1/"로 OpenAI를 확장한다(Voyage.ts:19). 검색할 때도 쿼리는 같은 방식으로 임베딩된다(LanceDbIndex.ts:451). 따라서 원격 제공자는 코드뿐 아니라 질문도 보게 된다.1
그러므로 정확한 멘탈 모델은 이렇다. 기본값은 로컬이며, 무엇이든 외부로 보낼지는 제공자마다 결정된다. 임베딩 설정이 없으면 아무것도 떠나지 않는다. 원격 임베딩 제공자를 쓰면 청크와 쿼리가 그 엔드포인트로 나간다.

Continue의 기본 VS Code 경로는 청크를 로컬에서 임베딩하고 저장한다. 프라이버시 경계는 원격 임베딩 제공자를 설정할 때만 이동한다.
파이프라인: 한 번 순회하고 여러 인덱스 구축
Continue는 벡터 인덱스 하나만 유지하지 않는다. 각 디렉터리를 한 번 순회하면서(CodebaseIndexer.ts:380) 최대 세 개의 콘텐츠 인덱스를 구축한 다음, not parallelizing to avoid race conditions in sqlite라는 직설적인 주석과 함께 순차적으로 실행한다(CodebaseIndexer.ts:198). 실제로 어떤 인덱스를 구축할지는 활성화한 컨텍스트 제공자에 따라 결정된다(CodebaseIndexer.ts:169).
| 인덱스 | 담는 내용 | 위치 |
|---|---|---|
| 청크 → 임베딩 | tree-sitter 코드 청크를 벡터화해 LanceDB에 저장 | LanceDbIndex.ts:265 |
| 전문 검색 | SQLite FTS5, 트라이그램 토크나이저, BM25 순위 산정 | FullTextSearchCodebaseIndex.ts:31 |
| 코드 스니펫 | tree-sitter 시그니처(심볼 인덱스) | CodeSnippetsIndex.ts:186 |
청커는 언어를 인식한다. 지원되는 코드 파일이라면 tree-sitter AST를 순회해 “스마트하게 축약한” 청크를 내보낸다. 함수와 클래스가 예산에 들어오면 전체를 담고, 들어오지 않으면 시그니처로 축약하며, 그마저 아니면 단순한 줄 누적 방식으로 폴백한다(chunk.ts:37, code.ts:246). 청크 크기는 임베딩 모델의 maxEmbeddingChunkSize에 고정된다(CodebaseIndexer.ts:184). 흥미로운 점은 벡터 인덱스와 전문 검색 인덱스가 같은 tree-sitter 청크를 사용한다는 것이다. 하나는 임베딩하고, 다른 하나는 키워드 검색용으로 인덱싱한다.
재인덱싱이 저렴한 이유: 콘텐츠 해싱과 브랜치 재사용
Continue는 콘텐츠 주소 지정 방식을 쓴다. 각 파일의 캐시 키는 내용의 SHA-256이다(refreshIndex.ts:371). 새로고침할 때 새 파일처럼 보이는 모든 파일은 (cacheKey, artifactId)를 키로 하는 전역 캐시와 대조된다. 정확히 같은 콘텐츠가 어떤 태그에서든 이미 임베딩됐다면 재사용하고 다시 계산하지 않는 addTag가 되며, 그렇지 않으면 compute가 된다(refreshIndex.ts:412).
알아둘 만한 부분은 이것이다. 인덱스 태그는 { directory, branch, artifactId }지만(index.d.ts:802), 전역 캐시 조회는 (cacheKey, artifactId)를 키로 삼고 브랜치는 키로 삼지 않는다(refreshIndex.ts:359). 따라서 git 브랜치를 바꿔도 변경되지 않은 파일을 다시 임베딩하지 않는다. 동일한 콘텐츠는 한 번만 임베딩되고 태그를 통해 여러 브랜치에서 공유된다. 임베딩 비용은 변경된 콘텐츠에만 지불한다. 큰 첫 인덱스는 느리고 이후의 모든 새로고침은 빠른 이유가 바로 이것이다.2
검색: 벡터지만 벡터만은 아니다
쿼리는 벡터 저장소만 조회하지 않는다. 두 검색 파이프라인 모두 네 가지 소스로 펼쳐져 결과를 병합한다. LanceDB의 임베딩 유사도, FTS5 인덱스의 전문 검색, 최근 편집한 파일, 레포 맵 파일 요청이다(RerankerRetrievalPipeline.ts:22). 결과는 파일과 줄 범위를 기준으로 중복 제거된다(util.ts:4). 리랭커를 설정했다면 후보의 순위를 다시 매긴 뒤 상위 N개만 남기고(RerankerRetrievalPipeline.ts:115), 설정하지 않았다면 임베딩 ½, 전문 검색 ¼, 최근 편집 파일 ¼이라는 고정 가중치로 풀을 섞는다(NoRerankerRetrievalPipeline.ts:17). 목표 크기는 작다. DEFAULT_N_FINAL = 25이고, 컨텍스트 윈도가 작으면 더 줄어든다(retrieval.ts:9).
여기에는 두 진영을 연결하는 세부 사항이 있다. 네 번째 소스인 “레포 맵 파일 요청”은 생성된 레포 맵에서 관련 파일 5~10개를 고르도록 LLM에 요청한다(repoMapRequest.ts:34). Continue에도 레포 맵이 있다. 하지만 Aider의 레포 맵 자체가 메커니즘, 즉 결정론적 PageRank인 반면, Continue의 레포 맵은 네 가지 신호 중 하나이며 그래프 알고리즘이 아니라 모델이 파일을 고른다.
어디에 놓일까?
다시 축으로 돌아가자. 에이전트는 모델이 어떤 코드를 보게 할지 어떻게 결정하는가?
| Aider(정적 분석 우선) | Continue(임베딩 우선) | |
|---|---|---|
| 관련성 신호 | 그래프 중심성 + 사용자의 언급 | 벡터 유사도 + FTS + 리랭크 |
| 결정론 | 결정론적이며 손으로 재계산 가능 | 임베딩 모델에 따라 달라짐 |
| 인프라 | 없음 — 파싱뿐 | 구축하고 새로고쳐야 하는 인덱스(LanceDB + SQLite) |
| “의미로 찾기” | 불가능 — 이름을 대야 함 | 가능 — 그것이 핵심 |
| 프라이버시 경계 | 아무것도 떠나지 않음 | 기본값에서는 아무것도 떠나지 않음. 원격 제공자를 선택하면 코드가 떠남 |
어느 쪽도 무조건 더 낫지 않다. 실패하는 방식이 다르다. 임베딩 우선 방식은 이름이 기억나지 않아도 “날짜를 파싱하는 함수”를 찾을 수 있다. 참조 그래프는 구조상 할 수 없는 일이다. 그 대가로 유지할 인덱스, 실행할 임베딩 모델, 내려야 할 프라이버시 결정이 생긴다. 정적 분석 우선 방식에는 그런 비용이 하나도 없고, 그런 재현율도 없다.
언제 이 방식을 선택할까?
코드베이스에서 의미 기반 검색을 원하고 로컬 모델을 실행해도 괜찮다면 Continue의 인덱싱이 잘 맞는다. 기본 VS Code 설정에서는 모든 것을 내 컴퓨터에 두므로 독점 코드에도 합리적인 선택이다. 다만 두 가지는 분명히 알고 시작해야 한다. 백그라운드 인덱스를 유지해야 하고(첫 구축이 비싸며 새로고침은 설계상 저렴하다), 더 나은 재현율을 위해 원격 임베딩 제공자로 바꾸는 순간 청크와 쿼리가 컴퓨터를 떠나기 시작한다. 그러므로 이 선택은 설정을 복사해 붙여 넣다가 우연히 할 일이 아니라 의도적으로 내려야 할 결정이다.
방법론과 범위
나는 **Continue v2.1.0의 커밋 b238c1f**를 읽었고, core/indexing/, core/context/retrieval/, 설정 로더, 임베딩 제공자에 초점을 맞췄다. 핵심 주장 세 가지, 즉 기본값이 로컬 임베딩이라는 점(설정한 원격 제공자를 통해서만 코드가 외부로 나감), SHA-256 콘텐츠 해시 태그가 브랜치 사이에서 재사용된다는 점, 한 번 순회해 여러 인덱스를 만들고 벡터·FTS·리랭크를 함께 쓰는 검색은 각각 이 커밋을 대상으로 적대적 주장 검증을 거쳐 확인됐다. 줄 번호는 해당 커밋에서 정확하다.
범위의 주의점을 명확히 밝힌다.
- 모델이 패키징 스크립트와 런타임 플래그를 통해 번들되고 로컬에서 로드된다는 점을 확인했으며, git 트리의 바이너리 가중치를 열어 확인한 것은 아니다(가중치는 커밋되지 않고 패키징할 때 생성된다).
- 어떤 인덱스를 구축할지는 활성화한 컨텍스트 제공자에 따라 달라진다. 임베딩 인덱스는 이를 요청하는 무언가가 있을 때만 구축된다(
CodebaseIndexer.ts:169). 나는 전체 집합을 설명했다. - 코드가 어디로 가는지 알아보기 위해 인덱싱과 임베딩 경로를 감사했으며, Continue의 전체 텔레메트리 계층을 처음부터 끝까지 감사한 것은 아니다.
Footnotes
-
“아무것도 떠나지 않는다”고 과장하지 않기 위해 한 가지 미묘한 점을 솔직히 밝힌다. ONNX wasm 런타임 바이너리가 로컬에서 발견되지 않으면 jsdelivr CDN에서 가져올 수 있다(
env.js:57). 이것은 실행 엔진인 런타임 자산이지 내 코드가 아니다. 모델 가중치는 로컬에 있고 원격 모델 가져오기는 비활성화돼 있다. ↩ -
반대 경우도 빠뜨리지 말자. 파일 콘텐츠를 참조하는 마지막 태그까지 사라지면
del이 되고 임베딩은 영원히 남는 것이 아니라 삭제된다. 둘 이상의 태그가 공유하는 콘텐츠는 삭제되지 않고 태그만 제거된다(removeTag). ↩


