> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tai42.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Author a config provider

> Implement the config-manager contract.

Author a config provider plugin behind the config-manager contract.

A config provider is where the server reads its environment and manifest. It implements the `ConfigManager` ABC — **eight** abstract methods — and exposes a `build_config_manager()` factory. The skeleton's config seam loads a provider by dynamic import of that factory, so there is no static edge in either direction.

## Implement the contract

Subclass `ConfigManager` and implement all eight abstract methods:

* **Env surface** — `read_env` and `write_env`. `write_env` merges, preserving keys absent from the new config.
* **Manifest reads** — `read_manifest` (the resolved view), `read_manifest_preserved` (the same document with every `!ENV <expr>` kept as its literal marker string, never resolved to a secret), and `read_defaults_manifest`.
* **Manifest writes** — `write_manifest` (a three-way merge reserved for config-layer internals such as bootstrap and the defaults merge; it preserves YAML `!ENV` tags), plus the two transactional write seams feature code uses: `mutate_manifest` and `replace_manifest`.

`mutate_manifest(mutator)` reads the persisted preserved view, runs `mutator` to edit it in place, and persists the result — untouched keys keep their values. `replace_manifest(document)` swaps the whole stored document, dropping any key absent from `document`. Both hold exclusive access across the entire read-modify-write span, so a concurrent writer cannot interleave: a file-backed manager takes a lock, an API-backed one retries on an optimistic-concurrency conflict (which is why `mutator` must be re-runnable). Both are abstract — a subclass that implements only one cannot be instantiated. A missing source raises loudly (`FileNotFoundError`) — never a silent empty.

```python examples/config_provider/vault_config.py theme={null}
"""A minimal fictional config provider: a ``vault`` mode.

Implements the ``ConfigManager`` ABC — the env surface (``read_env`` /
``write_env``) and the manifest surface (``read_manifest`` /
``read_manifest_preserved`` / ``read_defaults_manifest`` / ``write_manifest`` /
``mutate_manifest`` / ``replace_manifest``) — and exposes a
``build_config_manager()`` factory. The skeleton's config seam loads a provider
by dynamic import of that factory, so there is no static edge in either
direction. This example keeps its backing in memory; a real provider swaps in
its own store (a secrets vault, a database) behind the same eight methods.

``mutate_manifest`` and ``replace_manifest`` are both abstract, so a subclass
that implements only one cannot be instantiated. They are the transactional
write seams feature code uses: each holds exclusive access across the whole
read → modify → write span, so a concurrent writer cannot interleave.
"""

from collections.abc import Callable
from typing import Any

from tai42_contract.config import ConfigManager


class VaultConfigManager(ConfigManager):
    def __init__(self) -> None:
        self._env: dict[str, str] = {}
        self._manifest: dict[str, Any] = {}

    def read_env(self) -> dict[str, str]:
        return dict(self._env)

    def write_env(self, config: dict[str, str]) -> None:
        # Merge, preserving keys absent from ``config`` and dropping empty values.
        self._env.update({k: v for k, v in config.items() if v})

    def read_manifest(self) -> dict[str, Any]:
        return dict(self._manifest)

    def read_manifest_preserved(self) -> dict[str, Any]:
        # The in-memory backing stores no ``!ENV`` placeholders, so the
        # preserved view equals the resolved one.
        return dict(self._manifest)

    def read_defaults_manifest(self) -> dict[str, Any]:
        return {}

    def write_manifest(self, manifest: dict[str, Any]) -> None:
        self._manifest = dict(manifest)

    def mutate_manifest(self, mutator: Callable[[dict[str, Any]], None]) -> dict[str, Any]:
        # Read the current document, edit it in place under exclusive access,
        # and persist. The in-memory store serializes writes trivially; a real
        # backend takes a lock or retries on an optimistic-concurrency conflict,
        # so ``mutator`` must be re-runnable.
        document = dict(self._manifest)
        mutator(document)
        self._manifest = document
        return dict(document)

    def replace_manifest(self, document: dict[str, Any]) -> dict[str, Any]:
        # Replace the whole stored document: a key absent from ``document`` is
        # dropped. The caller builds ``document`` from the preserved view so no
        # resolved secret is ever persisted.
        self._manifest = dict(document)
        return dict(self._manifest)


def build_config_manager() -> ConfigManager:
    """Provider entry point for the config mode (the factory convention)."""
    return VaultConfigManager()
```

This example keeps its backing in memory; a real provider implements the same eight methods over its own store — a secrets vault, a database, or a cluster's secret and config objects.

## Expose the factory

Expose a module-level `build_config_manager()` that returns your manager — the selection convention every config provider follows. The example ends with exactly this factory.

## Wire the config mode

A config provider is selected by `TAI_CONFIG_MODE`, not by a manifest module — it must be resolved before the manifest is read. The skeleton maps a mode name to your factory's import path and loads it by dynamic import. Install your package, then set the mode:

```bash theme={null}
export TAI_CONFIG_MODE=vault   # your provider's mode name
```

Depend on `tai42-contract` (the interface) and `tai42-kit` (settings and manifest yaml helpers) only; never import the skeleton. Keep any client driver behind an optional extra. Document your provider's own settings in your repository's README.

## Shipped implementations

* [`tai42-config-k8s`](https://github.com/tai42ai/tai-config-k8s) — the Kubernetes `ConfigManager` (env via Secrets, manifest via ConfigMaps).

## See also

* [Config and secrets](/concepts/config-and-secrets) — the config-provider seam and the built-in file provider.
* [Deploy](/guides/deploy) — selecting a config provider at runtime.
* [Python SDK reference](/reference/python-sdk) — the `ConfigManager` contract surface.
