Architecture
Notes go in through a watcher and a job queue; questions come back as citations with line ranges. Each box names the file that implements it.
On a narrow screen, read the table of every part instead of the drawings.
The two flows
Retrieval, citations and eval need only Postgres and Ollama for embeddings. A language model adds the written answer in ask. It runs under Docker Compose or natively with systemd units; a GPU is used through Vulkan when /dev/dri exists and is never required. Source: core/README.md, core/docs/running.md.
The components behind them
The same system, grouped by layer. Read it top to bottom: each layer calls the one below it, and the bottom row is used by all of them. Every box is a file or a package under core/src/knowman/.
The arrow on the left runs from the layer that calls to the layer it depends on. Config, the access guard and logging are drawn last because every layer above reads them. Source: core/src/knowman/, core/README.md, core/docs/running.md.
Every part
Names in the Part column are translated. Names in the Code column are the real files and settings.
| Part | Code | Status | What it does |
|---|---|---|---|
| Notes (.md) | CORPUS_PATH folder | real | Your Markdown folder. Never modified by indexing; only the agent writes to it. |
| Watcher | watcher.py | real | Turns each added, edited or deleted .md file into a job. |
| Job queue | store.py | real | Jobs are rows in Postgres: ingest_path and delete_path, with status pending, processing, done or failed. |
| Worker | worker.py | real | Claims one job at a time and runs it; a failed job is recorded, not retried silently. |
| Chunker | chunking.py, ingest.py | real | Splits at blank lines, keeping each chunk's line range; ingest.py embeds and stores the chunks. |
| Embeddings | embeddings/base.py, embeddings/ollama.py | real | Ollama turns each chunk into a vector (model set by EMBEDDINGS_MODEL). One interface, one implementation. |
| Postgres + pgvector | store.py | real | Stores chunks, jobs, eval runs and traces. HNSW cosine index on the embedding column. |
| Embed query | retrieval.py | real | Embeds the question with the same model. |
| Nearest chunks | store.py | real | Returns the k closest chunks (default k from RETRIEVAL_DEFAULT_K). |
| Distance filter | retrieval.py | real | Drops chunks farther than RETRIEVAL_MAX_DISTANCE. An empty result is the explicit negative. |
| Citations | retrieval.py | real | Each citation carries path, line start, line end, text and distance. |
| Formatting | formatting.py | real | Renders a citation as path:Lstart-Lend plus its passage, and an empty result as "No evidence found for that query." |
| LLM answer | ask.py, llm/*.py | optional | Only when LLM_PROVIDER is set (Ollama, Claude, OpenAI or Grok) and there is evidence. search and eval never need it. |
| Prompts | prompts/ask_v1.txt | optional | The template the answer is written with; PROMPT_VERSION selects the file, so a new wording is a new file, not a code change. |
| Eval | eval.py, evaldata/dataset.json | real | Scores a labeled dataset against retrieval; no model judges another model. |
| Agent | agent.py | optional | knowman write: a LangGraph agent over Ollama that can search the notes and write a new one. |
| CLI | cli.py | real | db-init, ingest, search, ask, eval, worker, watch, mcp, write. |
| HTTP API | api.py | real | Search, ask, eval, index jobs, status; optional API_TOKEN and rate limit. |
| Access guard | access.py | real | The token check and the per-minute request counter behind those two options. Both are off unless set, and neither knows what search or ask do. |
| MCP server | mcp_server.py | real | Two read-only tools over stdio: search and ask. |
| Dashboard | dashboard.py | real | One page served at /dashboard: index vs disk, job queue, last eval, recent activity. |
| Config | config.py | real | One Settings object read from the environment and .env; every module asks it instead of parsing variables. |
| Logging | logging_setup.py | real | JSON logs on stdout, one record per line, so the containers and the systemd units log the same shape. |