Skip to content

Configuration

kennis reads its settings from one TOML file, and its credentials from somewhere else entirely. This page lists every key; the reasoning behind the split is in Keep credentials out of the config.

Where the file lives

kennis config path

The platform configuration directory, which on Linux is ~/.config/kennis/config.toml. Setting KENNIS_CONFIG_DIR overrides it, which is what makes a throwaway configuration possible without touching your own.

Precedence

A value is taken from the first source that offers it:

  1. The environment, as KENNIS_<SECTION>_<FIELD> - so KENNIS_EMBEDDING_BACKEND or KENNIS_EMBEDDING_API_KEY_ENV.
  2. config.toml in the configuration directory.
  3. The default shown in the tables below.

Exact names, not nested delimiters

pydantic-settings can split an environment variable on _ to find a nested field. kennis does not use that. KENNIS_EMBEDDING_API_KEY_ENV would resolve to embedding.api.key.env, match nothing, and be dropped in silence - the field keeps its default and the person who set the variable has no way to find out why. kennis walks its own model instead and looks each name up exactly.

A name in the file that kennis does not read - a misspelling such as chunking.sise, or a setting a later kennis removed - has no effect. Every command says so on stderr, and the settings page says so at the head of its kennis section. The next change to the configuration removes it, because every write renders the whole file from the settings kennis has.

Reading and changing settings

kennis config show                    # the whole file, as valid TOML
kennis config get                     # every setting and the value in effect
kennis config get embedding.model     # one value, unstyled, for scripts
kennis config set embedding.backend ollama
kennis config init                    # the guided setup

Every write leaves every setting in the file, including those at their default, whether it comes from config set, config init or the settings page. The file then says what is in use, and it can be copied or synced to another machine as it is. A value from the environment is never written into it: an exported variable is for the shell that set it. config init replaces the whole file, so on an existing one it asks first, and anything edited by hand, comments included, is lost.

The settings page offers every setting except two: corpus.root, because changing it under an open window moves the corpus from under every page, and backup.folder, which is chosen in the page's Backup section. config init asks only the settings where the right answer depends on you.

The [logging] section is read when kennis starts, so a change to it takes effect the next time a command runs or the window is opened.

kennis config show writes the file to standard output and its warnings to standard error, so kennis config show > config.toml produces a usable file rather than one with a warning in the middle.

kennis config get <key> prints the bare value with no styling, so it can be read by a script. kennis config get with no key prints the whole set, highlighted.

Every setting

[embedding]

How chunks and queries become vectors.

key type default what it does
backend fastembed / ollama / openai / none fastembed Backend used to embed chunks. fastembed downloads a 65 MB model the first time you index; none means lexical search only.
model str BAAI/bge-small-en-v1.5 Model name, interpreted by the backend.
normalise bool True Scale vectors to unit length, so cosine is a dot product.
batch_size int 64 Texts per backend request.
base_url str "" Server address. Unused for fastembed.
api_key_env str OPENAI_API_KEY Environment variable holding the key, for hosted backends.

[chunking]

How a document is cut up before it is indexed.

key type default what it does
size int 1500 Target characters per chunk.
overlap int 200 Characters shared between neighbouring chunks.

[retrieval]

How a search is run.

key type default what it does
default_top_k int 3 How many hits a search returns from each scope it searched, so a sweep can return this many times the number of scopes.
corpus_method hybrid / bm25 / dense hybrid Retrieval method for corpus collections.
context_method hybrid / bm25 / dense bm25 Retrieval method for a project's context bundle. bm25 keeps kennis context init offline and its index portable between machines; anything else needs an embedding backend.
scores human / raw / none human How a search shows how well each hit matched, on the command line and to an agent: as levels, as the raw scores, or not at all. kennis search --scores overrides it for one search.

[corpus]

Where the corpus lives and how documents are added to it.

key type default what it does
root str "" Corpus directory. Empty means the platform data directory.
keep_original bool False Keep the source file beside the document.
default_group str "" Group new documents are filed under when none is given.

[conversion]

How binary documents become markdown.

key type default what it does
backend mineru / datalab mineru Converter for PDF, DOCX, PPTX and XLSX. mineru runs locally and is installed with kennis; datalab is hosted, needs an API key, and sends the document to a third party.
mode fast / balanced / accurate balanced How hard the datalab backend works, and what it charges. Measured per page: fast 0.4c, balanced 0.4c, accurate 1.0c. The server only reuses a conversion when the mode matches, so changing this and re-adding a document pays for it again. mineru ignores this - it runs locally and costs nothing.
repair_ligatures bool True Repair ligature damage against the PDF's own text layer, read with pypdfium2. mineru drops a letter from ff, fi and fl on some documents, which no reindex can undo. Costs 0.7ms a page and applies to mineru only - datalab showed no such damage.
batch_size int 8 Documents per converter run. 0 is one run.

