CodeDocs Vault

Configuration System

The configuration system uses Pydantic v2 settings with multiple sources and validation.

Overview

File: vibe/core/config.py Class: VibeConfig at line 297

Configuration Sources

Priority Order (highest to lowest)

  1. Constructor arguments (init_settings)
  2. Environment variables (VIBE_* prefix)
  3. TOML config file (TomlFileSettingsSource)
  4. Default values

Source Customization

# config.py:388-409
@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, ...]:
    return (
        init_settings,
        env_settings,
        TomlFileSettingsSource(settings_cls),
        file_secret_settings,
    )

File Locations

Path Resolution

Path resolution is split across two modules:

Global Paths: vibe/core/paths/global_paths.py

# global_paths.py:10-16
class GlobalPath:
    def __init__(self, resolver: Callable[[], Path]) -> None:
        self._resolver = resolver
 
    @property
    def path(self) -> Path:
        return self._resolver()

Global path constants:

Constant Resolves To
VIBE_HOME ~/.vibe (or $VIBE_HOME)
GLOBAL_CONFIG_FILE ~/.vibe/config.toml
GLOBAL_ENV_FILE ~/.vibe/.env
GLOBAL_TOOLS_DIR ~/.vibe/tools
GLOBAL_SKILLS_DIR ~/.vibe/skills
GLOBAL_AGENTS_DIR ~/.vibe/agents
GLOBAL_PROMPTS_DIR ~/.vibe/prompts
SESSION_LOG_DIR ~/.vibe/logs/session
TRUSTED_FOLDERS_FILE ~/.vibe/trusted_folders.toml
DEFAULT_TOOL_DIR vibe/core/tools/builtins

Config Paths (Trust-Aware): vibe/core/paths/config_paths.py

# config_paths.py:12-17
class ConfigPath(GlobalPath):
    @property
    def path(self) -> Path:
        if _config_paths_locked:
            raise RuntimeError("Config path is locked")
        return super().path

Config paths are locked until unlock_config_paths() is called after the trusted folder check. This prevents accidental resolution before the trust decision is made. See Trusted Folders for details.

File Hierarchy

~/.vibe/                    # Global config directory
├── config.toml             # Main configuration
├── .env                    # API keys (not in TOML)
├── instructions.md         # User instructions for system prompt
├── vibehistory             # Command history
├── trusted_folders.toml    # Trusted/untrusted directory decisions
├── vibe.log                # Application log
├── logs/
│   └── session/            # Session log directories
│       └── session_YYYYMMDD_HHMMSS_<id>/
│           ├── meta.json
│           └── messages.jsonl
├── agents/                 # Custom agent TOML files
│   └── myagent.toml
├── skills/                 # Custom skills
│   └── my-skill/
│       └── SKILL.md
├── prompts/                # Custom system prompts
│   └── myprompt.md
└── tools/                  # Custom tools
    └── my_tool.py

./.vibe/                    # Project-local (requires trusted folder)
├── config.toml
├── tools/
├── skills/
├── agents/
└── ...

VibeConfig Fields

Class: at config.py:297

Core Settings

# config.py:298-313
class VibeConfig(BaseSettings):
    active_model: str = "devstral-2"           # Model alias to use
    vim_keybindings: bool = False              # Enable vim mode in input
    disable_welcome_banner_animation: bool = False
    displayed_workdir: str = ""                # Override displayed path
    auto_compact_threshold: int = 200_000      # Token threshold for auto-compact
    context_warnings: bool = False             # Warn when approaching limit
    textual_theme: str = "textual-dark"        # TUI theme
    instructions: str = ""                     # Additional instructions
    workdir: Path | None = None                # Override working directory
    system_prompt_id: str = "cli"              # System prompt to use
    include_commit_signature: bool = True      # Include git signature in prompt
    include_model_info: bool = True            # Include model info in prompt
    include_project_context: bool = True       # Include project context
    include_prompt_detail: bool = True         # Include detailed prompts
    enable_update_checks: bool = True          # Check for version updates
    api_timeout: float = 720.0                 # API timeout in seconds

Providers and Models

# config.py:314-317
providers: list[ProviderConfig] = Field(default_factory=lambda: list(DEFAULT_PROVIDERS))
models: list[ModelConfig] = Field(default_factory=lambda: list(DEFAULT_MODELS))

Agent, Skill, and Tool Settings

# Additional VibeConfig fields
auto_approve: bool = False                  # Auto-approve all tool executions
 
# Tool configuration
tools: dict[str, BaseToolConfig]            # Per-tool configuration
tool_paths: list[str]                       # Custom tool directories
enabled_tools: list[str]                    # Tool whitelist patterns
disabled_tools: list[str]                   # Tool blacklist patterns
 
# Agent configuration
agent_paths: list[Path]                     # Custom agent directories
enabled_agents: list[str]                   # Agent whitelist patterns
disabled_agents: list[str]                  # Agent blacklist patterns
 
