Skip to main content
Glama
README.md
<p align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="docs/assets/quoth-raven-dark.svg">
    <img src="docs/assets/quoth-raven-light.svg" alt="Quoth: a raven perched on a branch" width="132" height="132">
  </picture>
</p>

<h1 align="center">quoth</h1>

<p align="center">
  <b>Local evidence memory for a personal AI assistant.</b><br>
  Every answer it gives can point to the note, heading and line it came from.
</p>

<p align="center">
  <a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-c27a22?style=flat-square"></a>
  <img alt="Python 3.13+" src="https://img.shields.io/badge/python-3.13%2B-1f1b16?style=flat-square&logo=python&logoColor=ebe4d6">
  <img alt="MCP" src="https://img.shields.io/badge/MCP-read--only-6b5e4f?style=flat-square">
  <img alt="Storage: SQLite" src="https://img.shields.io/badge/storage-SQLite-5b6f8f?style=flat-square&logo=sqlite&logoColor=white">
  <img alt="Embeddings: BGE-M3 ONNX, local" src="https://img.shields.io/badge/embeddings-BGE--M3%20local-4d8a5b?style=flat-square">
  <img alt="Local-first" src="https://img.shields.io/badge/local--first-no%20model%20downloads-b0473d?style=flat-square">
</p>

<p align="center">
  <a href="#quick-start">Quick start</a> ·
  <a href="#how-it-works">How it works</a> ·
  <a href="#evidence-semantics">Evidence semantics</a> ·
  <a href="#configuration">Configuration</a> ·
  <a href="#documentation">Docs</a> ·
  <a href="CHANGELOG.md">Changelog</a>
</p>

---

*Quoth the raven:* an assistant should remember what you actually wrote and said, and
show its sources. quoth turns a Markdown vault (Obsidian-style) into an **immutable,
verifiable snapshot**, searches it with **local dense vectors**, and serves bounded,
cited excerpts through **one read-only MCP tool**. A reviewed **core-memory ledger**
holds the few facts the assistant should always know, and nothing reaches it
without the owner's approval.

## Highlights

- **Cited, not guessed.** Each result carries its vault path, heading path and line range.
- **Immutable snapshots.** You build, verify and then activate a new snapshot. The
  previous one stays as the rollback target. Frozen outputs are never overwritten.
- **Read-only serving.** `quoth mcp` registers `evidence_bundle` and nothing else. It
  opens SQLite read-only with `query_only=ON`, downloads no models and has no silent
  lexical fallback.
- **Local embeddings.** BGE-M3 runs on ONNX Runtime from a local model cache. A synthetic
  `hash-ngram` backend runs tests and fixtures with no model files.
