Skip to content

DocStack

A self-hosted document pipeline: OCR, a local LLM, and an entity graph that builds and resolves itself. No document ever leaves the house.

production2026 – presentPython · Docker · Paperless-ngx · Ollama · Postgres
Entity graph in Obsidian
Entity graph in Obsidian

Runs entirely on my own hardware6 entity types with deterministic resolutionBilingual OCR, French and EnglishExposes an MCP server for agent use

The problem

A document management system tells you where a file is. It does not tell you that three of them mention the same company under two spellings and a legal suffix.

Every product that does understand the content wants the content: uploaded, indexed, and stored somewhere else. For personal paperwork that is the wrong direction.

What it does

Documents land in Paperless, get converted to Markdown, enriched by a local LLM, and written into an Obsidian vault as a note plus an entity graph. Tags and a generated summary are written back to Paperless.

The graph is queryable: a GraphRAG endpoint answers questions with context assembled from the entities, and the whole thing is exposed as an MCP server so an agent can use it as a tool.

Ingestion
Webhook, plus a 5-minute catch-up poll
Model
llama3.1:8b on a local GPU
Vault
Obsidian, replicated by Syncthing

Architecture

01

Paperless-ngx

Ingestion, OCR in French and English, and the document store. The source of truth for the file itself.

02

Markdown conversion

Docling first, MarkItDown as a fallback, and Paperless' own OCR text as a last resort. Three layers, because conversion is where pipelines die.

03

Local LLM enrichment

Ollama with a pydantic schema and forced JSON output. Structured extraction, not free text that has to be parsed afterwards.

04

Entity graph

A closed six-type ontology with deterministic resolution, written into an Obsidian vault and fed back to Paperless as tags, always as a union, never overwriting.

PAPERLESSDOCLINGOLLAMAGRAPHBACK TO PAPERLESS

Every stage runs on hardware I own

Under the hood

01Entity resolution is mostly not machine learning

Matching “M. Jean Dupont”, “Jean DUPONT” and “Dupont Jean” is a problem of accents, titles, and legal suffixes long before it is a problem of embeddings. The resolver normalises deterministically, uses fuzzy matching only as a tiebreak, and injects the entities it already knows into the prompt so the model stops inventing new spellings. Pairs it is unsure about go to a review file rather than being merged silently.

02A model that is switched off must not mean a failed job

The GPU node is a desktop machine that is not always on. Jobs whose model is unreachable wait, with backoff, and resume when it comes back. They never fail. That single decision is the difference between a pipeline that survives a power cut and one that needs a human every time.

03Only one writer may touch the tags

Two components enriching the same document will each add their own tags, and neither will be wrong. The rule is that exactly one system writes, and enrichment is a union so it can never destroy a correction made by hand.

Gallery

An enriched note
An enriched note
Jobs in flight
Jobs in flight

Try it

This one cannot be opened up, and that is the point.

The whole premise is that the documents stay on hardware I control. Publishing an instance would contradict the only feature that matters. What is on this page instead is the pipeline, the resolution logic, and the failure modes, which is the part worth reading anyway.

Next projectHealth Docs