Drawer 00 · Shelf list & topology

A catalogue of the retrieval service

CodeScope is a self-hosted code search service. A Python package, compute_service, clones repositories, splits them into syntax-aware chunks, embeds those chunks, and answers queries by fusing a keyword index with a vector index. A Next.js application in web/ is the reading room. These seven cards describe what the code actually does — including the parts that are wired and the parts that are not.

00.1What the object is

The distributed artefact is a single Python package plus a web client. pyproject.toml:6 names the distribution compute-service at version 0.1.0, classified Development Status :: 3 - Alpha. It exposes one console entry point, compute-service = "compute_service.main:run" (pyproject.toml:91), which is a thin wrapper over uvicorn.run. There is no argparse or click CLI; the operational surface is the ASGI app, a shell wrapper at scripts/start.sh, and a two-profile docker-compose.yml.

Card 00-aRetrieval

Two indexes, one ranking

Keyword recall comes from Zoekt (with a ripgrep fallback); semantic recall comes from a FAISS inner-product index over normalised embeddings. The two result lists are merged by Reciprocal Rank Fusion at hybrid_search.py:183, then re-scored by a cross-encoder or by Cohere's rerank API.

Card 00-bChunking

Syntax-aware units

ASTChunker (ast_chunker.py:133) uses tree-sitter to cut files at function, class and import boundaries for fourteen declared languages, splitting anything over 150 lines and back-filling uncovered regions as module chunks. Files with no parser fall back to 80-line windows with 10 lines of overlap.

Card 00-cEmbedding

Local or hosted, swappable

UnifiedEmbeddingService fronts a local sentence-transformers model and two hosted providers (Voyage, OpenAI). Switching provider changes vector width, so the FAISS index is torn down and rebuilt via reinitialize() (vector_store_faiss.py:135).

Card 00-dAnswering

Retrieval-augmented Q&A

POST /chat classifies the question, retrieves up to 50 chunks, optionally prepends a stored repository summary, and forwards the whole thing to either a local Ollama model or the Anthropic Messages API. The response carries citations, a confidence number and the routing rationale.

00.2Module topology

Module topology of the compute_service package Four horizontal bands. The top band holds the FastAPI entry point main.py, the settings module config.py and the Pydantic models package. The second band holds ten route modules, nine of which are mounted and one, github.py, which is drawn dashed because it is never included in the application. The third band holds twenty-two service modules grouped into retrieval, ingest, and embedding and state clusters; index_worker.py and the memory modules are drawn dashed because nothing imports them. The bottom band holds the external process boundaries: the FAISS index files, four SQLite databases, the Zoekt and ripgrep binaries, an Ollama server on port 11434, and the hosted model APIs. compute_service/ — module topology (real filenames) main.py FastAPI app · lifespan() config.py Settings · COMPUTE_* models/ schemas · repo_schemas · memory_schemas lifespan() constructs every service and publishes them on app.state (main.py:228) routes/ — 9 routers mounted at main.py:356-364 embed.py search.py index.py repos.py files.py memory.py chat.py navigate.py embeddings.py github.py — not mounted services/ — 22 modules retrieval hybrid_search.py zoekt.py vector_store_faiss.py reranker.py query_router.py cross_repo_search.py · confidence.py ingest indexer.py ast_chunker.py contextual_retrieval.py repo_manager.py github_sync.py — unreachable index_worker.py — no importer embedding & state unified_embeddings.py embeddings.py cloud_embeddings.py embedding_cache.py living_summary.py memory.py · memory_embeddings.py process boundary — filesystem, subprocess, network faiss.index metadata.pkl 4 × SQLite repos · caches · summaries zoekt / zoekt-index subprocess, rg fallback Ollama :11434 context + chat models hosted model APIs embeddings · rerank · chat legend constructed in lifespan() and held on app.state imported and reachable from a mounted route present in the tree but not reachable at runtime state or dependency outside the Python process read/write of persistent state optional path, taken only when configured
Figure 00.1 — Module topology Drawn from the import graph, not from the README (which describes a routes/tasks.py and services/executor.py that no longer exist). Dashed modules are physically present but have no importer reachable from main.py; they are catalogued in Drawer 06.

00.3Shelf list

The register below is the complete compute_service tree. “Wiring” records whether the module is reachable from the mounted application, not whether its logic is correct.

Register A — services/ (22 modules)
Call no. Module Lines Principal class Wiring
SVC·01hybrid_search.py410HybridSearchServicelive
SVC·02indexer.py405CodeIndexerlive
SVC·03ast_chunker.py600ASTChunkerlive
SVC·04vector_store_faiss.py452FAISSVectorStorelive
SVC·05zoekt.py341ZoektServicelive
SVC·06reranker.py616CrossEncoderReranker · CohereReranker · QueryExpanderlive
SVC·07embeddings.py385EmbeddingServicelive
SVC·08unified_embeddings.py352UnifiedEmbeddingServicelive
SVC·09cloud_embeddings.py398VoyageEmbeddingProvider · OpenAIEmbeddingProvideroptional
SVC·10embedding_cache.py294EmbeddingCachelive
SVC·11repo_manager.py479RepoManagerlive
SVC·12cross_repo_search.py209CrossRepoSearchlive
SVC·13query_router.py326QueryRouterlive
SVC·14confidence.py96calculate_confidence · extract_sourceslive
SVC·15contextual_retrieval.py330ContextualRetrievalServiceconditional
SVC·16living_summary.py346LivingSummaryServiceread-only
SVC·17github_sync.py517GitHubSyncunreachable
SVC·18index_worker.py405IndexWorkerno importer
SVC·19memory.py608ServerAMEMno importer
SVC·20memory_embeddings.py169memory embedding helpersno importer
SVC·21code_navigation.py426CodeNavigatorroute-local
SVC·22vector_store.py368VectorStore (pre-FAISS)superseded
Register B — routes/ and models/
Call no. Module Prefix Endpoints Mounted
RTE·01search.py/search3yes
RTE·02repos.py/repos7yes
RTE·03chat.py/chat4yes
RTE·04navigate.py/navigate7yes
RTE·05embeddings.py/embeddings4yes
RTE·06embed.py/embed1yes
RTE·07files.py/files2duplicate of /repos
RTE·08index.py/index1placeholder body
RTE·09memory.py/memory7stub backend
RTE·10github.py/webhook + /repos4not included
MDL·01models/schemas.py9 modelsyes
MDL·02models/repo_schemas.py8 modelsyes
MDL·03models/memory_schemas.py16 modelsstub-backed

