Weaviate Documentation Indexing¶
Overview¶
Weaviate Documentation Indexing is the pipeline that fetches documentation files (Markdown, text, reStructuredText) from GitLab repositories and stores them in Minh's Weaviate vector database for semantic search. This enables Minh to answer questions by searching across all indexed project documentation using natural-language similarity rather than keyword matching.
Each repository is tagged with a product and content_type to support product-aware search and timeline-aware RAG responses. This metadata flows from the repo registry through indexing into Weaviate, and is surfaced in search results and LLM context.
The pipeline supports both full reindexing (clear-and-rebuild) and incremental indexing (diff-based updates since the last indexed commit). Incremental indexing reduces GitLab API calls and Weaviate write load by only processing added, modified, renamed, and deleted files.
The feature comprises three main components:
- Repo Registry (
src/core/repo_registry.py) — the single source of truth for which repositories are indexable, including product and content-type classification. - Repository Indexing Service (
src/services/repository_indexing_service.py) — orchestrates fetching files from GitLab and storing them in Weaviate, supporting both full and incremental indexing modes. - Vector Memory (
src/core/vector_memory.py) — manages the Weaviate connection, theDocumentationcollection where indexed content is stored, and theIndexingStatecollection that tracks per-repo commit SHAs for incremental indexing.
Architecture¶
Repo Registry¶
RepositoryConfig is a frozen dataclass that defines each indexable repository:
| Field | Type | Default | Description |
|---|---|---|---|
name |
str |
— | Short repository identifier (also used as the Weaviate category value) |
gitlab_path |
str |
— | Full GitLab project path (e.g. the-smithy1/agents/minh) |
product |
str |
"glass-umbrella" |
Product grouping — must be one of VALID_PRODUCTS |
content_type |
str |
"technical" |
Content classification — must be one of VALID_CONTENT_TYPES |
branch |
str |
"main" |
Branch to read files from |
file_patterns |
tuple |
("*.md", "*.txt", "*.rst") |
Glob patterns for files to index |
max_files |
int |
200 |
Maximum number of files indexed per repo |
Valid product values (VALID_PRODUCTS)¶
grit, pariah, paragon, paramount, glass-umbrella, agents, mastery
Valid content-type values (VALID_CONTENT_TYPES)¶
worldbuilding, narrative, game-implementation, technical
All configured repositories are listed in the REPO_REGISTRY tuple. As of the latest changes, this includes repositories such as knowledge-sanctuary, minh, glass-umbrella-knowledge-repo, agents-shared, wisp, spark, pearl, taryn, arturo, roland, chisel, and the Paradigm trilogy repos (paradigm-pariah, paradigm-paragon, paradigm-paramount, shared-universe) and mobile repos (reader, smithy-auth), among others.
Helper functions¶
get_repo_config(name)— return theRepositoryConfigfor a given repo name.get_repo_path(name)— return thegitlab_pathfor a given repo name.get_repos_by_product(product)— return all repos belonging to a product (e.g."grit","agents").get_repos_by_content_type(content_type)— return all repos with a given content type (e.g."worldbuilding").get_gitlab_base_url(category)— return the GitLab blob URL for a Weaviate document category.
Weaviate Collections¶
Documentation Collection¶
The Documentation collection in Weaviate stores each indexed file with these properties:
title(text) — extracted from the first#heading, or the filenamecontent(text) — full file content (truncated at 50 000 characters)file_path(text) — path within the repositoryurl(text) — GitLab blob URL base for the source repocategory(text) — repository name, used for filteringtags(text array) — includes the repo name and file extensionproduct(text) — product grouping from the repo config (e.g."grit","agents"); stored only when non-emptycontent_type(text) — content classification from the repo config (e.g."worldbuilding","technical"); stored only when non-emptylast_updated(date) — timestamp when the document was indexedaccess_count(int) — initialised to 0
Vectorisation uses the text2vec-transformers module.
IndexingState Collection¶
The IndexingState collection tracks per-repository commit SHAs to support incremental indexing. It stores metadata only — no vectoriser is configured (uses Vectorizer.none() to save CPU).
Properties:
repo_name(text) — the repository name (matches thecategoryvalue inDocumentation)last_sha(text) — the HEAD commit SHA at the time of the last successful indexlast_indexed_at(date) — RFC 3339 timestamp of when the state was recorded
This collection is created automatically by VectorMemory.ensure_collections() if it does not already exist.
GitLab Client — Incremental Indexing Support¶
The GitLabClient provides two methods used by incremental indexing:
get_head_sha(project_id, ref="main")— returns the latest commit SHA for a branch. ReturnsNoneon error.compare_commits(project_id, from_sha, to_sha)— compares two commits and returns a dict with keysadded,modified, anddeleted(each a list of file paths). Renamed files are treated as a delete of the old path plus an add of the new path, keeping the index clean. ReturnsNoneon error (caller falls back to full reindex).
Indexing Flow¶
Full Reindex¶
Repo Registry ──▶ RepositoryIndexingService.index_repository()
│
├── GitLabClient.get_documentation_files()
│ (list files matching extensions)
│
├── clear_repository()
│ (delete existing Weaviate docs for this repo)
│
├── for each file:
│ GitLabClient.get_file_content()
│ ──▶ VectorMemory.store_documentation()
│ (includes product & content_type from repo config)
│
└── VectorMemory.set_indexing_state()
(record HEAD SHA for future incremental runs)
Before re-indexing a repository, the service deletes all existing documents with that repository's category to ensure a clean state. After a successful run, the current HEAD SHA is recorded so future runs can index incrementally.
Incremental Reindex¶
Repo Registry ──▶ RepositoryIndexingService.index_repository_incremental()
│
├── VectorMemory.get_indexing_state()
│ (fetch last-indexed SHA)
│
├── GitLabClient.get_head_sha()
│ (current HEAD)
│
├── GitLabClient.compare_commits(last_sha → head_sha)
│ (returns added / modified / deleted file lists)
│
├── deleted files ──▶ VectorMemory.delete_documentation_by_paths()
│
├── added / modified files ──▶ for each file:
│ GitLabClient.get_file_content()
│ ──▶ VectorMemory.upsert_documentation()
│
└── VectorMemory.set_indexing_state()
(record new HEAD SHA)
Incremental indexing falls back to full reindex when:
- No previous SHA is recorded (first run for this repo).
- The diff exceeds
MAX_INCREMENTAL_FILES(default: 50) — the overhead of many individual GitLab file fetches and Weaviate upserts dominates above this point. - The GitLab compare API fails (e.g. force-push, rebase, API error).
No clear_repository() call is made during incremental indexing — the index remains searchable throughout.
Vector Memory — Incremental Indexing Methods¶
delete_documentation_by_paths(category, file_paths)— deletes specific documents from a repo category by file path, without clearing the entire repo. Returns the number of objects deleted (0 on error or when not connected).upsert_documentation(...)— insert-or-replace a single document by(category, file_path). Deletes any existing match first, then inserts the new version. Guarantees no duplicates while keeping the collection searchable throughout.get_indexing_state(repo_name)— fetch the stored indexing state for a repo. Returns a dict withlast_shaandlast_indexed_at, orNoneif no record exists (callers should treat missing state as "do a full reindex").set_indexing_state(repo_name, sha)— upsert the indexing state for a repo. Deletes any prior record then inserts a fresh one (Weaviate v4 does not provide native upsert for non-deterministic UUIDs). ReturnsTrueon success,Falseon error.
RAG Response Generation¶
When Minh answers a question using indexed documentation, the RAG pipeline now annotates each document heading with its product provenance. For example, a document from the pariah product with content type narrative is rendered as:
The LLM prompt instructs the model to note distinctions when documents come from different products or timelines (e.g. Pariah vs Paragon), helping produce more accurate, context-aware responses.
Usage¶
Index all repositories¶
from services.repository_indexing_service import RepositoryIndexingService
service = RepositoryIndexingService(
gitlab_client=gitlab_client,
vector_memory=vector_memory,
)
results = service.index_all()
for name, result in results.items():
print(f"{name}: indexed={result.files_indexed}, errors={len(result.errors)}")
Index a single repository by name¶
result = service.index_by_name("knowledge-sanctuary")
if result and result.success:
print(f"Indexed {result.files_indexed} files")
Index a single repository incrementally¶
from core.repo_registry import get_repo_config
repo_config = get_repo_config("knowledge-sanctuary")
result = service.index_repository_incremental(repo_config)
if result and result.success:
print(f"Indexed {result.files_indexed} files (incremental)")
Incremental indexing automatically falls back to a full reindex when no prior state exists or the diff is too large (see Indexing Flow — Incremental Reindex for details).
Search indexed documentation¶
from core.vector_memory import VectorMemory
vm = VectorMemory()
results = vm.search_documentation("how does the repo registry work", limit=5)
# Filter by repository
results = vm.search_documentation("deployment steps", category="minh", limit=3)
# Results now include product and content_type fields
for doc in results:
print(f"{doc['title']} [{doc['product']} / {doc['content_type']}]")
Query repositories by product or content type¶
from core.repo_registry import get_repos_by_product, get_repos_by_content_type
# Get all agent repositories
agent_repos = get_repos_by_product("agents")
for repo in agent_repos:
print(f"{repo.name}: {repo.gitlab_path}")
# Get all worldbuilding repositories
worldbuilding_repos = get_repos_by_content_type("worldbuilding")
# Get all Paradigm narrative repositories
pariah_repos = get_repos_by_product("pariah")
paramount_repos = get_repos_by_product("paramount")
Inspect and manage indexing state¶
from core.vector_memory import VectorMemory
vm = VectorMemory()
# Check last-indexed SHA for a repo
state = vm.get_indexing_state("knowledge-sanctuary")
if state:
print(f"Last SHA: {state['last_sha']}, indexed at: {state['last_indexed_at']}")
else:
print("No indexing state — full reindex required")
# Manually set indexing state (e.g. after a migration)
vm.set_indexing_state("knowledge-sanctuary", "abc123def456")
Delete specific documents by file path¶
# Remove specific files from the index without clearing the entire repo
deleted = vm.delete_documentation_by_paths("minh", ["docs/old-page.md", "docs/renamed.md"])
print(f"Deleted {deleted} documents")
Clear a repository's indexed documents¶
Configuration¶
Environment Variables¶
| Variable | Default | Description |
|---|---|---|
WEAVIATE_URL |
— | Full Weaviate URL (e.g. http://weaviate:8080). Takes priority over host/port. |
WEAVIATE_HOST |
localhost |
Weaviate hostname (used if WEAVIATE_URL is unset) |
WEAVIATE_PORT |
8080 |
Weaviate port (used if WEAVIATE_URL is unset) |
GITLAB_GROUP |
the-smithy1 |
GitLab group prefix for all repository paths |
Constants¶
| Constant | Value | Location | Description |
|---|---|---|---|
MAX_CONTENT_LENGTH |
50 000 | repository_indexing_service.py |
Maximum characters per file before truncation |
MAX_INCREMENTAL_FILES |
50 | repository_indexing_service.py |
Diff size above which incremental indexing falls back to full reindex |
Prerequisites¶
- A running Weaviate instance with the
text2vec-transformersvectoriser module enabled. - A GitLab access token with read access to the repositories listed in the registry.
- The
weaviate-clientPython package (v4 API).
Error Handling¶
- If the GitLab client or vector memory is not configured,
index_repositoryreturns anIndexingResultwith the error recorded in theerrorslist. - Files that fail to fetch or store are logged individually; the service continues with the remaining files.
- Empty files are skipped (counted in
files_skipped). - Content exceeding 50 000 characters is truncated with a
[Content truncated...]marker. VectorMemorymethods silently log errors and return empty results when Weaviate is unreachable, so callers degrade gracefully.- Every
RepositoryConfigmust use aproductvalue fromVALID_PRODUCTSand acontent_typevalue fromVALID_CONTENT_TYPES; tests enforce this constraint across the entire registry. get_indexing_state()returnsNoneon any error; callers should treat missing state as "do a full reindex".set_indexing_state()returnsFalseon error, and the consequence depends on where it failed. An early return — not connected, read-only cooldown, or missing arguments — leaves any prior record intact, so the next run still indexes incrementally, just from the older SHA (safe: the comparison still spans every change since). If the delete succeeds and the insert then fails, no record remains and the next run falls back to a full reindex. Either way the index stays correct; a persistently failing state write is worth alerting on because of the repeated cost, not because of drift.
See also¶
The portable technique in this document has been extracted into client-free patterns. This page keeps the implementation detail; the patterns carry the part that transfers.