Skip to content

Document libraries

Pass a directory to vera search to search multiple .vera files as one corpus. Results are ranked together and attributed to their source archive.

Search a flat directory

vera search "./library" "termination clause" --top-k 10

Only .vera files directly inside the directory are discovered by default.

JSON results add:

  • file on each result, identifying its .vera archive;
  • a top-level index object describing whether a collection index was used;
  • a top-level skipped_files array containing the absolute path, category (invalid or incompatible), and reason for each rejected archive.

Keep citations separated by archive when comparing sources.

Malformed archives do not abort folder search or inspection. Direct fallback search validates each discovered archive and searches the valid subset. VeraCorpus.inspect() similarly reports discovered_file_count, the valid file_count, skipped, and skipped_files.

Search nested directories

Without a collection index, enable recursive discovery explicitly:

vera search "./library" "termination clause" --recursive --json

Exclude paths or names with repeatable patterns:

vera search "./library" "termination clause" \
  --recursive \
  --exclude "archive/**" \
  --exclude "*.draft.vera" \
  --json

Limit discovery to matching paths with --include. If any include is present, a file must match at least one include and no exclude:

vera search "./research" "adding capacity" \
  --recursive \
  --include "companies/GRID/archives/**" \
  --json

Patterns are matched against forward-slash relative paths and individual path components. Directory symlinks and archive symlinks are not followed.

Build a collection index

Direct corpus search opens and searches individual archives. For a larger or frequently searched library, build a local index:

vera index build "./library" --recursive --json

The index stores its discovery settings, file manifest, unified keyword index, chunk references, and per-model vector matrices under:

library/.vera-index/

The index is a rebuildable local acceleration artifact. It does not modify the .vera files, and the archives remain independently portable. It persists across process and app restarts. Opening a library does not rebuild its index; only an explicit build or update writes a new generation. A successful rebuild deletes every other generation directory under .vera-index/generations/.

Use the same exclusion patterns while building:

vera index build "./library" \
  --recursive \
  --exclude "archive/**" \
  --include "companies/GRID/archives/**" \
  --json

vera index update keeps the saved include and exclude patterns.

Search an indexed library

Search the directory normally:

vera search "./library" "termination clause" --json

VERA automatically uses a fresh index. You do not need to repeat --recursive or --exclude; the saved index settings control discovery.

The response reports:

{
  "index": {
    "used": true,
    "exists": true,
    "fresh": true,
    "reasons": []
  }
}

Treat index.used, not merely index.exists, as the indication that indexed search was active. When a fresh index is used, top-level skipped_files copies those omissions with absolute paths. The nested index object is the full status report, so index.skipped_files keeps the relative paths, category (invalid or incompatible), and reasons recorded at build time. Inspection uses this manifest and does not reopen archives that the fresh index already rejected.

VeraCorpus.inspect_summary() also reads file, page, chunk, and model metrics from a fresh index without reopening any source archives. With a missing or stale index it returns discovery counts and marks summary_complete false; call VeraCorpus.inspect() when an explicit deep validation scan is required.

Check and update freshness

vera index status "./library" --json

An index becomes stale when archives are added, removed, moved, replaced, or changed, or when an index artifact is missing or incompatible. A stale or missing status still prints JSON but exits with status 1.

Rebuild using the saved discovery settings:

vera index update "./library" --json

Run update after changing the library contents.

Safe fallback

If the index is missing or stale, corpus search falls back to direct file search. The JSON response sets index.used to false and lists the reason:

{
  "index": {
    "used": false,
    "exists": true,
    "fresh": false,
    "reasons": ["library files were added, removed, or moved"]
  }
}

This preserves correctness while making the performance change visible.

When --where uses a chunk-only metadata key that is not stored in the collection index, VERA also falls back to per-file search even if the index is otherwise fresh. index.used is false and index.reasons includes chunk metadata filter not in collection index. Caller --metadata tags are stored on every chunk, so they match with or without an index. Convert-owned archive headers such as source_file_name (and citation columns already in the index) can filter indexed search before top_k. Do not post-filter the JSON results array.

vera index status --json also reports the active generation and timestamps, database/vector/total storage, indexed_chunks versus source_chunks coverage, discovery settings, skipped files, and a model_groups array with dimensions and document/chunk counts. CLI status hashes archives and sets verified_at. Directory search and the desktop badge compare size/mtime only (verified_at is null) unless Inspect refreshes with hashes. These fields power the desktop app's Library Info view. On current builds the two chunk counters match; older indexes may report indexed chunks as the source total until rebuilt.

Mixed embedding models

A library may contain archives created with different embedding models. VERA queries each model group with its recorded model and rank-fuses the groups. The runtime must have the dependency required by every model it needs to query. Archives created with Sentence Transformers therefore require the ml extra at search time.

If an indexed semantic or hybrid search cannot load a group's model, or the runtime model's dimension differs from the indexed dimension, VERA skips that semantic group and reports it:

{
  "skipped_semantic_model_groups": [
    {
      "model_name": "sentence-transformers/all-MiniLM-L6-v2",
      "dimension": 384,
      "error": "ImportError: No module named 'sentence_transformers'"
    }
  ]
}

This array is empty for keyword-only searches. Hybrid search can still return keyword matches, but its semantic coverage is incomplete whenever the array is non-empty.

Corpus ranking is not identical to single-document ranking. Direct semantic search can merge raw cosine scores for archives sharing a model, while mixed models and keyword/hybrid corpus results require rank fusion. Indexed hybrid search also fuses ranked semantic and keyword lists. Use scores to order one result set; do not compare score values across single-document, direct-corpus, and indexed-corpus searches.

Python API

Search a library directly:

from vera_doc import VeraCorpus

with VeraCorpus.open("./library", recursive=True) as corpus:
    results = corpus.search("termination clause", mode="hybrid", top_k=10)
    for result in results:
        print(result.file, result.page_start, result.text[:100])

VeraCorpus.open(..., allow_empty=True) is available to applications that need to represent an empty library during setup. The default remains strict and raises FileNotFoundError when discovery finds no .vera archives.

The corpus opens source archives lazily and uses a bounded handle cache. See Python API and the detailed collection index design.