00.4Startup: what lifespan() builds

Nothing is constructed at import time. The whole object graph is assembled once inside the async context manager at main.py:69, wrapped in a single try/except that appends to app_state.startup_errors and lets the process continue in a degraded state (main.py:245-249). This is why /health can report "degraded" rather than refusing to start.

Order of construction inside the FastAPI lifespan handler A vertical sequence of thirteen numbered construction steps running down the left, each annotated with the line number in main.py where it occurs. Arrows on the right show which object each step feeds into, converging on the HybridSearchService and the CrossRepoSearch object that the search route reads from app.state. A side branch shows GPU detection choosing between CUDA, MPS and CPU. A dashed branch shows the optional cloud embedding configuration path taken only when EMBEDDING_PROVIDER is not local and an API key is present. main.py:69 — async lifespan(app) — construction order 1 · detect_gpu() → cuda | mps | cpu :79 2 · EmbeddingService(model, gpu, cache) :102 3 · _load_model() → real dimensions :110 4 · UnifiedEmbeddingService(local, cache) :115 5 · configure(cloud) if provider ≠ local :126 6 · FAISSVectorStore(dims, gpu) :150 7 · ZoektService(index_dir) :161 8 · ASTChunker() :170 9 · CodeIndexer(embed, faiss, zoekt, chunker) :175 10 · get_reranker(prefer_cloud=True) :184 11 · HybridSearchService(0.4 / 0.6) :190 12 · RepoManager(repos_path, indexer) :209 13 · CrossRepoSearch(hybrid, repos) :215 device selection — embeddings and reranker are told separately torch.cuda → "cuda" torch.mps → "mps" otherwise "cpu" EmbeddingService receives use_gpu and resolves the device itself (embeddings.py:86). CrossEncoderReranker defaults to device="cpu" (reranker.py:92) and is never handed the detected device. On a GPU host the local cross-encoder therefore runs on CPU. dimension propagation model.get_sentence_…() FAISSVectorStore(dims) The index width is taken from the model that actually loaded, not from a constant — the fallback chain may land on a narrower model. Switching to a cloud provider later triggers reinitialize(). published on app.state (main.py:228-242) app.state.hybrid_search app.state.cross_repo_search app.state.repo_manager app.state.vector_store app.state.unified_embedding_service app.state.living_summary chat.py does not read app.state; it holds module-level globals set by chat.set_hybrid_search() and chat.set_living_summary() instead. repos.py likewise uses a module global installed by set_repo_manager(). Two injection idioms coexist in the same startup block.
Figure 00.2 — Construction order Steps 1–13 correspond one-to-one with statements in compute_service/main.py:79-218. The right-hand panels record two consequences worth knowing before reading the rest of the catalogue: the cross-encoder is pinned to CPU, and service injection uses two different mechanisms.

00.5Operator surface

scripts/start.sh is the only command-line front door. Its four subcommands are a case statement at the foot of the file; the help text below is reproduced from print_usage().

Terminal 1 · scripts/start.shverbatim from print_usage()
$ ./scripts/start.sh --help
Usage: ./scripts/start.sh [dev|prod|docker|gpu]

Commands:
  dev     Start in development mode with auto-reload (default)
  prod    Start in production mode with multiple workers
  docker  Start with Docker (CPU)
  gpu     Start with Docker (GPU)

Environment Variables:
  COMPUTE_HOST       Server host (default: 0.0.0.0)
  COMPUTE_PORT       Server port (default: 8080)
  COMPUTE_WORKERS    Number of workers (default: 1 for dev, 4 for prod)
  COMPUTE_LOG_LEVEL  Log level (default: info)

$ ./scripts/start.sh dev
[INFO] Using virtual environment: /app/.venv
[INFO] Python version: 3.12
[WARN] No GPU detected, running on CPU
[INFO] Server starting on http://0.0.0.0:8080
[INFO] API docs: http://0.0.0.0:8080/docs
exec uvicorn compute_service.main:app --host 0.0.0.0 --port 8080 --reload --log-level info
dev
Checks for a virtualenv, asserts Python ≥ 3.11, probes torch.cuda then torch.backends.mps, installs the package if import compute_service fails, then execs uvicorn with --reload.
prod
Same checks minus the venv warning; defaults COMPUTE_WORKERS to 4 and execs uvicorn with --workers. Note that each worker builds its own copy of the service graph, including its own in-process FAISS index.
docker
docker compose --profile cpu up --build — the compute-service container, faiss-cpu, no Zoekt binaries.
gpu
docker compose --profile gpu up --build — the compute-service-gpu container with network_mode: host, an NVIDIA device reservation, and the host's zoekt / zoekt-index binaries bind-mounted read-only.
Reading order

Drawer 01 follows a repository from clone to stored vector. Drawer 02 follows a query in the other direction. Drawer 03 is the arithmetic of ranking, Drawer 04 the on-disk schema, and Drawer 05 the HTTP and browser surface. Drawer 06 is the honest assessment.