# Contract: Configuration — TOML Config Chain **Layer:** Configuration **Version:** 0.1.0 **Last updated:** 2026-04-01 --- ## resolve_llm() `llm_connect.toml_config.resolve_llm(cli_provider, cli_model, app_name)` Walks a 7-level priority chain to resolve provider and model independently. Returns `ResolvedLLM(provider, model, provider_source, model_source)`. ### Priority chain (highest → lowest) | Level | Source | |-------|--------| | 1 | CLI flags (`cli_provider`, `cli_model`) | | 2 | Env var `{APP_NAME}_HELPER_MODEL` (model only) | | 3 | User preference — `~/.config/{app_name}/config.toml` `[llm.preference]` | | 4 | Directory preference — `.{app_name}.toml` `[llm.preference]` | | 5 | Directory default — `.{app_name}.toml` `[llm.default]` | | 6 | User default — `~/.config/{app_name}/config.toml` `[llm.default]` | | 7 | Hardcoded fallback — `gemini / gemini-2.5-flash` | ### Invariants - Always returns a fully-resolved `ResolvedLLM` (never raises, never returns None). - Provider and model are resolved independently — a preference for model does not imply a preference for provider. - TOML parse errors are silently ignored (returns empty layer). - `app_name` defaults to `"markitect"` for backward compatibility; consumers should pass their own app name. ### Known issue `toml_config.py` has `markitect`-specific defaults (`MARKITECT_HELPER_MODEL`, `USER_CONFIG_DIR`). These are kept for backward compatibility but callers outside markitect should always pass an explicit `app_name`. --- ## resolve_api_key() `llm_connect.config.resolve_api_key(explicit, env_var, key_file_paths)` Resolution order: 1. `explicit` argument 2. Environment variable `env_var` 3. First readable file in `key_file_paths` with non-empty content Returns `None` if nothing is found. Never raises. --- ## find_project_root() Walks up from CWD looking for `pyproject.toml`. Returns the containing directory or `None`. Used by adapters to locate key files. --- ## LLMConfig `llm_connect.config.LLMConfig` Dataclass holding per-adapter configuration. Used directly by `OpenRouterAdapter` and `ClaudeCodeAdapter`. Not required by the Core `LLMAdapter` ABC. | Field | Default | |-------|---------| | `provider` | `"openrouter"` | | `model` | `"anthropic/claude-sonnet-4"` | | `api_key` | `None` | | `api_base` | `"https://openrouter.ai/api/v1"` | | `claude_cli_path` | `"claude"` | | `timeout_seconds` | `300` | | `max_retries` | `3` |