Skip to main content
kennis

kennis

A local knowledge and memory management system for people and AI agents.

kennis holds a corpus of documents, a per-workspace context bundle, and a retrieval stack over both that combines BM25 with dense embeddings. It is domain-agnostic: it knows nothing about any particular subject, and domain content is supplied from outside as data rather than code.

Documentation

Full documentation - a tutorial, how-to guides, the reasoning behind each mechanism, and a command reference generated from the code - is built with MkDocs:

uv run --group docs mkdocs serve      # http://127.0.0.1:8000
uv run --group docs mkdocs build      # into site/

Status

Pre-release, published on PyPI as kennis. Built and tested:

  • the corpus, machine-global, with git-backed history, BM25 and dense retrieval over it, and the command line;
  • the context bundle, one per project, committed with it, with its own lexical index and searched alongside the corpus;
  • packs, the format a provider supplies domain content in, and the two sync commands that converge each scope with what is installed;
  • the MCP server, kennis serve, nine tools over stdio, with its description of this machine generated from the installed packs;
  • the graphical interface, kennis gui, serving on loopback with a per-run token, in a native window where the platform provides one.

Cross-machine synchronisation and any packaged application come after that.

The on-disk format may still change without a migration. Every frontmatter model refuses an unknown key rather than ignoring it, so a corpus written by a later version is not readable by an earlier one, and there is no version recorded on a document to say so. That is a deliberate choice about failing loudly, and it is why a corpus built now is a corpus you should be prepared to rebuild.

Install

uv tool install kennis                # or "kennis[mcp]" for the MCP server

PDF conversion is part of the install and is its large part - MinerU brings PyTorch, and on Linux the CUDA libraries, several gigabytes. The install guide says how to take PyTorch's CPU build instead.

Your data

kennis keeps everything on your machine, has no telemetry, and sends a document to a third party only when you set a hosted backend and supply its key. Adding a paper or a site fetches from the host you name, and an agent connected through kennis serve passes what it reads to whatever its model runs on. The full list, with every host, is Your data: where it is kept and where it goes.

From source

git clone git@github.com:kwazzi-jack/kennis.git
cd kennis
uv sync                       # add --extra mcp for the MCP server
uv run kennis --help

To get a kennis on your PATH instead of prefixing every command with uv run, install it as a tool. PDF conversion is part of the install and is its large part - MinerU brings PyTorch:

uv tool install .                     # or
uv tool install "kennis[mcp] @ ."     # with the MCP server

uv tool install links only the requested package's executables, so mineru itself does not land on your PATH - kennis finds it in the environment it was installed into.

Usage

Set up

kennis config init            # guided: embedding, conversion, retrieval, paths
kennis config show            # valid TOML, safe to redirect to a file
kennis config get embedding.backend
kennis config set embedding.backend none

Nothing here is required. With no configuration kennis uses its defaults and KENNIS_EMBEDDING_BACKEND=none gives a lexical-only index, which needs no model download and is the quickest way to try it.

Put something in

kennis corpus init                             # the corpus, and the git repository it is
kennis corpus add -n notes.md --group radio    # a file, or a directory, into notes
kennis corpus add -l arXiv:2101.11270          # a paper, by identifier or DOI
kennis corpus add -d https://docs.astropy.org/en/stable/ --max-pages 20

Three collections, each with a shorthand: -n notes, -l literature, -d docs. They differ in what kennis does besides storing the markdown - literature resolves bibliographic identity and derives a citekey, docs records which project and page a document is.

Adding a site is polite by default: it waits between requests, honours robots.txt, and stops at --max-pages. Twenty pages takes about a minute.

Search it

kennis corpus index                            # build the index; after any batch of adds
kennis search "complex gains"
kennis search "gains" --collection notes --group radio -k 5
kennis search "fits files" --mode bm25 --snippet full
kennis search "complex gains" --scores raw     # the per-leg numbers instead of a level
kennis search "gains" --collection context     # this project's bundle only
kennis search "gains" --collection notes,context

--collection takes a comma-separated list over four scopes - literature, docs, notes and context - and defaults to all. A scope you name is a promise and a scope you did not is not: asking for context outside a project is an error, while a sweep outside one simply does not mention it.

A hit looks like this:

Found 2 in literature, 1 in notes

Literature relevance by cosine similarity
  [1] Calibration of radio interferometers - Gain solutions
      relevance: very high  id=gpfa1o3yad chunk=3
      Complex gains are solved per antenna and per interval ...

Notes relevance relative to the best lexical match
  [1] Calibration conventions
      relevance: very high  id=f4inoeh3gd chunk=0
      We solve gains on a 30 second interval ...

Results are grouped by collection, and that is not only cosmetic. Each group carries the scale its own levels are on. A collection indexed without an embedding backend is banded relative to its own best hit, where one with a dense leg is banded on an absolute cosine - and those two very high above mean different things. Ordering them into one column would state a comparison kennis cannot make. -k applies per group.

id and chunk are what read takes. --mode is hybrid by default and falls back to bm25 against a lexical-only index rather than failing.