[literature]

How papers are identified and fetched.

key type default what it does
request_delay float 3.0 Least seconds between arXiv papers. A longer Crawl-delay in a host's robots.txt applies instead: arxiv.org asks for 15.
unpaywall_email str "" Your email address, which Unpaywall requires, to look up a free and legal copy of a paper known only by its DOI. Empty: Unpaywall is not asked, and such a paper needs your own copy.
citation_style str "" A .csl file to format citations with, such as one from the Zotero style repository. Empty: Harvard (Cite Them Right), which is installed with kennis.

[terminal]

What the command line prints beside its reports.

key type default what it does
messages all / warnings / errors all Which advice the command line prints: all of it, warnings and errors without hints, or errors alone. An error keeps the command that resolves it, and reports are always printed.

[logging]

What is written to the log file.

key type default what it does
level debug / info / warning / error info Detail written to the log file.
max_bytes int 1048576 Size at which the log rotates.
backup_count int 3 Rotated log files kept.

[backup]

Where the encrypted backup of the corpus is written.

key type default what it does
folder str "" Folder the encrypted backup is written to, often one a cloud client synchronises. The backup holds the full text of every document.

[service]

How the process that owns the corpus behaves (milestone 14).

key type default what it does
idle_minutes float 30.0 Minutes the kennis service waits with no task, request or open window before it exits. The next command starts it again.

Settings kennis deliberately does not offer

A setting that nothing honours is worse than a missing one, because it is a promise. Where the design defers a mechanism, its setting is absent rather than present and ignored.

The model

Bases: BaseSettings

Every setting kennis honours.

Source code in src/kennis/engine/settings.py
class Settings(BaseSettings):
    """Every setting kennis honours."""

    model_config = SettingsConfigDict(env_prefix=f"{_ENVIRONMENT_PREFIX}_")

    embedding: EmbeddingSettings = EmbeddingSettings()
    chunking: ChunkingSettings = ChunkingSettings()
    retrieval: RetrievalSettings = RetrievalSettings()
    corpus: CorpusSettings = CorpusSettings()
    conversion: ConversionSettings = ConversionSettings()
    literature: LiteratureSettings = LiteratureSettings()
    terminal: TerminalSettings = TerminalSettings()
    logging: LoggingSettings = LoggingSettings()
    backup: BackupSettings = BackupSettings()
    service: ServiceSettings = ServiceSettings()

    @classmethod
    def settings_customise_sources(
        cls,
        settings_cls: type[BaseSettings],
        init_settings: PydanticBaseSettingsSource,
        env_settings: PydanticBaseSettingsSource,
        dotenv_settings: PydanticBaseSettingsSource,
        file_secret_settings: PydanticBaseSettingsSource,
    ) -> tuple[PydanticBaseSettingsSource, ...]:
        """Environment, then the file, then defaults.

        pydantic's own `env_settings` is replaced rather than supplemented:
        leaving it in place would reintroduce delimiter splitting beside the
        exact-name lookup, and two sources disagreeing about one variable is
        worse than either.
        """
        return (
            init_settings,
            ExactNameEnvSource(settings_cls),
            TomlFileSource(settings_cls),
        )

settings_customise_sources classmethod

settings_customise_sources(settings_cls: type[BaseSettings], init_settings: PydanticBaseSettingsSource, env_settings: PydanticBaseSettingsSource, dotenv_settings: PydanticBaseSettingsSource, file_secret_settings: PydanticBaseSettingsSource) -> tuple[PydanticBaseSettingsSource, ...]

Environment, then the file, then defaults.

pydantic's own env_settings is replaced rather than supplemented: leaving it in place would reintroduce delimiter splitting beside the exact-name lookup, and two sources disagreeing about one variable is worse than either.

Source code in src/kennis/engine/settings.py
@classmethod
def settings_customise_sources(
    cls,
    settings_cls: type[BaseSettings],
    init_settings: PydanticBaseSettingsSource,
    env_settings: PydanticBaseSettingsSource,
    dotenv_settings: PydanticBaseSettingsSource,
    file_secret_settings: PydanticBaseSettingsSource,
) -> tuple[PydanticBaseSettingsSource, ...]:
    """Environment, then the file, then defaults.

    pydantic's own `env_settings` is replaced rather than supplemented:
    leaving it in place would reintroduce delimiter splitting beside the
    exact-name lookup, and two sources disagreeing about one variable is
    worse than either.
    """
    return (
        init_settings,
        ExactNameEnvSource(settings_cls),
        TomlFileSource(settings_cls),
    )