# How ChromaDB Stores and Retrieves Vector Embeddings in LifeTrace

> Discover how ChromaDB stores and retrieves vector embeddings for LifeTrace using SQLite files and cosine similarity search. Learn about metadata filtering and reranking.

- Repository: [FreeU-group/lifetrace](https://github.com/freeu-group/lifetrace)
- Tags: internals
- Published: 2026-03-02

---

**LifeTrace uses ChromaDB as a persistent vector store that saves dense embeddings to SQLite-backed files on disk and retrieves them via cosine similarity search, supporting metadata filtering and optional cross-encoder reranking.**

The freeu-group/lifetrace repository implements a complete **vector storage and retrieval pipeline** using ChromaDB to power semantic search over OCR text and personal documents. The core integration resides in [`lifetrace/llm/vector_db.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/llm/vector_db.py), where the `VectorDatabase` class orchestrates embedding generation, persistent storage, and similarity-based retrieval. Understanding how ChromaDB stores and retrieves vector embeddings reveals the mechanics behind LifeTrace’s context-aware query capabilities.

## Initializing the ChromaDB Persistent Client

LifeTrace configures ChromaDB for **on-disk persistence** to ensure embeddings survive process restarts. The initialization flow determines storage paths, configures the client, and establishes the data schema.

### Configuring the Storage Directory

The system determines the persistent directory via `lifetrace/util/path_utils.py#get_vector_db_dir`, reading `settings.vector_db.persist_directory` from the Dynaconf configuration. This path feeds directly into the Chroma client constructor.

```python

# lifetrace/llm/vector_db.py (lines 90-104)

self.chroma_client = chromadb.PersistentClient(
    path=str(self.vector_db_path),
    settings=Settings(anonymized_telemetry=False, allow_reset=True),
)

```

### Collection Setup

After initializing the `PersistentClient`, LifeTrace obtains or creates a named collection via `get_or_create_collection`. The collection stores three parallel data structures: **documents** (raw text), **embeddings** (dense vectors), and **metadatas** (JSON attributes).

```python

# lifetrace/llm/vector_db.py (lines 90-104 continued)

self.collection = self.chroma_client.get_or_create_collection(
    name=self.collection_name,
    metadata={"description": "LifeTrace OCR text embeddings"},
)

```

## Storing Vector Embeddings

The `add_document` method implements the storage logic, converting text to vectors and persisting them with searchable metadata.

### Embedding Generation and Metadata Construction

LifeTrace first transforms raw text into a **dense embedding vector** using the configured `sentence-transformers` model via `self.embed_text`. It then constructs a metadata payload containing the timestamp, text length, and an MD5 hash for integrity verification.

```python

# lifetrace/llm/vector_db.py (lines 81-88, 162-188)

embedding = self.embed_text(text)
doc_metadata = {
    "timestamp": get_utc_now().isoformat(),
    "text_length": len(text),
    "text_hash": hashlib.md5(text.encode(), usedforsecurity=False).hexdigest(),
}

```

### Persisting to ChromaDB

The method calls `collection.add` with parallel lists for the document, its embedding, metadata, and a unique identifier. ChromaDB writes these vectors to an **SQLite-backed file** inside the persist directory, enabling fast nearest-neighbor lookups across sessions.

```python

# lifetrace/llm/vector_db.py (lines 81-88, 162-188 continued)

collection.add(
    documents=[text],
    embeddings=[embedding],
    metadatas=[doc_metadata],
    ids=[doc_id],
)

```

*Example: Storing an OCR result*

```python
from lifetrace.llm.vector_db import VectorDatabase

vec_db = VectorDatabase()
doc_id = "ocr-2024-03-02-001"
text = "Meeting notes: discuss quarterly budget and hiring plan."
vec_db.add_document(doc_id, text, metadata={"source": "screenshot_001.png"})

```

## Retrieving Embeddings via Semantic Search

The `search` method retrieves relevant context by comparing query embeddings against the stored vector index.

### Embedding Queries and Similarity Search

LifeTrace embeds the query string using the same model (`embed_text`) to ensure vector space consistency. It then invokes `collection.query` with the query embedding and desired result count (`n_results`).

```python

# lifetrace/llm/vector_db.py (lines 312-330)

query_embedding = self.embed_text(query)
results = self.collection.query(
    query_embeddings=[query_embedding],
    n_results=top_k,
    where=cleaned_where,
)

```

ChromaDB returns the **top-k nearest vectors** along with their original documents, metadata, and distance scores. LifeTrace reshapes this response into a list of dictionaries for downstream consumption.

### Filtering by Metadata

The retrieval pipeline supports **metadata constraints** via the `where` parameter. LifeTrace cleans and passes filter dictionaries (e.g., source file constraints) directly to ChromaDB’s query engine.

*Example: Searching with metadata filters*

```python
filters = {"source": {"$eq": "screenshot_001.png"}}
filtered = vec_db.search("budget", top_k=3, where=filters)

```

### Reranking with Cross-Encoders

After initial retrieval, LifeTrace optionally improves relevance using a **cross-encoder** (`self._get_cross_encoder`). This secondary model reranks the candidate documents based on direct query-document interaction scores, refining the order without modifying the underlying vector storage.

*Example: Reranking retrieved results*

```python
docs = [r["document"] for r in results]
reranked = vec_db.rerank(query, docs, top_k=3)

for doc, score in reranked:
    print(f"Score {score:.4f}: {doc[:100]}")

```

## Summary

- **Persistent Storage**: LifeTrace initializes `chromadb.PersistentClient` pointing to a configurable directory (resolved via [`lifetrace/util/path_utils.py`](https://github.com/freeu-group/lifetrace/blob/main/lifetrace/util/path_utils.py)), storing vectors in SQLite-backed files.
- **Three-Column Structure**: Each ChromaDB collection maintains parallel columns for documents, embeddings, and metadatas, enabling rich filtering.
- **Embedding Pipeline**: Text transforms into dense vectors via `sentence-transformers` before storage, with automatic metadata generation (timestamps, hashes).
- **Similarity Retrieval**: Queries embed via the same model, then execute via `collection.query` for cosine-similarity nearest-neighbor search.
- **Enhanced Ranking**: Optional cross-encoder reranking improves result relevance without altering the stored vector index.

## Frequently Asked Questions

### What storage backend does LifeTrace use for ChromaDB?

LifeTrace configures ChromaDB with `PersistentClient`, which stores data in **SQLite-backed files** on disk at the path specified by `settings.vector_db.persist_directory`. This ensures embeddings persist across application restarts and reboots.

### How does LifeTrace generate embeddings before storing them?

The `VectorDatabase` class uses a `sentence-transformers` model (invoked via `self.embed_text`) to convert raw text into **dense embedding vectors** immediately before calling `collection.add`. This guarantees that both stored documents and subsequent queries occupy the same vector space.

### Can I filter search results by metadata in LifeTrace?

Yes. The `search` method accepts a `where` dictionary that applies **metadata constraints** during the ChromaDB query. For example, you can restrict results to documents from a specific source file using filters like `{"source": {"$eq": "screenshot.png"}}`.

### Does LifeTrace support reranking of retrieved documents?

Yes. After the initial vector similarity search, LifeTrace can invoke a **cross-encoder** via the `rerank` method to rescore the top-k candidates. This step analyzes query-document pairs directly, often improving relevance beyond pure vector cosine similarity.