Skip to content

Configuration — API reference

Registry

Setting dataclass

Setting(
    key: str,
    env: str,
    default: str,
    requires_migration: bool = False,
    secret: bool = False,
    help: str = "",
    kind: str = "str",
    settable: bool = True,
    advanced: bool = False,
    derive: Callable[[], str] | None = None,
    validate: Callable[[str], str | None] | None = None,
    choices: tuple[str, ...] | None = None,
)

One configuration knob shown by mait-code settings.

Most settings are settable: backed by an environment variable, with a value resolved env → file → default. Two extra flavours exist:

  • Derived (settable=False): a read-only value computed at display time by :attr:derive (e.g. database paths derived from data-dir). These have no environment variable and never appear as an assignable line in the settings file.
  • Advanced (advanced=True): a real settable knob that is written commented-out in the generated settings file, so the hardcoded default stays authoritative until the user deliberately uncomments it.
ATTRIBUTE DESCRIPTION
key

Stable, kebab-case identifier shown by mait-code settings.

TYPE: str

env

The environment variable that backs it (empty for derived).

TYPE: str

default

Display string for the default value (<data-dir>-style placeholders are allowed where the real default is derived).

TYPE: str

requires_migration

True when changing it invalidates stored embeddings (provider/model changes need a re-embed).

TYPE: bool

secret

True when the value should be masked in output.

TYPE: bool

help

One-line description.

TYPE: str

kind

Value type for typed accessors — "str", "int" or "float". Drives :func:get_int / :func:get_float coercion.

TYPE: str

settable

False marks a derived, display-only value.

TYPE: bool

advanced

True writes the knob commented-out in the settings file.

TYPE: bool

derive

For derived settings, a zero-arg callable returning the computed value as a string. Must lazy-import any heavy deps so config stays a light, stdlib-only leaf.

TYPE: Callable[[], str] | None

validate

Optional callable taking the raw string value and returning an error message, or None when valid. Run by doctor.

TYPE: Callable[[str], str | None] | None

choices

Optional fixed set of valid values for an enum-like setting (e.g. embedding-provider → ("local", "bedrock")). Drives the interactive editor's picker; the matching :attr:validate enforces membership for set and doctor.

TYPE: tuple[str, ...] | None

SETTINGS module-attribute

