
kiro-cli 의 Knowledge Base 로직 뜯어보기
kiro-cli 가 코드베이스를 어떻게 색인하고 검색하는지 binary 와 on-disk 포맷 수준에서 분해하고, Continue, Cody, Tabby, aider 같은 OSS 코딩 도구들이 같은 문제 영역을 어떻게 다르게 다루는지 비교해본다.
kiro-cli 가 chat 안에서
/knowledge add 같은 명령을 노출한다는 점은 공식 문서에도 적혀 있다. 그런데 이 KB 가 실제로 어떻게 동작하는지는 docs 에 자세히 적혀 있지 않다. 문서 한 조각 (chunk) 이 어떻게 잘리는지, 임베딩은 어디서 나오는지, 인덱스가 어떤 자료구조인지가 빠져 있다. 명령을 한 번 돌려 보고 만들어진 파일을 따라가면 docs 보다 더 많은 게 보인다.이 글에서는 두 가지를 다룬다. 첫째, kiro-cli 의 KB 를 사용자 명령에서 시작해 on-disk 파일과 source code 까지 따라가본다. 둘째, 같은 문제 영역을 다루는 GitHub OSS 도구들 (Continue.dev, Sourcegraph Cody, Tabby, aider 등) 이 어떻게 다른 선택을 했는지를 비교한다.
KB 활성화와 저장 위치
기본은 비활성이다.
/knowledge add 를 처음 던지면 다음 메시지가 뜬다.1
Knowledge tool is disabled. Enable it with: kiro-cli settings chat.enableKnowledge true
활성화 후
/knowledge add --name <NAME> --path <PATH> --index-type <Fast|Best> 로 KB 를 추가하면 두 위치에 데이터가 떨어진다.1
2
3
4
5
6
7
8
9
10
11
~/.semantic_search/models/all-MiniLM-L6-v2/
model.safetensors # 87 MB
tokenizer.json # max_length=128, padding=Fixed:128
~/.local/share/kiro-cli/knowledge_bases/<agent>_<hash>/
contexts.json # KB 메타데이터 (name, source_path, embedding_type, item_count, auto_sync, ...)
<context-uuid>/
data.bm25.json # Fast 인덱스
data.json # Best 인덱스의 chunk + vector
index.hnsw.data # HNSW vector binary
index.hnsw.graph # HNSW graph topology
임베딩 모델과 KB 데이터는 다른 디렉토리에 분리되어 있다. 모델은
~/.semantic_search/ 에서 한 번만 다운로드되어 모든 KB 가 공유하고, KB 자체는 agent 단위로 격리되어 ~/.local/share/kiro-cli/knowledge_bases/ 안에 저장된다.두 인덱스 모드: Fast (BM25) 와 Best (HNSW)
--index-type 옵션이 핵심이다. 직접 두 모드로 같은 디렉토리에 KB 를 만들고 on-disk 결과를 옆에 두고 보면 차이가 분명하다.Fast 모드 (
/knowledge add --index-type Fast) 가 만드는 파일.1
data.bm25.json (418 bytes, 2 chunks)
JSON list 한 개. 각 entry 가
{id, payload: {text, file_type, total_chunks, chunk_index, path}, content} 구조. 임베딩 vector 는 없고 원문 그대로 저장된다.Best 모드 (
/knowledge add --index-type Best) 가 만드는 파일.1
2
3
data.json (9.7 KB, 2 chunks + vectors)
index.hnsw.data (3.1 KB, HNSW vector binary)
index.hnsw.graph (625 bytes, HNSW graph topology)
data.json 의 각 chunk 에 vector: [-0.097, 0.046, ...] 형태의 384 원소 float32 array 가 박혀 있다. index.hnsw.graph 의 binary header 를 hexdump 로 보면 anndists::dist::distances::DistCosine 와 f32 가 그대로 들어 있다.1
2
3
4
00000020 01 00 00 00 00 00 00 25 00 00 00 00 00 00 00 61 |.......%.......a|
00000030 6e 6e 64 69 73 74 73 3a 3a 64 69 73 74 3a 3a 64 |nndists::dist::d|
00000040 69 73 74 61 6e 63 65 73 3a 3a 44 69 73 74 43 6f |istances::DistCo|
00000050 73 69 6e 65 03 00 00 00 00 00 00 00 66 33 32 10 |sine........f32.|
anndists 가 라이브러리 이름처럼 보인다. 그런데 kiro-cli source repo 가 따로 공개돼 있지 않으니 의존성을 직접 확인할 곳이 없다. 이 시점에서 binary 자체를 정적 분석으로 들여다 본다.KB 코드의 출처와 의존성 버전
/usr/bin/kiro-cli-chat 에서 KB 관련 정보를 추출하면 source 의 위치가 드러난다.1
2
3
4
5
6
7
8
9
10
11
12
$ strings /usr/bin/kiro-cli-chat | grep -oE '(hnsw_rs|candle-core|candle-nn)-[0-9.]+' | sort -u
candle-core-0.9.2
candle-nn-0.9.2
hnsw_rs-0.3.4
$ strings /usr/bin/kiro-cli-chat | grep 'kirodotdev/Kiro/issues/new'
https://github.com/kirodotdev/Kiro/issues/new
$ strings /usr/bin/kiro-cli-chat | grep -E '\[#(2545|2608|3433)\]' | head -3
"description": "`--include` and `--exclude` flags for the `/knowledge add` command - [#2545](https://github.com/aws/amazon-q-developer-cli/pull/2545)"
"description": "Knowledge beta improvements (phase 2): Refactored async_client and added BM25 support - [#2608](https://github.com/aws/amazon-q-developer-cli/pull/2608)"
"description": "Kiro CLI announcement. Learn more at kiro.dev/cli-upgrade - [#3433](https://github.com/aws/amazon-q-developer-cli/pull/3433)"
세 가지가 보인다. 첫째, 사용자에게 노출되는 issue tracker URL 은
kirodotdev/Kiro (kiro 자체 repo) 다. 둘째, 그럼에도 changelog 의 PR backreference 는 모두 aws/amazon-q-developer-cli 의 PR 번호를 그대로 인용한다. 즉 사용자 issue 채널은 분리됐지만 source/PR 메타데이터는 aws/amazon-q-developer-cli 와 공유된다. 셋째, KB 핵심 의존성의 정확한 버전이 binary 안에 박혀 있다. hnsw_rs-0.3.4, candle-core-0.9.2 다.KB 관련 PR 도 changelog 에 그대로 남아 있다. binary 안 description 그대로 인용하면 이렇다.
- #2545 —
--includeand--excludeflags for the/knowledge addcommand - #2608 — Knowledge beta improvements (phase 2): Refactored async_client and added BM25 support
- #2647 — Agent-scoped knowledge base and context-specific search
- #3052 — Unified show and status commands for knowledge
- #3151 — PDF file support for knowledge
이 5 개가 docs 에 안 적힌 KB 진화 단계들이다. 한 가지 짚어둘 점: kiro-cli 자체의 source repo 는 공개돼 있지 않다. GitHub 에서
kiro-cli 로 검색해도 AWS 공식 repo 는 0건이다. 따라서 이 글에서 인용하는 Rust 코드는 aws/amazon-q-developer-cli 의 main branch 현재 시점 코드이고, binary 의 빌드 시점 코드와 1:1 일치한다는 보장은 없다. 실제로 main 의 Cargo.toml 은 hnsw_rs = "=0.3.1", candle-core = "0.9.1" 로 pin 되어 있는데, binary 는 0.3.4 / 0.9.2 다. 글의 코드 인용은 가장 가까운 가용 source 로 읽어야 한다.이제 그 main 의
crates/semantic-search-client/Cargo.toml 을 열어 위 anndists 의 정체를 확인할 수 있다.1
2
3
4
5
# Vector search library - pin to avoid edition2024 requirement
hnsw_rs = "=0.3.1"
# BM25 implementation - works on all platforms including ARM
bm25 = { version = "2.3.2", features = ["language_detection"] }
anndists 는 hnsw_rs 가 거리 함수 (distance metric) 를 가져다 쓰는 하위 crate (sub-crate) 로 보인다. 즉 Best 모드는 hnsw_rs 위에서 동작하고, 거리 함수는 cosine, 자료형 (dtype) 은 float32 다 (위 hexdump 가 그 근거다).
chunk 단위와 임베딩 입력 길이
text_chunker.rs 의 chunk 함수.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pub fn chunk_text(text: &str, chunk_size: Option<usize>, overlap: Option<usize>) -> Vec<String> {
let config = config::get_config();
let chunk_size = chunk_size.unwrap_or(config.chunk_size);
let overlap = overlap.unwrap_or(config.chunk_overlap);
let mut chunks = Vec::new();
let words: Vec<&str> = text.split_whitespace().collect();
// ...
while i < words.len() {
let end = (i + chunk_size).min(words.len());
let chunk = words[i..end].join(" ");
chunks.push(chunk);
i += chunk_size - overlap;
if i >= words.len() || i == 0 { break; }
}
chunks
}
text.split_whitespace() 로 단어를 자르고 단어 기준으로 chunk 를 만든다. 한 chunk 는 default 가 512 단어 이고 겹치는 부분 (overlap) 은 128 단어다 (config.rs::Default ). 즉 한 chunk 는 일반 README 한 페이지 분량이다.그런데 임베딩 시점에 토크나이저는 max_length=128 토큰으로 truncate 한다 (
tokenizer.json 의 truncation: {max_length: 128, strategy: LongestFirst}). all-MiniLM-L6-v2 의 BERT 토크나이저는 단어 1 개당 평균 1.3 ~ 1.5 subword 토큰을 만들어내므로, 128 토큰은 대략 80 ~ 100 단어에 해당한다.정리하면 chunk 자체는 512 단어로 만들어지지만, 임베딩 vector 는 앞쪽 약 80 ~ 100 단어 만 입력으로 받은 결과일 가능성이 높다. tokenizer.json 의
truncation: {strategy: LongestFirst, max_length: 128} 이 단일 시퀀스에서 어느 쪽을 자르는지는 (보통 LongestFirst 는 앞쪽을 유지하고 끝쪽을 잘라낸다) source 안에서 별도로 명시 확인하지 못했다. 다만 잘림 처리 자체는 일어난다. 한 chunk 의 뒷부분 약 400 단어가 의미 매칭에서 보이지 않을 가능성이 높고, 이는 sentence-transformers 의 max_seq_length=128 제약과 chunk_size=512 가 맞물려 생긴 한계로, Best 모드를 큰 파일에 쓸 때 가장 분명하게 드러나는 제약이다.BM25 인덱스 직렬화의 placeholder 상태
bm25_index.rs 의 save_to_disk 함수.1
2
3
4
5
6
7
8
9
10
11
12
13
14
pub fn save_to_disk<P: AsRef<Path>>(&self, path: P) -> crate::error::Result<()> {
// Extract documents from the search engine
let _engine = self.engine.read().unwrap();
let documents: Vec<SerializableDocument> = Vec::new();
// Note: The BM25 crate doesn't expose a way to iterate over documents
// This is a limitation - we'd need to track documents separately
// For now, this is a placeholder that would need the BM25 crate to expose document iteration
let file = File::create(path)?;
let writer = BufWriter::new(file);
serde_json::to_writer(writer, &documents)?;
Ok(())
}
빈
Vec 을 그대로 디스크에 쓴다. 코드 주석은 "BM25 crate 가 document iteration 을 노출하지 않아서 placeholder 상태" 라고 명시한다. 즉 우리가 본 data.bm25.json 은 다른 layer 에서 따로 chunk 원문이 직렬화 되어 들어간 결과지, BM25 라이브러리의 실제 인덱스 직렬화가 아니다. 코드만 읽어서는 다음 세션에서 BM25 인덱스가 의미 있게 복원될 수 없으므로, 재시작 시 메모리에서 다시 만들어지는 것으로 보인다 (직접 재시작해 timing 을 측정해 보지는 않았다).이 부분은 docs 에 안 적혀 있다. 직접 source 를 읽어야 보인다.
Linux ARM 에서의 임베딩 지원 범위
Cargo.toml 의 target 조건.1
2
3
4
5
[]
candle-core = { version = "0.9.1", features = [] }
candle-nn = "0.9.1"
candle-transformers = "0.9.1"
tokenizers = "0.21.1"
candle-* (HuggingFace 의 Rust ML framework) 와 tokenizers 가 Linux aarch64 에서는 빠진다. 즉 ARM Linux 에서 main 의 Cargo.toml 대로 빌드하면 candle 기반 임베딩 추론 (inference) 코드가 컴파일되지 않는다. mod.rs 에는 다른 백엔드 (ONNX 등) 가 보이지 않아 ARM 사용자는 Best 모드를 쓸 수 없고 Fast (BM25) 만 사용 가능할 가능성이 높다. 단, ARM 빌드를 직접 확인하지는 않았다.binary strings 에는 "Enable embeddings on Linux ARM (aarch64)" 라는 changelog string 도 보인다. 이는 향후 PR 흔적일 가능성이 높고, 현재 main 기준으로는 여전히 제외 상태다.
같은 영역의 OSS 도구들
여기까지 본 kiro 의 KB 는 한 줄로 요약하면 이렇다.
/knowledge add 로 추가한 디렉토리를 512 단어 chunk 로 잘라 (텍스트는 단어 빈도 기반 검색 (sparse) 인 BM25, 임베딩은 의미 기반 검색 (dense) 인 hnsw_rs HNSW 둘 중 하나로) ~/.local/share/kiro-cli/knowledge_bases/ 에 저장하고, 임베딩 추론은 binary 안의 candle-core-0.9.2 가 가리키듯 로컬에서 처리된다. 한 KB 가 두 모드 중 하나만 만드므로 같은 KB 안에서 sparse + dense 결과를 섞은 hybrid 검색은 사용자에게 노출되지 않는다.오픈소스 코드베이스 KB 도구들은 같은 문제를 어떻게 푸는지 본다. 살펴볼 다섯 도구는 Continue.dev (4 indexer hybrid), Sourcegraph Cody (임베딩 폐기), Tabby (Tantivy + binarized 임베딩), aider (RAG 미사용), repomix/gitingest (검색 없는 dump) 다. 인덱스 자료구조, sparse/dense 선택, 임베딩 운영 방식이 다섯 가지로 갈린다.
Continue.dev: 4 개 인덱서를 한 사이클에 빌드하는 hybrid 구조
Continue 의
CodebaseIndexer 는 한 codebase 에 4 개의 인덱스를 한 indexing 사이클 안에 함께 만든다.1
2
3
4
5
6
const indexTypeToIndexerMapping: Record<ContextIndexingType, ...> = {
chunk: // SQLite chunks (raw text by chunk)
codeSnippets: // AST 기반 함수/클래스 추출
fullTextSearch: // SQLite FTS (BM25)
embeddings: // LanceDB dense ANN
};
각 indexer 가 독립적으로 동작하고, 검색 (retrieval) 시점에 결과를 합친다. kiro 가 "Fast 모드 OR Best 모드" 둘 중 하나만 만드는 것과 달리, Continue 는 활성화된 context provider 가 어떤 인덱스를 필요로 하느냐에 따라 (
getIndexesToBuild() 가 dependsOnIndexing 의 합집합을 계산) 여러 indexer 를 한 indexing 사이클 안에서 빌드한다 (코드 주석에 따르면 SQLite 경합 회피를 위해 병렬이 아닌 순차 실행이다). 임베딩은 LanceDB (Lance 컬럼나 포맷 위의 ANN) 에 저장하고, BM25 는 SQLite FTS, AST 추출은 별도 SQLite 테이블이다.Sourcegraph Cody: 임베딩 폐기
Cody 의 2024-02-15 blog 에서 sourcegraph 는 embedding 을 정식 출시 (GA) 시점에 의도적으로 폐기했다. 이유는 세 가지로 명시되어 있다.
Your code has to be sent to a 3rd party (OpenAI) for processing, and not everyone wants their code to be relayed in this way. The process of creating embeddings and keeping them up-to-date introduces complexity that Sourcegraph admins have to manage. As the size of a codebase increases, so does the respective vector database, and searching vector databases for codebases with >100,000 repositories is complex and resource-intensive.
대신 자사 Code Search 플랫폼의 BM25 ranking function + 학습된 추가 신호 (ranking signal) 을 사용한다. 즉 Cody 의 검색은 100% sparse 다.
이 결정은 kiro 의 Best 모드와 정반대다. Cody 는 임베딩의 운영 부담 (3rd-party API, vector DB 유지보수, codebase 가 커질수록 늘어나는 vector DB 크기) 이 검색 품질의 이득보다 크다고 판단했다.
Tabby: Tantivy + binarized 임베딩
Tabby 의
crates/tabby-index/src/code/mod.rs 는 Lucene-Rust 포팅인 Tantivy 를 인덱스 백엔드로 쓴다. 코드 안에 binarize_embedding(embedding.iter()) 호출이 있어 임베딩을 양자화 (binarized) 한 뒤 인덱스에 넣는다. 양자화한 벡터로 검색 후보를 빠르게 추리고 정밀 순위 매기기는 별도 단계에서 처리하는 것이 일반적이지만, 본 글에서 Tabby 의 검색 측 정확한 파이프라인을 코드로 끝까지 추적하지는 않았다.kiro 가 raw float32 vector 를 저장하는 것과 대비된다.
aider: repo-map 기반 접근
aider 는 RAG 자체를 안 쓴다. 함수/클래스 시그니처를 추출해 한 텍스트 파일 (repo-map ) 로 만들고, graph ranking 알고리즘으로 LLM 의 토큰 한도 (budget) 에 맞춰 잘라낸 (trim) 뒤 prompt 에 주입한다. 임베딩도, 인덱스도 없다.
이 접근은 codebase 가 작거나 모델의 context window 가 충분히 클 때 가장 적합하다. 큰 monorepo 에서는 한도에 안 들어가서 실패한다.
repomix, gitingest: 전체 텍스트 dump
repomix , gitingest 같은 도구는 repo 전체를 한 token-friendly text 로 변환한다. 문서를 일정 단위로 자르는 처리 (chunking) 도, 검색도 없다. 사용자가 그 텍스트를 ChatGPT 등에 통째로 붙여넣는 식이다. RAG 와 반대로, 한꺼번에 모두 입력으로 넣는 접근이다.
비교 매트릭스로 본 차이
지금까지 본 도구들을 한 차원으로 정리해보면 패턴이 분명해진다.