# Skill configuration
skill_paths: list[Path]                     # Custom skill directories
enabled_skills: list[str]                   # Skill whitelist patterns
disabled_skills: list[str]                  # Skill blacklist patterns

Sub-Configurations

project_context: ProjectContextConfig       # Project context settings
session_logging: SessionLoggingConfig       # Session logging settings
mcp_servers: list[MCPServer]                # MCP server configurations

Sub-Configuration Classes

ProjectContextConfig

# config.py:117-125
class ProjectContextConfig(BaseSettings):
    max_chars: int = 40_000                 # Max project context size
    default_commit_count: int = 5           # Recent commits to include
    max_doc_bytes: int = 32 * 1024          # Max doc file size
    truncation_buffer: int = 1_000
    max_depth: int = 3                      # Directory tree depth
    max_files: int = 1000                   # Max files in tree
    max_dirs_per_level: int = 20
    timeout_seconds: float = 2.0            # Context gathering timeout

SessionLoggingConfig

# config.py:128-143
class SessionLoggingConfig(BaseSettings):
    save_dir: str = ""                      # Log directory (default: ~/.vibe/logs/session)
    session_prefix: str = "session"         # Log file prefix
    enabled: bool = True                    # Enable session logging

ProviderConfig

# config.py:151-156
class ProviderConfig(BaseModel):
    name: str                               # Provider identifier
    api_base: str                           # API base URL
    api_key_env_var: str = ""              # Env var for API key
    api_style: str = "openai"              # API style
    backend: Backend = Backend.GENERIC      # Backend type

ModelConfig

# config.py:241-247
class ModelConfig(BaseModel):
    name: str                    # Model name for API
    provider: str                # Provider reference
    alias: str                   # User-friendly alias
    temperature: float = 0.2    # Default temperature
    input_price: float = 0.0    # Price per million input tokens
    output_price: float = 0.0   # Price per million output tokens

MCP Server Configuration

# config.py:159-238
class MCPHttp(_MCPBase, _MCPHttpFields):
    transport: Literal["http"]
 
class MCPStreamableHttp(_MCPBase, _MCPHttpFields):
    transport: Literal["streamable-http"]
 
class MCPStdio(_MCPBase):
    transport: Literal["stdio"]
    command: str | list[str]
    args: list[str] = Field(default_factory=list)
 
MCPServer = Annotated[MCPHttp | MCPStreamableHttp | MCPStdio, Field(discriminator="transport")]

TOML Configuration File

Example config.toml

# Model selection
active_model = "devstral-2"
 
# UI Settings
vim_keybindings = false
textual_theme = "textual-dark"
disable_welcome_banner_animation = false
 
# Features
include_project_context = true
include_model_info = true
include_commit_signature = true
include_prompt_detail = true
enable_update_checks = true
 
# Context Management
auto_compact_threshold = 200000
context_warnings = false
 
# System Prompt
system_prompt_id = "cli"
 
# API Timeout
api_timeout = 720.0
 
# Provider Configuration
[[providers]]
name = "mistral"
api_base = "https://api.mistral.ai/v1"
api_key_env_var = "MISTRAL_API_KEY"
backend = "mistral"
 
[[providers]]
name = "llamacpp"
api_base = "http://127.0.0.1:8080/v1"
api_key_env_var = ""
 
# Model Configuration
[[models]]
name = "mistral-vibe-cli-latest"
provider = "mistral"
alias = "devstral-2"
temperature = 0.2
input_price = 0.4
output_price = 2.0
 
[[models]]
name = "devstral-small-latest"
provider = "mistral"
alias = "devstral-small"
input_price = 0.1
output_price = 0.3
 
# Tool Configuration
[tools.bash]
permission = "always"
timeout = 60
 
[tools.read_file]
permission = "always"
max_read_bytes = 64000
 
[tools.write_file]
permission = "ask"
 
# Tool Enable/Disable Patterns
enabled_tools = []
disabled_tools = []
 
# MCP Server Configuration
[[mcp_servers]]
name = "fetch_server"
transport = "stdio"
command = "uvx"
args = ["mcp-server-fetch"]
 
[[mcp_servers]]
name = "my_http_server"
transport = "http"
url = "http://localhost:8000"
headers = { "Authorization" = "Bearer token" }
 
# Project Context
[project_context]
max_chars = 40000
max_files = 1000
max_depth = 3
timeout_seconds = 2.0
 
# Session Logging
[session_logging]
enabled = true
save_dir = "~/.vibe/logs/session"
session_prefix = "session"

Loading Configuration

Main Load Method

# config.py:564-569
@classmethod
def load(cls, agent: str | None = None, **overrides: Any) -> VibeConfig:
    cls._migrate()                          # Apply migrations
    agent_config = cls._get_agent_config(agent)  # Load agent config if specified
    init_data = {**(agent_config or {}), **overrides}
    return cls(**init_data)

Migration