SETTINGS: tuple[Setting, ...] = (
    Setting(
        "data-dir",
        "MAIT_CODE_DATA_DIR",
        DEFAULT_DATA_DIR_DISPLAY,
        help="Where memories, logs and personalised files live.",
    ),
    Setting(
        "log-level",
        "MAIT_CODE_LOG_LEVEL",
        DEFAULT_LOG_LEVEL,
        help="Log verbosity: DEBUG, INFO, WARNING or ERROR.",
        validate=_log_level,
        choices=_LOG_LEVELS,
    ),
    Setting(
        "log-file",
        "MAIT_CODE_LOG_FILE",
        "<state-dir>/mait-code.jsonl",
        help="Override the log file path.",
    ),
    Setting(
        "theme",
        "MAIT_CODE_THEME",
        DEFAULT_THEME,
        help="TUI colour theme; any registered theme, unknown names fall back to mait-dark.",
        validate=_non_empty,
    ),
    Setting(
        "bridge",
        "MAIT_CODE_BRIDGE",
        "disabled",
        kind="bool",
        help="Bridge capture/notify channel: 'enabled' or 'disabled'. Off by default — enable via the hub or here to allow network access.",
        validate=_bridge_gate,
        choices=_BRIDGE_GATE,
    ),
    Setting(
        "bridge-type",
        "MAIT_CODE_BRIDGE_TYPE",
        "ntfy",
        help="Which Bridge channel to use (configured in the Bridge screen).",
        validate=_bridge_type,
    ),
    Setting(
        "mods",
        "MAIT_CODE_MODS",
        "disabled",
        kind="bool",
        help="The mait-companion Claude Code mod (/capture + status bar): 'enabled' or 'disabled'. Off by default — early-access mods API.",
        validate=_mods_gate,
        choices=_MODS_GATE,
    ),
    Setting(
        "status-bar-style",
        "MAIT_CODE_STATUS_BAR_STYLE",
        "blocks",
        help="How the mod's status bar draws: 'blocks' or 'slim'.",
        validate=_status_bar_style,
        choices=_STATUS_BAR_STYLES,
    ),
    Setting(
        "jira-base-url",
        "MAIT_CODE_JIRA_BASE_URL",
        "",
        help="Jira site the status bar links card keys to (https://acme.atlassian.net).",
        validate=_jira_base_url,
    ),
    Setting(
        "embedding-provider",
        "MAIT_CODE_EMBEDDING_PROVIDER",
        DEFAULT_EMBEDDING_PROVIDER,
        requires_migration=True,
        help="Embedding backend: 'local' or 'bedrock'.",
        validate=_embedding_provider,
        choices=_EMBEDDING_PROVIDERS,
    ),
    Setting(
        "embedding-model",
        "MAIT_CODE_EMBEDDING_MODEL",
        DEFAULT_EMBEDDING_MODEL,
        requires_migration=True,
        help="Local embedding model (used when provider is 'local').",
    ),
    Setting(
        "bedrock-model-id",
        "MAIT_CODE_BEDROCK_MODEL_ID",
        DEFAULT_BEDROCK_MODEL_ID,
        requires_migration=True,
        help="Bedrock model id (used when provider is 'bedrock').",
    ),
    Setting(
        "bedrock-region",
        "MAIT_CODE_BEDROCK_REGION",
        DEFAULT_BEDROCK_REGION,
        help="AWS region for the Bedrock embedding client.",
    ),
    Setting(
        "log-backup-count",
        "MAIT_CODE_LOG_BACKUP_COUNT",
        "14",
        kind="int",
        advanced=True,
        validate=_positive_int,
        help="Days of rotated log files to keep.",
    ),
    Setting(
        "extraction-model",
        "MAIT_CODE_EXTRACTION_MODEL",
        "haiku",
        advanced=True,
        help="Model used for memory extraction (fast/cheap by default).",
    ),
    Setting(
        "reflection-model",
        "MAIT_CODE_REFLECTION_MODEL",
        "haiku",
        advanced=True,
        help="Model used for reflection synthesis.",
    ),
    Setting(
        "llm-timeout",
        "MAIT_CODE_LLM_TIMEOUT",
        "90",
        kind="int",
        advanced=True,
        validate=_positive_int,
        help="Timeout (seconds) for subprocess LLM calls.",
    ),
    Setting(
        "reflection-batch-size",
        "MAIT_CODE_REFLECTION_BATCH_SIZE",
        "50",
        kind="int",
        advanced=True,
        validate=_positive_int,
        help="Default entries processed per reflection (--batch-size overrides).",
    ),
    Setting(
        "reflection-novelty-gate",
        "MAIT_CODE_REFLECTION_NOVELTY_GATE",
        "3",
        kind="int",
        advanced=True,
        validate=_non_negative_int,
        help="Default new entries required to trigger reflection (--min-new overrides).",
    ),
    Setting(
        "git-timeout",
        "MAIT_CODE_GIT_TIMEOUT",
        "5",
        kind="int",
        advanced=True,
        validate=_positive_int,
        help="Timeout (seconds) for git context probes.",
    ),
    Setting(
        "dashboard-tile-timeout",
        "MAIT_CODE_DASHBOARD_TILE_TIMEOUT",
        "5",
        kind="int",
        advanced=True,
        validate=_positive_int,
        help="Timeout (seconds) for home-hub shell-command tiles.",
    ),
    Setting(
        "score-weight-recency",
        "MAIT_CODE_SCORE_WEIGHT_RECENCY",
        "0.3",
        kind="float",
        advanced=True,
        validate=_unit_interval,
        help="Scoring weight for recency (the three weights must sum to 1.0).",
    ),
    Setting(
        "score-weight-importance",
        "MAIT_CODE_SCORE_WEIGHT_IMPORTANCE",
        "0.3",
        kind="float",
        advanced=True,
        validate=_unit_interval,
        help="Scoring weight for importance (the three weights must sum to 1.0).",
    ),
    Setting(
        "score-weight-relevance",
        "MAIT_CODE_SCORE_WEIGHT_RELEVANCE",
        "0.4",
        kind="float",
        advanced=True,
        validate=_unit_interval,
        help="Scoring weight for relevance (the three weights must sum to 1.0).",
    ),
    Setting(
        "half-life-episodic",
        "MAIT_CODE_HALF_LIFE_EPISODIC",
        "3.0",
        kind="float",
        advanced=True,
        validate=_positive_float,
        help="Recency half-life (days) for episodic memories (events, tasks).",
    ),
    Setting(
        "half-life-semantic",
        "MAIT_CODE_HALF_LIFE_SEMANTIC",
        "90.0",
        kind="float",
        advanced=True,
        validate=_positive_float,
        help="Recency half-life (days) for semantic memories (facts, preferences).",
    ),
    Setting(
        "half-life-procedural",
        "MAIT_CODE_HALF_LIFE_PROCEDURAL",
        "180.0",
        kind="float",
        advanced=True,
        validate=_positive_float,
        help="Recency half-life (days) for procedural memories (workflows, how-tos).",
    ),
    Setting(
        "dedup-string-threshold",
        "MAIT_CODE_DEDUP_STRING_THRESHOLD",
        "0.85",
        kind="float",
        advanced=True,
        validate=_unit_interval,
        help="String-similarity threshold above which a memory is a duplicate.",
    ),
    Setting(
        "review-threshold",
        "MAIT_CODE_REVIEW_THRESHOLD",
        "0.5",
        kind="float",
        advanced=True,
        validate=_unit_interval,
        help="Recall probability below which a memory is surfaced for review (0.5 = one half-life since last review).",
    ),
    Setting(
        "review-min-importance",
        "MAIT_CODE_REVIEW_MIN_IMPORTANCE",
        "5",
        kind="int",
        advanced=True,
        validate=_positive_int,
        help="Minimum importance (1-10) for a memory to be surfaced for review; lower-value memories decay without nagging.",
    ),
    Setting(
        "dedup-vector-threshold",
        "MAIT_CODE_DEDUP_VECTOR_THRESHOLD",
        "0.92",
        kind="float",
        advanced=True,
        validate=_unit_interval,
        help="Cosine-similarity threshold above which a memory is a duplicate.",
    ),
    Setting(
        "dedup-conflict-threshold",
        "MAIT_CODE_DEDUP_CONFLICT_THRESHOLD",
        "0.60",
        kind="float",
        advanced=True,
        validate=_unit_interval,
        help="Lower edge of the contradiction band. Cosine similarity in [this, dedup-vector-threshold) flags a possible conflict rather than merging or storing silently.",
    ),
    Setting(
        "scope-boost-global",
        "MAIT_CODE_SCOPE_BOOST_GLOBAL",
        "0.7",
        kind="float",
        advanced=True,
        validate=_unit_interval,
        help="Relevance multiplier applied to global-scoped memories.",
    ),
    Setting(
        "scope-boost-cross-project",
        "MAIT_CODE_SCOPE_BOOST_CROSS_PROJECT",
        "0.3",
        kind="float",
        advanced=True,
        validate=_unit_interval,
        help="Relevance multiplier applied across project boundaries.",
    ),
    Setting(
        "embedding-dim",
        "",
        "",
        settable=False,
        kind="int",
        derive=_derive_embedding_dim,
        help="Embedding vector size (derived from provider + model).",
    ),
    Setting(
        "memory-db-path",
        "",
        "",
        settable=False,
        derive=lambda: _display_path("memory.db"),
        help="SQLite store for memories (derived from data-dir).",
    ),
    Setting(
        "reminders-db-path",
        "",
        "",
        settable=False,
        derive=lambda: _display_path("reminders.db"),
        help="SQLite store for reminders (derived from data-dir).",
    ),
    Setting(
        "bridge-config-path",
        "",
        "",
        settable=False,
        derive=lambda: _display_path("bridge.json"),
        help="Bridge channel config — server, topics, token (derived from data-dir).",
    ),
    Setting(
        "model-cache-dir",
        "",
        "",
        settable=False,
        derive=lambda: _display_path("models"),
        help="Local embedding-model cache (derived from data-dir; can be ~550MB).",
    ),
    Setting(
        "observations-dir",
        "",
        "",
        settable=False,
        derive=lambda: _display_path(
            "memory", "observations"
        ),
        help="Observation JSONL logs (derived from data-dir).",
    ),
    Setting(
        "project-aliases-path",
        "",
        "",
        settable=False,
        derive=lambda: _display_path(
            "project-aliases.json"
        ),
        help="Project-alias map (derived from data-dir).",
    ),
    Setting(
        "dashboard-config-path",
        "",
        "",
        settable=False,
        derive=lambda: _display_path("dashboard.toml"),
        help="Home-hub start-page layout — widgets & command tiles (derived from data-dir).",
    ),
)