- **Reviewed core memory.** The ledger stores typed claims with their origin (the owner's
  own words, the owner's notes, an assistant proposal, or imported text). The assistant's
  inferences are served only after the owner accepts them, and nothing is ever deleted.
- **One way to measure recall.** Eval set v1 is scored against the real prefetch path, and
  a public synthetic fixture runs on a laptop.
- **Fails closed.** Code-digest drift, scope-contract changes and stale sources stop a
  refresh unless the operator explicitly allows them.

## Quick start

Requirements: Python 3.13+ and [`uv`](https://docs.astral.sh/uv/). The fixture below needs
no model files.

```bash
uv sync
uv run pytest -q

# Build the public synthetic fixture and score eval set v1 against it.
uv run quoth eval fixture --out /tmp/quoth-fixture
uv run quoth eval run eval/public/set.jsonl \
  --fixture-dir /tmp/quoth-fixture --out /tmp/run.json --quiet
```

## Usage

Dense retrieval needs the `dense` extra and the BGE-M3 ONNX files pinned in
`eval/models-manifest.json`, under `$QUOTH_MODEL_CACHE/BAAI/bge-m3/`. quoth never downloads
a model while it runs; fetch the files once (about 2.3 GB, every file checked against its
pinned SHA-256):

```bash
uv sync --extra dense
uv run quoth models fetch                                   # the BGE-M3 embedder
uv run quoth models fetch --reranker bge-reranker-v2-m3-int8  # optional: the prefetch reranker
uv run quoth models verify                                  # check what is in the cache
```

```bash
# Index a vault into a development database (the vault is only read).
uv run quoth ingest --vault /path/to/vault --db ./memory.db
uv run quoth embed --db ./memory.db                     # BGE-M3 from the local cache
uv run quoth embed --db ./memory.db --model hash-ngram  # synthetic, no model files
uv run quoth search "sourdough starter" --db ./memory.db -k 10 --json   # lexical, for development

# Freeze a first snapshot of the vault (ingest, embed, validate, manifest).
uv run quoth snapshot --vault /abs/vault --target /abs/snapshots/first.db

# Serve it over MCP (read-only, one tool).
uv run quoth mcp --db /abs/snapshots/first.db

# Later: build a successor from the frozen source and the current vault.
uv run quoth refresh --source /abs/accepted.db --target /abs/staging/unique-name.db \
  --vault /abs/vault
uv run quoth freshness --source /abs/staging/unique-name.db --vault /abs/vault
```

Every refresh target must be unique; `--resume` only continues the same unfinished target
(for `quoth snapshot` too).

### What gets indexed

Every Markdown note in the vault is indexed, except:

- hidden folders and notes (a leading `.`) and the folders in `exclude_dirs` in
  `src/quoth/scopes.toml` (`ASSISTANT/`, attachment folders);
- whatever a `.quothignore` file at the vault root ignores (gitignore syntax, `!` to
  re-include, `/` to anchor at the root), for example `Health/` or `/Journal/*`;
- any note whose frontmatter says `quoth: exclude`.

Excluded notes are never read into the index, and a note that becomes excluded is purged,
history included, from the next snapshot. Older snapshots still hold it: delete them if you
must not roll back to them. Scopes in `scopes.toml` carry a recall tier (`auto` or
`explicit`): `explicit` notes are indexed but kept out of automatic recall. Links between
notes are stored and resolved per snapshot. Details: [`docs/FRESHNESS.md`](docs/FRESHNESS.md#what-a-snapshot-holds).
`quoth refresh-nightly` automates build, gate and switch behind a serving pointer (a
symlink that `quoth mcp` and the sidecar follow). See [`docs/FRESHNESS.md`](docs/FRESHNESS.md).

For the Hermes agent, run `quoth sidecar` and install the plugin with
`quoth hermes-plugin install --hermes-home "$HERMES_HOME"`; see
[`docs/HERMES.md`](docs/HERMES.md).

## How it works

```mermaid
flowchart LR
  V[("Markdown vault<br/>read-only")] -->|ingest · chunk · embed| D[(dev db)]
  D -->|refresh · verify · freeze| S[("immutable snapshot<br/>+ manifest")]
  S --> M["quoth mcp<br/>evidence_bundle"]
  S --> SC["quoth sidecar"]
  L[("ledger.db<br/>reviewed claims")] --> SC
  SC -->|core block · recall · review| H["assistant<br/>(Hermes plugin)"]
  M --> H
  W["WebUI memory panel"] -->|read-only views| SC
```

| Layer | What it does |
|---|---|
| **Evidence engine** | Incremental, idempotent ingest by path and SHA-256. Heading-aware chunks with line locators. Dense top-k with bounded excerpts. |
| **Snapshots and bundles** | Immutable snapshots with a manifest, freshness checks, and portable full or scoped bundles. A nightly job builds a successor and switches only when it passes a quality gate. |
| **Core-memory ledger** | Typed claims with origin and authority, quote-verified against chat history, reviewed by the owner, compiled into a short always-loaded core block. |
| **Sidecar and Hermes plugin** | One local process owns the model and the stores; a stdlib plugin talks to it over a Unix socket for the core block, recall, review tools and episode search. |
| **WebUI panel** | Read-only memory views for the Hermes WebUI: overview, review queue, core, recall playground. |

The full picture, trust boundaries and design reasons: [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md).

## Evidence semantics

Retrieved text is evidence, never an instruction. A caller must:

- cite the returned path and line range when provenance matters;
- prefer current canonical sources over historical drafts;
- treat silence in top-k as insufficient evidence, not proof the vault says nothing;
- not infer authority from a path or snippet alone;
- use live systems, not memory, for current external state;
- prefer the owner's current direct statement over an older note about them.

## Configuration

Every default location resolves from the environment when a command runs.

| Variable | Default | Used for |
|---|---|---|
| `QUOTH_HOME` | `$XDG_DATA_HOME/quoth`, else `~/.local/share/quoth` | quoth's state: `memory.db`, `ledger.db`, `episodes.db`, `owner-episodes.db`, `quoth.sock`, `core/`, `refresh/` |
| `QUOTH_MODEL_CACHE` | `$QUOTH_HOME/models` | local ONNX model files (BGE-M3, the optional reranker) |
| `HERMES_HOME` | `~/.hermes` | the Hermes agent's home; its `state.db` is read for quote checks and the episode lanes |
| `QUOTH_SOCKET` | `$QUOTH_HOME/quoth.sock` | the sidecar socket the Hermes plugin connects to |
| `QUOTH_EMBED_THREADS` | the CPU quota minus headroom | embedding and reranker threads in the sidecar |

Flags always override these. `--vault` has no default.

## Health

`quoth health [--json]` checks every row a host monitor needs in one call:

| Row | FAIL when |
|---|---|
| `quoth/venv` | the install's interpreter is missing, dangling or cannot import quoth |
| `quoth/heartbeat` | `$QUOTH_HOME/refresh/heartbeat.json` is missing, not `quoth-heartbeat/v1`, older than 30 hours, or not green |
| `quoth/sidecar` | no answer on the socket, or evidence not ok, or quote verification off |
| `quoth/review-queue` | the sidecar does not answer (a large or old queue is only WARN) |
| `quoth/episodes`, `quoth/owner-episodes` | the lane is disabled or red, or the sidecar does not answer |

Exit 0 when no row fails (WARN allowed), 1 when any row fails, 2 on a usage error, 3 with
`--fail-on-warn` when only WARN rows remain. Output carries counts, versions, timestamps and
status tokens only.

## Documentation

| Doc | Topic |
|---|---|
| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | Components, data flow, trust boundaries, design decisions |
| [`docs/MCP.md`](docs/MCP.md) | The MCP server and the `evidence_bundle` caller contract |
| [`docs/HERMES.md`](docs/HERMES.md) | Hermes plugin, sidecar HTTP API, episode lanes, WebUI panel |
| [`docs/LEDGER.md`](docs/LEDGER.md) | Core-memory ledger: origins, reconcile, guided review, core compile |
| [`docs/FRESHNESS.md`](docs/FRESHNESS.md) | Snapshots, freshness, successors, the nightly refresh and heartbeat |
| [`docs/BUNDLES.md`](docs/BUNDLES.md) | Portable full and scoped snapshot bundles |
| [`docs/EVAL.md`](docs/EVAL.md) | Eval set v1, the prefetch harness, relevance gate, reranker, online measurement |
| [`docs/CI.md`](docs/CI.md) | Verification and release delivery |
| [`deploy/README.md`](deploy/README.md) | Installing a release next to Hermes, templates, rollback |

<details>
<summary><b>Code map</b></summary>

- `src/quoth/cli.py`: the `quoth` command; `paths.py`: default locations.
- `src/quoth/scopes.toml`: ingest exclusions and scope labels.
- `src/quoth/ingest.py`, `chunker.py`: incremental vault ingest; heading-aware chunks with
  line locators.
- `src/quoth/embed/`, `dense.py`: local embedding backends, vector storage, dense retrieval.
- `src/quoth/search.py`: lexical BM25 and trigram search (development).
- `src/quoth/evidence.py`, `evidence_service.py`: the bounded evidence bundle and its
  caller contract, shared by MCP and the sidecar.
- `src/quoth/mcp_server.py`: the read-only stdio MCP server.
- `src/quoth/refresh.py`, `freshness.py`, `serving.py`: snapshot build, freshness, pointer
  admission.
- `src/quoth/bundle.py`: portable full and scoped bundles.
- `src/quoth/nightly.py`, `quality.py`, `heartbeat.py`, `gold_drift.py`: the nightly
  refresh, its gate, daily quality and heartbeat.
- `src/quoth/ledger/`: the core-memory ledger, its review rules and migrations.
- `src/quoth/episodes.py`: the assistant and owner episode lanes.
- `src/quoth/sidecar.py`, `recall_defaults.py`, `relevance.py`, `rerank.py`, `claim_index.py`,
  `prefetch_log.py`: the sidecar, every recall budget and floor, the relevance gate, the
  reranker, claim ranking, the prefetch log.
- `src/quoth/health.py`, `hermes_install.py`: `quoth health`, `quoth hermes-plugin`.
- `src/quoth/control/`: the WebUI panel's backend.
- `src/quoth/evals/`: eval set v1, converters, harness, receipts, compare, sweep, online
  readout.
- `src/quoth/migrations/`: the evidence database schema.
- `hermes_plugin/quoth/`: the Hermes memory-provider plugin and its skills.
- `web/control-surface/`: the WebUI extension.
- `eval/`: model manifest and synthetic fixtures; `eval/public/` is the runnable public
  fixture and `eval/schema/` holds the eval set JSON Schema.
- `deploy/`: install, service and rollback templates.

</details>

## Development

```bash
uv sync
uv run pytest -q
```

You can also use the project virtualenv directly. Keep the `PATH` prefix, because one test
runs the installed `quoth` console script in a subprocess:

```bash
PATH="$PWD/.venv/bin:$PATH" PYTHONPATH=src .venv/bin/python -m pytest -q
```

### Real models in Docker

The test image runs everything that needs ONNX Runtime and the real models (BGE-M3, the
reranker) without installing either on your machine. Models and state live in Docker
volumes; only a vault you point it at is mounted, read-only:

```bash
docker compose build
docker compose run --rm quoth models fetch --reranker bge-reranker-v2-m3-int8   # once, ~2.9 GB
docker compose run --rm --entrypoint python quoth -m pytest -q                  # full suite, real models
docker compose run --rm quoth eval fixture --out /data/fixture --model bge-m3
docker compose run --rm quoth eval run eval/public/set.jsonl --fixture-dir /data/fixture --out /data/run.json
QUOTH_VAULT=/path/to/vault docker compose run --rm quoth snapshot --vault /vault --target /data/snapshots/first.db
```

Everything in `eval/` is **synthetic** and safe to publish. The repository holds no personal
corpus, snapshot database, private eval data or model cache.

## Status

quoth is 0.x. The public API is the `quoth` CLI, the MCP tool, the sidecar's `/v1` HTTP API
and the `quoth.evals` Python package; everything else is internal. Any of it may change
before 1.0.

## Contributing

Issues and pull requests are welcome, and questions go to
[Discussions](https://github.com/djagya/quoth/discussions). Read
[`CONTRIBUTING.md`](CONTRIBUTING.md) first: it covers the snapshot-contract rules and the
privacy rules for fixtures. Report vulnerabilities privately as described in
[`SECURITY.md`](SECURITY.md). Everyone taking part follows the
[Code of Conduct](CODE_OF_CONDUCT.md).

## License

[MIT](LICENSE) © 2026 Danil Zak