# config.py:542-562
@classmethod
def _migrate(cls) -> None:
    """Apply configuration migrations."""
    if not CONFIG_FILE.exists():
        return
 
    config = tomllib.load(CONFIG_FILE.open("rb"))
    needs_save = False
 
    # Example migration: update auto_compact_threshold default
    if config.get("auto_compact_threshold") == 100_000:
        config["auto_compact_threshold"] = 200_000
        needs_save = True
 
    if needs_save:
        cls.dump_config(config)

Agent Configuration

# config.py:528-539
@classmethod
def _get_agent_config(cls, agent: str | None) -> dict[str, Any] | None:
    if agent is None:
        return None
 
    agent_config_path = (AGENT_DIR / agent).with_suffix(".toml")
    try:
        return tomllib.load(agent_config_path.open("rb"))
    except FileNotFoundError:
        raise ValueError(f"Config '{agent}.toml' not found in {AGENT_DIR}")

Validation

API Key Validation

# config.py:411-421
@model_validator(mode="after")
def _check_api_key(self) -> VibeConfig:
    active_model = self.get_active_model()
    provider = self.get_provider_for_model(active_model)
    api_key_env = provider.api_key_env_var
    if api_key_env and not os.getenv(api_key_env):
        raise MissingAPIKeyError(api_key_env, provider.name)
    return self

Backend Compatibility

# config.py:423-442
@model_validator(mode="after")
def _check_api_backend_compatibility(self) -> VibeConfig:
    active_model = self.get_active_model()
    provider = self.get_provider_for_model(active_model)
    MISTRAL_API_BASES = ["https://codestral.mistral.ai", "https://api.mistral.ai"]
    is_mistral_api = any(provider.api_base.startswith(base) for base in MISTRAL_API_BASES)
 
    if (is_mistral_api and provider.backend != Backend.MISTRAL) or \
       (not is_mistral_api and provider.backend != Backend.GENERIC):
        raise WrongBackendError(provider.backend, is_mistral_api)
    return self

Model Alias Uniqueness

# config.py:477-486
@model_validator(mode="after")
def _validate_model_uniqueness(self) -> VibeConfig:
    seen_aliases: set[str] = set()
    for model in self.models:
        if model.alias in seen_aliases:
            raise ValueError(f"Duplicate model alias: '{model.alias}'")
        seen_aliases.add(model.alias)
    return self

Saving Configuration

# config.py:493-526
@classmethod
def save_updates(cls, updates: dict[str, Any]) -> None:
    CONFIG_DIR.mkdir(parents=True, exist_ok=True)
    current_config = TomlFileSettingsSource(cls).toml_data
 
    def deep_merge(target: dict, source: dict) -> None:
        for key, value in source.items():
            if key in target and isinstance(target[key], dict) and isinstance(value, dict):
                deep_merge(target[key], value)
            elif key in target and isinstance(target[key], list) and isinstance(value, list):
                if key in {"providers", "models"}:
                    target[key] = value  # Replace lists
                else:
                    target[key] = list(set(value + target[key]))  # Merge
            else:
                target[key] = value
 
    deep_merge(current_config, updates)
    cls.dump_config(current_config)
 
@classmethod
def dump_config(cls, config: dict[str, Any]) -> None:
    with CONFIG_FILE.open("wb") as f:
        tomli_w.dump(config, f)

Environment Variables

API Keys

Loaded from ~/.vibe/.env:

# config.py:44-49
def load_api_keys_from_env() -> None:
    if GLOBAL_ENV_FILE.is_file():
        env_vars = dotenv_values(GLOBAL_ENV_FILE)
        for key, value in env_vars.items():
            if value:
                os.environ.setdefault(key, value)

Config Overrides

Environment variables with VIBE_ prefix override config values:

Helper Methods

# config.py:372-386
def get_active_model(self) -> ModelConfig:
    """Get the currently active model configuration."""
    for model in self.models:
        if model.alias == self.active_model:
            return model
    raise ValueError(f"Active model '{self.active_model}' not found")
 
def get_provider_for_model(self, model: ModelConfig) -> ProviderConfig:
    """Get the provider for a given model."""
    for provider in self.providers:
        if provider.name == model.provider:
            return provider
    raise ValueError(f"Provider '{model.provider}' not found")

Source File References

File Key Lines Description
paths/global_paths.py:10-16 GlobalPath Lazy path resolver class
paths/global_paths.py:28-40 Constants All global path constants
paths/config_paths.py:12-17 ConfigPath Trust-locked path resolver
paths/config_paths.py:20-31 _resolve_config_path() Trust-aware config resolution
paths/config_paths.py:58-60 unlock_config_paths() Unlock after trust check
config.py:44-49 load_api_keys_from_env() API key loading
config.py:91-114 TomlFileSettingsSource TOML source implementation
config.py:117-125 ProjectContextConfig Project context settings
config.py:128-143 SessionLoggingConfig Session logging settings
config.py:151-156 ProviderConfig Provider configuration
config.py:241-247 ModelConfig Model configuration
config.py:297-354 VibeConfig Main configuration class
config.py:388-409 settings_customise_sources() Source priority
config.py:411-442 Validators API key and backend validation
config.py:493-526 save_updates() Config persistence
config.py:564-569 load() Main load method