Troubleshooting¶
Common failure modes and how to recover.
Install / first run¶
pip install succeeds but from kglite_docs import Corpus fails¶
You installed in editable mode (pip install -e .) without git tags. hatch-vcs derives __version__ from the most recent v* tag. Either tag the local commit (git tag v0.0.1) or run pip install . (non-editable, which baked in the fallback version).
bge-m3 weights take forever to download¶
The ONNX weights are ~2 GB. First-call latency is dominated by this download into ~/.cache/fastembed/. To reuse an existing HuggingFace cache:
export HF_HUB_CACHE=/path/to/your/huggingface/hub
export FASTEMBED_CACHE_PATH=/path/to/your/huggingface/hub
If the download keeps failing or partial-completing, delete the cache directory and retry — hf_hub_download is idempotent.
Warning: You are sending unauthenticated requests to the HF Hub¶
Cosmetic — HuggingFace warns when no HF_TOKEN is set. The download still works. Set HF_TOKEN to silence it and avoid future rate-limit headaches.
Ingest¶
UnsupportedFormatError: unsupported format: 'rtf'¶
You hit a format we don't parse. Either:
- Pass format="md" (or another supported format) explicitly to hint a parser, or
- Convert the file with pandoc (pandoc x.rtf -o x.md) and ingest the result.
Supported extensions: pdf, docx, pptx, md, markdown, html, htm, txt, plus image formats (png, jpg, jpeg, tif, tiff, webp, bmp).
Ingest is slow on long-text chunks¶
bge-m3 embed time scales with sequence length. Pages with very long paragraphs (close to the 8192-token cap) take seconds each on CPU. Options:
- Enable a GPU execution provider (CoreMLExecutionProvider on macOS, CUDAExecutionProvider on Linux) when constructing the embedder.
- Lower the chunker's target_tokens (default 512) so each chunk is shorter.
- Run ingest jobs as background tasks; the embedder cool-down keeps subsequent calls warm for 15 minutes.
Re-ingesting a modified file is silent — old chunks remain¶
By design. Document.id is the sha256 of the file bytes — if the file changed, you'll get a new Document node alongside the old one. The old document's summaries don't auto-update; check enrich.mark_stale_for_doc() if you want to flip stale summaries to verification_status='stale' after the new ingest.
OCR¶
list_pending_ocr returns rows with image_error: source file missing¶
The Document.path recorded at ingest time no longer points at the file on disk. The OCR loop can't render without the source. Two recovery paths:
- Restore or relocate the file to the recorded path.
- Re-ingest the file from its current location — that creates a fresh
Document(new path), and OCR will work against it. The old broken-path document remains but won't OCR.
kglite-docs ocr-do fails on every page¶
Most common cause: your --agent-cmd doesn't actually call a vision-capable model with the image. The CLI requires {image} in the template, but it doesn't check the agent actually reads it. Debug with --dry-run first:
Then run a single page to confirm the agent's output:
A passing OCR returns markdown of > 0 chars on stdout with exit code 0. Empty stdout or non-zero exit is logged as a skip.
Storage¶
Corruption / unable to open a .kgl file¶
kglite's .kgl is a memory-mapped binary. If the file is truncated mid-write (Ctrl-C during save(), disk full, kernel panic), it may not reload. Recovery:
- Best case — you have a recent
.kgle(embeddings) export.kglite.load(other.kgl)+g.import_embeddings('snap.kgle')recovers vectors against a rebuilt graph. - Otherwise — re-ingest.
Document.idis content-keyed, so re-runningingest_dir(src)reproduces every doc deterministically. Summaries/tags/reviews are lost; the underlying data isn't.
File grows much faster than expected¶
Each chunk holds: - ~4 KB text (typical) - 1024 × 4 B = 4 KB embedding - ~1 KB schema overhead
So ~9 KB / chunk. A 16-doc corpus with ~50 chunks/doc is ~7 MB; 1k docs are ~450 MB. If you're seeing 10x that, check embedding_diagnostics() — every property could have its own store and a stale one might be lingering:
for row in store.g.embedding_diagnostics():
if row["status"] == "embedded" and row["text_column"] != "text":
store.g.remove_embeddings(row["node_type"], row["text_column"])
Concurrency¶
ReviewConflict: agent 'b' can't claim ticket held by 'a'¶
Two agents tried to grab the same ticket. By design — only one agent can hold an in_review ticket at a time. Either unclaim_review (if you're holder a) or claim_next_review to pick the next free one.
Multiple processes writing to the same .kgl¶
Don't. kglite is single-writer per file. Two processes calling c.save() against the same path will produce a corrupted file. Designs that need concurrent writers:
- Read-many, write-one: route all writes through one long-lived process (the MCP server). Other processes open the
.kglread-only. - Sharded: split the corpus by document — one
.kglper source, one writer process per shard.
Multi-process write coordination via kglite-level transactions is on the upstream roadmap.
MCP¶
Claude doesn't see kglite-docs tools¶
Check that:
kglite-docs-mcp --db kb.kglruns cleanly from a shell (no traceback before the MCP server boots).- The MCP config in Claude Code points at the right command: Use an absolute path — Claude's working directory may not be yours.
kglite-docsis installed (mcp+mcp-methodsare core deps, no extras needed):pip install kglite-docs.
MCP search returns hits, but text is missing from one of them¶
vector_search returns ids + scores reliably; we re-join via Cypher to attach text / page / doc_id. If a chunk is just embedded but not in the node store (e.g. mid-OCR), the join misses. Usually self-correcting on the next save+reload.
Embeddings¶
"set_embeddings: 'skipped': N" warning¶
Means some of the ids in the dict didn't resolve to nodes in the current graph. Common causes:
- Re-ingesting an old
.kgleagainst a graph with new ids. - Race between two writers (shouldn't happen — see Concurrency).
- Pre-0.10.4 kglite (id-index bug — upgrade to
kglite>=0.10.4).
How do I move the bge-m3 model to a different location?¶
from kglite_docs.embed import make_embedder
embedder = make_embedder(cache_dir="/my/path/hub")
corpus = Corpus.open("kb.kgl", embedder=embedder)
Or set the env var before anything else imports the embedder:
Still stuck?¶
- Check
docs/architecture.mdfor what should happen at each stage. - Crank logging:
logging.getLogger("kglite_docs").setLevel("DEBUG")pluslogging.getLogger("kglite.mcp_server").setLevel("DEBUG")if running the MCP server. - File an issue at https://github.com/kkollsga/kglite-docs/issues with the
minimal repro plus the output of
kglite-docs --version, the kglite version (python -c "import kglite; print(kglite.__version__)"), and your OS / Python version.