resolve

resolve(setting: Setting) -> tuple[str, str]

Return (value, source) for a setting.

Resolution order (highest priority wins):

  1. Environment variable — source = "env"
  2. Settings file — source = "settings"
  3. Hardcoded default — source = "default"

Derived settings (settable=False) short-circuit to their computed value with source "derived".

This reports configuration; it does not validate it — that is doctor's job.

get

get(key: str) -> str

Return the resolved value for a setting by its kebab-case key.

Convenience wrapper around :func:resolve that discards the source.

RAISES DESCRIPTION
KeyError

If key is not a registered setting.

get_int

get_int(key: str) -> int

Return a setting coerced to int.

Falls back to the setting's hardcoded default (logging a warning) when the resolved value cannot be parsed, so a fat-fingered settings file degrades to stock behaviour rather than crashing a hook. doctor is responsible for surfacing the bad value loudly.

RAISES DESCRIPTION
KeyError

If key is not a registered setting.

get_float

get_float(key: str) -> float

Return a setting coerced to float.

Like :func:get_int, falls back to the hardcoded default on a bad value.

RAISES DESCRIPTION
KeyError

If key is not a registered setting.

get_bool

get_bool(key: str) -> bool

Return a setting coerced to bool.

Accepts the usual truthy/falsey spellings plus enabled/disabled (case-insensitive). Like :func:get_int, an unrecognised value falls back to the setting's hardcoded default (logging a warning) so a fat-fingered gate degrades to its default rather than crashing a hook.

