Core Concepts
RCE Code turns a repository into one queryable index. This page is the mental model: the handful of entities the index is built from, how they relate, and the properties — content addressing, two-phase resolution, honest freshness — that make the index fast and incremental.
The entities at a glance
Index
A single SQLite database at <repo>/.rce/code/index.db holding every file, chunk, symbol, reference, and embedding.
File
One source file, content-hashed and language-detected, the unit of incremental indexing.
Chunk
A definition-aligned slice of a file — the unit of search and embedding.
Symbol
A named definition (function, class, method, …) extracted from the AST.
Reference
A call or import site, resolved to a target symbol on a lazy second pass.
Embedding
A per-chunk vector, cached by content hash and queried by cosine KNN.
The index
Everything lives in a single SQLite database, created and migrated on first use at <repo>/.rce/code/index.db. There is one serialized writer: parsing happens in pure workers that hold no database handle, and the parent process applies their results one file per transaction. Indexing is a pure pipeline — walk, parse, chunk, extract symbols and references, then resolve the call graph — and it is content-addressed, so re-indexing an unchanged tree is nearly free.
Files and content hashing
Each source file is the unit of incremental indexing. A file is content-hashed (with blake3), language-detected, and tracked by its mtime. The hash defines a cache identity: unchanged files are hash-skipped, changed files are re-parsed, and deleted files are dropped. Chunk content hashes are deliberately position-independent — they exclude line and byte offsets — so moving a function down a file does not invalidate its chunk or its embedding.
Chunks
A Chunk is a definition-aligned slice of a file and the unit of both search and embedding. The AST chunker never splits a definition mid-body; each top-level definition becomes one chunk, with comments attached. Every chunk carries its line and byte span, a kind, an optional name, the raw content, a search_terms field (the sub-token-expanded text the lexical index queries), a content_hash, a token_count, and its language. A chunk may point at a parent_chunk_id — for example a method's enclosing class.
The ChunkKind enum classifies each chunk:
Symbols
A Symbol is a named definition extracted from the AST. Alongside its name, every symbol has a qualified_name — the names of its enclosing definitions joined by the language's separator (. for Python, :: for Rust, and so on), e.g. module.Class.method. Symbols carry a kind, source span (start/end line and column), an optional signature, docstring, and visibility, plus a parent_id and the chunk_id they belong to.
The SymbolKind enum is broader than ChunkKind:
Method reclassification
function whose nearest enclosing captured definition is a class is reclassified to method. This keeps, say, Python class bodies modelled as methods, while languages without that nesting (or with receiver-based methods, like Go) are classified per their own structure.References
A Reference is a use site — a call or an import, distinguished by the is_import flag. References are extracted by name first and resolved later: RCE Code stores the to_name immediately and fills in to_symbol_id on a lazy second pass, attaching a resolution_confidence score. Until that resolver runs — or when a name cannot be resolved — to_symbol_id stays None. Each reference also records where it came from (from_symbol_id, from_file_path) and its line/col. Resolved references are what power get_callers and get_callees.
Two-phase by design
Freshness and incremental indexing
Every indexed result carries a Freshness record so the caller can decide whether to trust it. It exposes the source file's file_mtime, the index's indexed_at time, whether the chunk is_stale(its source changed since it was indexed), and whether it has_embedding. Because the index is content-hash-cached, incremental re-indexing only touches files whose hash changed. Grep hits are the exception: they read the live filesystem and are always current, so a GrepHit carries no freshness at all.
| Entity | Key fields |
|---|---|
Chunk | id, file_path, start_line/end_line, kind, name, parent_chunk_id, content, search_terms, content_hash, token_count, language |
Symbol | id, name, qualified_name, kind, file_path, chunk_id, parent_id, signature, docstring, visibility, language |
Reference | id, from_symbol_id, from_file_path, to_name, to_symbol_id, resolution_confidence, is_import, line/col, language |
SearchResult | chunk, score, source (SearchSource), freshness, snippet, matched_terms |
Freshness | file_mtime, indexed_at, is_stale, has_embedding |
IndexStats | files, chunks, symbols, refs, embedding_coverage, last_full_sync_at, stale_files, skipped, fts_ok |
Embeddings
Each chunk can have a single vector embedding, the basis of semantic and hybrid search. Embedding is lazy: embed() only embeds chunks that lack a current embedding, and the embedding cache is keyed by the chunk's content hash combined with the model id and version — so unchanged content is never re-embedded, even across model swaps for content that already matched. Vectors are queried by cosine KNN through a numpy backend by default, with sqlite-vec available as an option.
Incremental, not rebuilt
index() and embed() after a change re-parses and re-embeds only what actually changed — both are idempotent and content-hash-cached.Search results and sources
Indexed queries return SearchResult objects: the matched chunk, a score, the source modality that produced it, the chunk's freshness, an optional snippet, and the matched_terms. The SearchSource enum names where a result came from:
IndexStats (returned by stats()) rolls these entities up into a health snapshot — file, chunk, symbol, and reference counts, embedding coverage, the last full sync time, and whether the FTS index is intact.