AWS Builder Center
kiro-cli 의 Knowledge Base 로직 뜯어보기

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  — --include and --exclude flags for the /knowledge add command
  • #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 가 그 근거다).
kiro-cli 의 KB 데이터 흐름. 소스 파일 → text_chunker (split_whitespace 단위) → 임베딩 또는 BM25 → on-disk 인덱스

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
[target.'cfg(not(all(target_os = "linux", target_arch = "aarch64")))'.dependencies]
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 와 반대로, 한꺼번에 모두 입력으로 넣는 접근이다.

비교 매트릭스로 본 차이

지금까지 본 도구들을 한 차원으로 정리해보면 패턴이 분명해진다.
코드베이스 RAG 도구 비교 매트릭스. kiro-cli 는 단일 모드 (Fast 또는 Best 중 택일), Continue 는 4 indexer hybrid, Cody 는 embedding 폐기, aider 는 RAG 자체 미사용
두드러진 차이는 kiro 와 Continue 가 같은 차원 (BM25 + Dense 둘 다 지원) 을 다른 방식으로 푼다는 점이다. kiro 는 KB 추가 시점에 --index-type 으로 둘 중 하나만 골라야 한다. Continue 는 활성화된 indexer 들이 한 사이클 안에서 모두 빌드된다 (SQLite 경합 회피를 위해 병렬이 아닌 순차). 검색 비용은 kiro 쪽이 적지만, 한 KB 안에서 sparse 와 dense 결과를 섞어 다시 순위를 매기는 (reranking) 같은 하이브리드 검색은 사용자에게 노출되지 않는다.
chunk 한 차원만 따로 보면 kiro 는 다른 도구와 다른 트레이드오프를 가진다.
각 도구의 chunk 단위 비교. kiro 는 512 단어로 자르지만 임베딩 시 128 토큰만 본다
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 를 직접 쓰거나 만들 일이 있다면 이 비교를 첫 검토 단계로 두는 것이 적합하다.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article