RAISES DESCRIPTION
KeyError

If key is not a registered setting.

validate_settings

validate_settings() -> list[str]

Return a list of human-readable validation errors (empty when healthy).

Runs every setting's per-value :attr:Setting.validate callable, plus the cross-field invariants below. Called by doctor; it reads the resolved configuration but never mutates it.

Settings file I/O

read_settings_file

read_settings_file(
    path: Path | None = None,
) -> dict[str, str]

Read the settings TOML file.

PARAMETER DESCRIPTION
path

Override the settings file path (defaults to :func:~mait_code.cli._paths.settings_path).

TYPE: Path | None DEFAULT: None

RETURNS DESCRIPTION
dict[str, str]

A flat {key: value} dict with kebab-case keys and string

dict[str, str]

values. Returns {} if the file is missing or malformed.

dict[str, str]

Tables (notably [env]) are not part of the flat view — read

dict[str, str]

the env table with :func:read_env_table.

write_settings_file

write_settings_file(
    values: dict[str, str],
    *,
    path: Path | None = None,
    env: dict[str, str] | None = None,
) -> Path

Write the settings TOML file atomically.

Generates a fully commented TOML with all registered settings. Values not in values are written with their defaults.

PARAMETER DESCRIPTION
values

Setting values to write (kebab-case keys).

TYPE: dict[str, str]

path

Override the settings file path (defaults to :func:~mait_code.cli._paths.settings_path).

TYPE: Path | None DEFAULT: None

env

Custom [env] table to write. None (the default) preserves the table already in the file, so rewrites from settings set, the TUI editor and install/update round-trip it untouched.

TYPE: dict[str, str] | None DEFAULT: None

RETURNS DESCRIPTION
Path