두드러진 차이는 kiro 와 Continue 가 같은 차원 (
BM25 + Dense 둘 다 지원) 을 다른 방식으로 푼다는 점이다. kiro 는 KB 추가 시점에 --index-type 으로 둘 중 하나만 골라야 한다. Continue 는 활성화된 indexer 들이 한 사이클 안에서 모두 빌드된다 (SQLite 경합 회피를 위해 병렬이 아닌 순차). 검색 비용은 kiro 쪽이 적지만, 한 KB 안에서 sparse 와 dense 결과를 섞어 다시 순위를 매기는 (reranking) 같은 하이브리드 검색은 사용자에게 노출되지 않는다.chunk 한 차원만 따로 보면 kiro 는 다른 도구와 다른 트레이드오프를 가진다.

kiro
kiro 의 512 단어는 문서 chunk 로는 큰 편이지만, 그 안에서 실제로 임베딩 입력에 들어가는 범위는 앞 80~100 단어로 좁아진다. Continue 는 모델의
maxEmbeddingChunkSize 를 따르므로 모델 의존이고, Tabby 는 binarized 저장 후 정밀 단계에서 다시 순위를 매긴다. Cody 는 임베딩을 더 이상 쓰지 않는다.다섯 도구 중에서는 Tabby 의 디자인이 '로컬 only + 단순한 인덱스' 라는 면에서 kiro 와 가장 가깝다. 두 도구 모두 임베딩 추론을 로컬에서 하고 (kiro 의 경우
kiro-cli-chat binary 안의 candle-core-0.9.2 가 strings 로 직접 확인된다. Linux x86_64 빌드 기준이며 다른 OS 의 빌드 구성은 본 글에서 확인하지 않았다) 인덱스도 로컬 디스크에 둔다. 차이는 두 가지다. Tabby 는 Tantivy + binarized 임베딩의 hybrid 단일 layer 인 반면, kiro 는 두 모드 중 하나를 사용자가 골라야 한다. auto-sync 측면에서 kiro 는 자동 동기화가 기본 비활성이고 사용자가 명시적으로 /knowledge update 를 호출해야 한다. Tabby 의 자동 재인덱싱 메커니즘은 본 글에서 코드/문서로 직접 확인하지 않았다.정리
본문에서 본 결과를 OSS 의 풀이와 짝지어 보면 kiro 의 KB 가 어떤 부분까지 구현되어 있고 어떤 부분이 (Beta) 로 남아 있는지 분명해진다.
- 임베딩 입력이 chunk 앞부분에 한정된다 (chunk_size=512 단어 + max_seq=128 token 의 조합). Continue 는 모델의
maxEmbeddingChunkSize에 chunk size 를 맞춘다. Tabby 는 chunk 를 작게 가져가고 binarized 단계로 양을 늘린다. - 자동 동기화가 기본 비활성 (
contexts.json의auto_sync필드가 false). 사용자가/knowledge update를 명시적으로 호출해야 한다. 다른 OSS 도구의 자동 갱신 동작은 본 글에서 코드/문서로 직접 확인하지 않았다. - 한 KB 안에서 hybrid retrieval 불가. Fast 또는 Best 둘 중 하나만 만들어진다. Continue 는 4 개 인덱서 결과를 검색 시점에 합친다.
- BM25 인덱스 직렬화는 자리만 잡힌 placeholder 상태. 위에서 본
save_to_disk의 빈 Vec.
이 비교는 docs 만 읽어서는 도달하기 어렵다. system 에 깔린 4 개 binary 중 KB 코드는
/usr/bin/kiro-cli-chat 한 곳에 몰려 있고, 그 binary 의 strings + on-disk KB 포맷 + amazon-q-developer-cli main 코드 (issue/PR 메타데이터로 source 공유가 확인되지만 빌드 시점과 1:1 일치하는지는 보장 안 됨) 를 함께 봐야 위 finding 이 정리된다. 특히 crates/semantic-search-client/ 의 Cargo.toml, config.rs, processing/text_chunker.rs, index/bm25_index.rs 만 봐도 이 글의 chunk / BM25 / 의존성 관련 사실이 (binary 안의 정확한 의존성 버전과 함께) 검증된다.OSS 도구들도 마찬가지다. Continue 의
CodebaseIndexer.ts, Tabby 의 code/mod.rs, Cody 의 blog post, aider 의 repomap.py. 각 100~300 줄 안에서 그 도구의 검색 설계가 정리된다. 코드베이스 RAG 를 직접 쓰거나 만들 일이 있다면 이 비교를 첫 검토 단계로 두는 것이 적합하다.Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article