Skip to content

Configuring with coniti.toml and overrides

Built and verified

The container and its config loading are built and verified. The base Container accepts four keys: memory (default in_memory), embedding (default local_hash), structures (default [flat]) and strategy (default default). A subclass with its own slots accepts its own keys.

Config files are trusted input

A kind can be a class path such as "my_pkg.embedders:TinyEmbedder". Resolving it imports that module, and importing runs code. Treat a config file like code: only load files you wrote or trust.

Where the config is found

Container(config) looks in this order and uses the first that applies:

  1. config argument: a Path or str path to a file, or a mapping (a dict used directly).
  2. The CONITI_CONFIG environment variable (a file path).
  3. ./coniti.toml in the working directory, unless you pass search_cwd=False.
  4. Nothing: only the defaults apply.

A path that is given (argument or environment variable) must exist. TOML and JSON are built in; YAML needs the yaml extra. A missing, malformed or unreadable file raises ConfigError.

Unknown top-level keys are rejected (typo safety), and the error lists the known keys.

Format

Top-level keys are piece names: memory, embedding and structures (a list) and strategy.

# coniti.toml
[memory]
kind = "in_memory"

[embedding]
kind = "local_hash"
params = { dim = 64, seed = 7 }

[[structures]]
kind = "flat"

Structures are given the embedder’s dim automatically, so you set the dimension once, under [embedding].

class is an alias for a class-path kind; give one or the other, not both:

[embedding]
class = "my_pkg.embedders:TinyEmbedder"   # class path; no registration needed

The strategy is chosen the same way (default is the only built-in kind):

[strategy]
kind = "default"

segment_tree is built too (time-range summaries; see structure):

[[structures]]
kind = "segment_tree"
params = { bucket_seconds = 3600 }

Priority and merging

default < config < bind < override < explicit argument.

  • A higher layer naming the same implementation, or giving only params, deep-merges its params over the lower layer.
  • A higher layer naming a different implementation replaces the lower binding.
  • A list-valued piece (such as structures) is replaced as a whole, never merged.
from coniti import Container, Embedder

c = Container("coniti.toml")
c.bind(Embedder, {"kind": "local_hash", "params": {"dim": 128}})   # permanent for c

with c.override(Embedder, MyEmbedder()):                           # temporary, restores on exit
    ...

c.resolved()   # per piece: layer, kind, params, spec id, implementation; builds nothing

Changing the embedding dimension rebuilds the structures to match; leaving an override restores the previous ones. A Strategy can be overridden the same way.

Changing the defaults without editing coniti

Use a config file, bind / override, or subclass Container and replace defaults (and slots to add pieces). See container for a runnable example.

Errors

Any invalid binding raises ConfigError, a ValueError carrying .key (the piece) and .layer; both are named in the message. Cases include an unknown kind, an unimportable class path, a class that does not subclass the interface, bad params, a wrong binding type, a dependency cycle, a factory that raises, and a malformed or missing config file.