The path the file was written to.

reset_cache

reset_cache() -> None

Forget the cached settings-file contents.

Call after writing the settings file in-process so a subsequent :func:get reflects the new value rather than the stale cache.

Env injection

read_env_table

read_env_table(path: Path | None = None) -> dict[str, str]

Read the [env] table of custom environment variables.

PARAMETER DESCRIPTION
path

Override the settings file path (defaults to :func:~mait_code.cli._paths.settings_path).

TYPE: Path | None DEFAULT: None

RETURNS DESCRIPTION
dict[str, str]

A {NAME: value} dict of user-defined environment variables.

dict[str, str]

Returns {} if the file is missing or malformed, or there is

dict[str, str]

no [env] table. Non-string values are dropped, mirroring

dict[str, str]

func:read_settings_file.

apply_env

apply_env() -> list[str]

Inject the settings [env] table into :data:os.environ.

Called from each entry point's startup path (via :func:mait_code.logging.setup_logging and the mait-code CLI), so user-defined variables — e.g. AWS_PROFILE for Bedrock embeddings — are present however a tool is invoked, not only inside Claude Code sessions.

Variables already present in the real environment are left alone, so a one-off AWS_PROFILE=other mc-tool-… override keeps working — and repeat calls are naturally no-ops. MAIT_CODE_* keys are skipped with a warning: first-class settings already resolve env → file → default, and injecting them would create a second, conflicting resolution path. doctor flags those too.

RETURNS DESCRIPTION
list[str]

The names of the variables actually injected.

Resolvers

data_dir

data_dir() -> Path

Return the mait-code data directory — the canonical resolver.

Honours $MAIT_CODE_DATA_DIR when set to a non-empty value; otherwise ~/.claude/mait-code-data. Computed at call time so tests that relocate $HOME are honoured.

A leading ~ in the override is expanded — otherwise a value like ~/.claude/mait-code-data (a literal, unexpanded tilde from the environment) would resolve relative to the cwd, scattering data into a stray ~ directory instead of $HOME.

Settings view

ResolvedSetting dataclass

ResolvedSetting(
    key: str,
    value: str,
    source: str,
    requires_migration: bool,
)

A setting with its resolved value and provenance, for display.

SettingsSnapshot dataclass

SettingsSnapshot(
    settings: tuple[ResolvedSetting, ...],
    drift: str | None = None,
)

All resolved settings, plus any detected configuration drift.

collect_settings

collect_settings() -> SettingsSnapshot

Resolve every setting for display, and flag embedding-provider drift.

Drift is detected when the env var overrides the settings file value for embedding-provider — this means the runtime provider differs from what was configured, and memories may need re-embedding.

RETURNS DESCRIPTION
SettingsSnapshot

A read-only :class:SettingsSnapshot.

render

render(snapshot: SettingsSnapshot) -> None

Print the snapshot to the shared console (read-only, provenance-aware).

Imports rich lazily so this module stays a light, stdlib-only leaf for the hook/tool processes that import it only for :func:data_dir.

render_json

render_json(snapshot: SettingsSnapshot) -> str

Render the snapshot as a JSON document.

Defaults

DEFAULT_DATA_DIR_DISPLAY module-attribute

DEFAULT_DATA_DIR_DISPLAY = '~/.claude/mait-code-data'

DEFAULT_LOG_LEVEL module-attribute

DEFAULT_LOG_LEVEL = 'INFO'

DEFAULT_EMBEDDING_PROVIDER module-attribute

DEFAULT_EMBEDDING_PROVIDER = 'local'

DEFAULT_EMBEDDING_MODEL module-attribute

DEFAULT_EMBEDDING_MODEL = 'nomic-ai/nomic-embed-text-v1.5'

DEFAULT_BEDROCK_MODEL_ID module-attribute

DEFAULT_BEDROCK_MODEL_ID = 'amazon.titan-embed-text-v2:0'

DEFAULT_BEDROCK_REGION module-attribute

DEFAULT_BEDROCK_REGION = 'eu-west-2'

DEFAULT_THEME module-attribute

DEFAULT_THEME = 'mait-dark'