Errors¶
kennis raises a domain exception rather than letting an internal failure
reach the terminal as a traceback. Every one of them derives from
KennisError, and every one carries the command that resolves it, attached
with add_note - that note is what becomes the hint: line.
The engine raises these. It never catches them to print: the front end decides what an error looks like. See Three layers.
The engine's exceptions.
Every error names the command that resolves it. There is no state-derived hint system deciding what advice a failure deserves; the error carries its own answer, chosen where the failure is raised and by nothing else.
The resolution is recorded twice, on purpose. It is attached with add_note,
so it travels with the exception through a handler that knows nothing about
kennis and prints unaided at the bottom of a traceback; and it is kept as an
attribute, so a front end can render it in its own idiom - the command line as
a hint: line under the failure - without parsing __notes__.
These are domain exceptions. An engine module raising a front end's error
type is the inversion the engine/interface separation exists to forbid: the
command line is what turns CorpusNotFound into Error: ... and an exit
code, while the MCP server turns the same exception into a tool error.
KennisError
¶
Bases: Exception
Something kennis could not do, and the command that would fix it.
A subclass sets default_resolution to the command that resolves it in
the usual case. A caller with better information passes resolution;
a caller that knows nothing resolves this particular failure passes
resolution=None, which is also the base class's own default - an error
with no fix to name should say so by omission rather than by a
placeholder.
Source code in src/kennis/engine/errors.py
CorpusNotFound
¶
ContextNotFound
¶
Bases: KennisError
No .context/ bundle governs the directory kennis was run in.
The corpus is machine-global and a bundle is per-workspace, so this is
CorpusNotFound one scope down - and it is a different failure from a
bundle that exists and has no index, which is why the walk returns None
rather than raising and lets the caller pick.
Source code in src/kennis/engine/errors.py
PublishRefused
¶
Bases: KennisError
Another write changed what this one prepared against while it was preparing, so publishing would overwrite it. Nothing was placed (14e).
The one write that is refused rather than kept waiting, because waiting would not help: what it prepared is out of date, and running it again prepares against what is there now.
Source code in src/kennis/engine/errors.py
IndexBusy
¶
Bases: KennisError
Another kennis process is rebuilding this collection's index.
The documents are writable while an index is rebuilt; only the
rebuild has to wait - which is why
remember writes the note and reports the index as deferred
rather than losing it. Design section 16, concern #167.
Source code in src/kennis/engine/errors.py
CorpusExists
¶
Bases: KennisError
corpus init was asked for where a corpus already is.
Not a refusal to try again: initialising again would discard the history the first one made.
Source code in src/kennis/engine/errors.py
HistoryUnwritable
¶
Bases: KennisError
Git refused to record a change kennis made.
The failure is git's, for a reason it states - carried in the message - and retrying meets the same reason again. A publish that fails here is rolled back (14e), so the documents are as they were.
Source code in src/kennis/engine/errors.py
GitUnavailable
¶
Bases: KennisError
The git binary is missing.
kennis shells out to git rather than binding a library, and there is no
fallback implementation, so this is a system requirement rather than a
degraded mode.
Source code in src/kennis/engine/errors.py
SettingsError
¶
Bases: KennisError
A setting is missing, malformed, or outside its permitted range.
Source code in src/kennis/engine/errors.py
UnknownCollection
¶
Bases: KennisError
A collection name kennis does not have.
There are three, fixed: notes, literature and docs. A fourth is not
a configuration option - each of the three is a different processing layer
over one ingestion pipeline, not a folder someone may add to.
Source code in src/kennis/engine/errors.py
ProjectNotKnown
¶
Bases: KennisError
A name that is not one of the machine's known projects (14g).
Source code in src/kennis/engine/errors.py
BundleNotDeletable
¶
Bases: KennisError
A listed project whose folder is not a bundle by both its name and its manifest, so kennis will not delete it (2026-11-04).
The list is paths read from a file; a delete aimed by it removes a
whole tree, so it is aimed only at a .context folder holding
bundle.json. Forgetting the project takes the entry off the list.
Source code in src/kennis/engine/errors.py
ProjectListUnreadable
¶
Bases: KennisError
The list of the machine's projects could not be read (14g).
No command repairs it: it is a list of paths, rebuilt as kennis touches each project again once the file is removed, so the message names the file and the resolution is left out.
Source code in src/kennis/engine/errors.py
DocumentNotFound
¶
Bases: KennisError
No document answers to the handle given.
Also raised for an ambiguous handle. Two documents sharing a citekey or a title make that key useless, and the honest answer is that it addresses nothing; resolving it to whichever was walked first would silently hand back the wrong paper.
Source code in src/kennis/engine/errors.py
DocumentInvalid
¶
Bases: KennisError
A document on disk does not validate against its collection's schema.
A missing field, a key kennis does not understand, or an owner from a
vocabulary it does not recognise. Refused with the document named rather
than guessed at: kennis ships no migration, because the corpora are
rebuildable and there is no compatibility obligation.
Source code in src/kennis/engine/errors.py
InputError
¶
Bases: KennisError
An argument names nothing kennis can act on.
Raised rather than collected as a failed outcome: it means the command as typed cannot be carried out, and running the rest of the batch would leave the user to notice the gap in a summary. A pattern that matched nothing is the clearest case - quietly adding nothing is the one outcome that looks like success and is not.
Source code in src/kennis/engine/errors.py
SourceUnreadable
¶
Bases: KennisError
A source exists but its bytes could not be obtained.
An unreadable file, an unreachable URL, an encoding no ladder could decode. Distinct from a conversion failure: nothing was wrong with the converter, there was simply nothing to hand it.
Source code in src/kennis/engine/errors.py
ConverterUnavailable
¶
Bases: KennisError
A format needs a converter that is not installed.
Checked once per batch rather than once per document: a folder of fifty PDFs would otherwise produce fifty copies of the same install instructions, one at a time, over a run that cannot succeed.
Source code in src/kennis/engine/errors.py
ConversionFailed
¶
Bases: KennisError
A converter ran and produced nothing usable for a document.
Per document, not per run. A converter converts the documents it can and records the rest, so one document's failure must not cost the others.
Source code in src/kennis/engine/errors.py
ConverterRefused
¶
Bases: ConversionFailed
The converter turned kennis away, whatever the document: a hosted service rejecting its API key.
Not one document's failure, so it ends the add rather than being filed against each document in turn - where its remedy was dropped and the same refusal was paid for once per file.
Source code in src/kennis/engine/errors.py
NothingToIndex
¶
Bases: KennisError
There is no text to build an index over.
Raised before the index is built rather than translated afterwards,
because the failure it replaces is ValueError: max() iterable argument
is empty raised from inside bm25s's vocabulary build - and, under
filterwarnings = ["error"], three RuntimeWarnings that fire before
that and surface instead of it. Neither names anything a user can act on.
The condition is "no tokens survived tokenisation", not "no documents": stopwords are removed before the vocabulary is built, so a collection of documents that are all stopwords fails in exactly the same place as an empty one.
Source code in src/kennis/engine/errors.py
EmbeddingUnavailable
¶
Bases: KennisError
An embedding backend could not produce vectors.
Named separately from SettingsError because the configuration may be
perfectly correct and the daemon simply not running. The message carries
the backend and the model, since a failure from inside an HTTP client
says nothing about which of three configured backends was being asked,
or for what.
Source code in src/kennis/engine/errors.py
SearchUnavailable
¶
Bases: KennisError
The index cannot answer the question as asked.
Distinct from an absent index, which is NothingToIndex: this one is
built and readable, and the request does not fit it - a dense query
against a lexical-only index, or a document that is not in it. The
message names what would work instead.
Source code in src/kennis/engine/errors.py
PackInvalid
¶
Bases: KennisError
A .ken.yml file does not validate against the schema.
Design section 5, step 1: refuse naming the field path, because a
message that says only "invalid" makes the author hunt through a file
that may declare hundreds of documents. Separate from the two version
refusals - a schema_version kennis does not understand and a
min_version above its own - which are not defects in the file and are
resolved by upgrading rather than by editing.
Source code in src/kennis/engine/errors.py
PackNotInstalled
¶
Bases: KennisError
No pack of that id is in this machine's store.
Named rather than treated as a success, because the realistic cause is a mistyped id and reporting "removed" for a pack that was never there tells the user the thing they meant is gone.
Source code in src/kennis/engine/errors.py
BackupRefused
¶
Bases: KennisError
A backup or a restore that kennis will not start.
An empty password, a folder that is not there or cannot be written, a restore onto something that is not empty, or an archive naming a path outside the corpus. Each is refused before anything is written, so a refusal never leaves a partial file or a half-restored corpus.
Source code in src/kennis/engine/errors.py
BackupUnreadable
¶
Bases: KennisError
An archive that would not open, or failed its test.
One error for two causes, because they cannot be told apart: with the names encrypted, a wrong password decrypts the header into arbitrary bytes, and the library then fails however those bytes lead it to.
Source code in src/kennis/engine/errors.py
ServiceUnavailable
¶
Bases: KennisError
The corpus's service could not be started or reached (milestone 14).
Source code in src/kennis/engine/errors.py
McpExtraMissing
¶
Bases: KennisError
kennis serve in an installation without the mcp extra, which is
what supplies fastmcp. A traceback there reached an MCP client only as a
closed connection (2026-11-01).
Source code in src/kennis/engine/errors.py
ClientOutdated
¶
Bases: ServiceUnavailable
This process loaded kennis before kennis was reinstalled under it (#601). Replacing the service would start one from the new code, which this process could not read; no command fixes it, a restart does.
Source code in src/kennis/engine/errors.py
TaskCancelled
¶
Bases: BaseException
A task stopping part way because someone asked it to (Brian, 2026-10-27), raised in the task's own thread at the next point that can stop - before anything is published, so a cancelled write left the corpus as it was.
Not a KennisError, nor an Exception, as asyncio's
CancelledError is not since Python 3.8, and for the same reason: a
loop that records one item's KennisError as a failed item and carries
on would count a cancellation as one more failure and keep going. What
a cancelled task reports is TaskStopped, which the service makes of
this once it reaches the top. The message follows the word
"Cancelled", which every front end puts before it.
Source code in src/kennis/engine/errors.py
TaskStopped
¶
Bases: KennisError
A task's outcome when it was cancelled: the sentence TaskCancelled
carried, as a refusal every front end already knows how to report.
Source code in src/kennis/engine/errors.py
ServiceBusy
¶
Bases: KennisError
A stop asked of the service while a task is queued or running, which would end the task wherever it was.
Source code in src/kennis/engine/errors.py
CitationStyleUnreadable
¶
Bases: KennisError
The .csl file literature.citation_style names cannot be read as a
citation style. Empty, the setting uses the style kennis ships.