The first index on a new machine downloads the embedding model, which kennis says it is doing and keeps in ~/.cache/kennis/models, so it is paid once. With embedding.backend = ollama, a model the daemon does not hold is pulled the same way.

Relevance is measured, not invented. With a dense leg and an embedding model kennis has calibrated, the level is the hit's cosine similarity against cuts measured on a real corpus, so it means the same thing for every query. Against a lexical-only index it is relative to the best hit of that query, and the summary line says which of the two you are reading. --scores raw gives the numbers themselves.

Reading a document:

kennis read 3vgtsow3cl                         # the whole document, as markdown
kennis read "Recipe for-loops.md"              # by filename, title, or citekey
kennis read 3vgtsow3cl --chunks 2:5            # just those chunks
kennis read 3vgtsow3cl > paper.md              # the report goes to stderr, so this works

--chunks is a python slice over the document's own chunks: 3 is one, 0:3 is the first three, 2: runs to the end, -1 is the last. There is no step, because a passage that is not contiguous is not a passage. Without it the document is read from the corpus, so a document that has never been indexed is still readable; with it the passage is stitched out of the index.

Either way stdout is the text and stderr is the provenance, so both forms pipe and redirect cleanly. Redirected, the text is the document byte for byte: colour is dropped when stdout is not a terminal, and no line is padded or re-wrapped.

Code blocks are highlighted where the language is known. A fence that names one (```yaml) is lexed as that. A fence that names nothing is parsed rather than guessed at - if the text is a YAML or JSON document it is highlighted as one, and otherwise it is coloured as a single block and left alone. Guessing was tried and measured: over 262 unlabelled fences it answered MySQL, GDScript, scdoc and Tera Term macro for content that was plainly YAML, so it is not used.

Write something down

kennis remember "QuartiCal needs --input-ms-time-chunk tuned for long tracks"
kennis remember --from jottings.md --title "Jottings"
echo "The array has 64 dishes." | kennis remember

remember writes a note and indexes it in the same command, so what you have just told kennis is searchable straight away - provided the notes collection has already been indexed once. It says which of the two happened.

Knowledge that belongs to one project

kennis context init                                  # a .context/ bundle at the workspace root
kennis remember --context "Calibration runs in four-minute chunks"
kennis remember --context --group decisions --title "Solver" "We use quartical"
kennis context index                                 # BM25 only, offline, milliseconds
kennis context status                                # what it holds, and whether the index is in step

The corpus is machine-global; a bundle belongs to one project and is committed with it, so the knowledge travels with a clone. The index does not travel - it is derived, rebuilt in milliseconds, and specific to whoever built it, so context init gitignores it and a clone runs kennis context index once.

kennis never commits to your repository. Committing .context/ is yours to do.

See what is there

kennis corpus status                           # per collection: how many, and how stale
kennis corpus list --collection notes
kennis corpus tree
kennis corpus history --limit 10
kennis corpus restore <document-id> --commit <hash>

Every command that changes the corpus records a commit, so history is the record of what kennis did and restore puts a document back as it was.

Where things live

what where override
the corpus ~/.local/share/kennis KENNIS_CORPUS_ROOT
a project's context bundle .context/ at the workspace root KENNIS_CONTEXT_DIR
configuration ~/.config/kennis KENNIS_CONFIG_DIR
the log ~/.local/state/kennis/log KENNIS_LOG_DIR
downloaded embedding models ~/.cache/kennis/models -

Every setting can also be given as an environment variable: KENNIS_EMBEDDING_BACKEND, KENNIS_CONVERSION_BACKEND, and so on. The log is always on, and an error tells you where it is.

Requirements

  • Python 3.12 or newer
  • uv
  • git

Licence

GPL-3.0-or-later. See LICENSE.

Metadata

Release files for kennis 0.12.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for kennis 0.12.0
File Size Uploaded Provenance Actions
kennis-0.12.0.tar.gz 1.3 MB Without provenance Details for kennis-0.12.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for kennis 0.12.0
File Interpreter ABI Platform Actions
kennis-0.12.0-py3-none-any.whl Python 3 none any Details for kennis-0.12.0-py3-none-any.whl

Total release size: 2.7 MB

Release files / kennis-0.12.0.tar.gz

Download URL kennis-0.12.0.tar.gz
Size 1.3 MB
Tags Source
SHA-256 checksum
How to use checksums
8dc575cf9beffa0511d66a830b97eee94b5ea45a20ab9a93ed329d20e5b5c88e
BLAKE2b-256 checksum
How to use checksums
6b38bfc53e8d2af6eed04db07eb3cc1fdeae98ece8ed6cd67a3262ed701dd026
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / kennis-0.12.0-py3-none-any.whl

Download URL kennis-0.12.0-py3-none-any.whl
Size 1.4 MB
Tags Python 3
SHA-256 checksum
How to use checksums
552ae8592422b9962337e35281292fa5473bbcf2b15492c80b0d4d218d74ea29
BLAKE2b-256 checksum
How to use checksums
199991a9580d4e21b485cb5d0bb56aba65a1d4362304a5fcf9bc7bac3d3c488f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for kennis 0.12.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page