#3997·LightRAG

[Feature Request]: vector/source count reconciliation in kg_integrity_repair, repaired through rebuild_vdb

Author: danielaskddCreated Sep 17, 2026Updated Sep 17, 2026
Labelsenhancementtracked

Do you need to file a feature request?

  • I have searched the existing feature request and this feature request is not already filed.
  • I believe this is a legitimate feature request, not just a question or bug.

Feature Request Description

A vector storage is an index over data held elsewhere. When the two lose sync — an interrupted run, a partial failure, a code path that wrote one side and not the other — retrieval degrades silently: queries return fewer results, nothing errors, and nothing reports it.

The startup gate added in #3991 only catches the total case (is_empty() on the index while its source is non-empty), because that is the shape a changed embedding model produces on a named-container backend. Partial divergence is deliberately out of its scope, and the reasoning is recorded in docs/design/VectorSpaceProvenance.md under Accepted residues: the counts are not required to be equal (AGENTS.md Consistency without transactions accepts residues where one store retains what another dropped), a count mismatch is not the negative verdict a fail-closed startup check may refuse on, and exact counting means a full enumeration that must not run on a hot path.

That makes the offline audit the right home. This request is to put it there.

Additional Context

What already exists

Most of the machinery is in the tree, which is why this is mostly wiring:

piece where covers
check_vdb_consistency(graph, entities_vdb, relationships_vdb) lightrag/tools/rebuild_vdb.py per-record existence, graph → VDB, entities + relations only
enumerate_kv_keys(kv) lightrag/tools/rebuild_vdb.py key enumeration for all five KV backends (JSON / Redis / PG / Mongo / OpenSearch)
rebuild_entities_vdb / rebuild_relationships_vdb / rebuild_chunks_vdb lightrag/tools/rebuild_vdb.py the repair, per target
audit_kg_integrity(rag, apply=...) lightrag/tools/kg_integrity_repair.py full graph enumeration + source_idtext_chunksfull_doc_id attribution

The four gaps

  1. Chunks are not checked. check_vdb_consistency takes entities_vdb and relationships_vdb only, while rebuild_chunks_vdb exists and enumerate_kv_keys already enumerates text_chunks. The check covers two of the three vector targets; the rebuild covers all three.

  2. Only the graph → VDB direction. Its own docstring says so: "stale reverse orphans (records present in the VDB but absent from the graph) can only be eliminated by a full rebuild." A vector row whose source record is gone is invisible to a forward existence probe — and this is exactly what a count comparison adds over the per-record check, since the two directions have to agree for the totals to match.

  3. kg_integrity_repair does not call any of it. It already enumerates the whole graph and already maps chunks back to their owning documents, so running the census in the same pass costs no extra enumeration — and it can report which documents are short of vectors, which check_vdb_consistency cannot (it reports entity names and relation pairs, capped at MAX_REPORTED_MISSING). "Document X is missing 40 of its 120 chunk vectors" is directly actionable in a way a global delta is not.

  4. Full materialization. check_vdb_consistency uses get_all_nodes() / get_all_edges(), while the bounded iter_labels / iter_edges exist on BaseGraphStorage. Acceptable for a tool that is already offline-only, but worth deciding deliberately if it is to run as part of a larger audit on a big graph.

Proposed shape

  • Extend the census to the third pairing (text_chunkschunks_vdb) and to the reverse direction, so the report states counts on both sides per target, not just forward misses.
  • Surface it from audit_kg_integrity, attributed per document where the mapping allows.
  • Repair reuses rebuild_vdb rather than growing its own writer — the three rebuild_*_vdb functions already drop and rebuild a single target, and RebuildTool already offers partial target selection.
  • Report-only by default, consistent with how kg_integrity_repair treats orphans today (it reports them; removing graph data is left to an operator decision).

Things to get right

  • Some divergence is legitimate and must not be reported as damage. PurgeRecoveryContract.md documents accepted residues for merge and rename where one store is updated before the other. The report should distinguish "this is a known-accepted shape" from "vectors are genuinely missing", or it will train operators to ignore it.
  • Relation ids are not cleanly 1:1. The id is compute_mdhash_id(src + tgt, prefix="rel-") and the graph is undirected, so both orderings must be probed — make_relation_vdb_ids already does this in check_vdb_consistency and any count logic has to account for legacy reverse-order rows rather than counting them as extras.
  • A backend that cannot answer must not be reported as a shortfall. check_vdb_consistency already has this right for the embedding-space refusal case (incompatible), and the same discipline applies to any read that fails: an unreadable container is not an empty one. #3991 added BaseVectorStorage.is_empty() with a fail-loud contract for exactly this reason.

Out of scope

Making this a startup check. See the design-doc section linked above for why; the gate stays on the total case.