## File: README.md # Mistral Vibe [](https://pypi.org/project/mistral-vibe) [](https://www.python.org/downloads/release/python-3120/) [](https://github.com/mistralai/mistral-vibe/actions/workflows/ci.yml) [](https://github.com/mistralai/mistral-vibe/blob/main/LICENSE) ``` ██████████████████░░ ██████████████████░░ ████ ██████ ████░░ ████ ██ ████░░ ████ ████░░ ████ ██ ██ ████░░ ██ ██ ██░░ ██████████████████░░ ██████████████████░░ ``` **Mistral's open-source CLI coding assistant.** Mistral Vibe is a command-line coding assistant powered by Mistral's models. It provides a conversational interface to your codebase, allowing you to use natural language to explore, modify, and interact with your projects through a powerful set of tools. > [!WARNING] > Mistral Vibe works on Windows, but we officially support and target UNIX environments. ### One-line install (recommended) **Linux and macOS** ```bash curl -LsSf https://mistral.ai/vibe/install.sh | bash ``` **Windows** First, install uv ```bash powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" ``` Then, use uv command below. ### Using uv ```bash uv tool install mistral-vibe ``` ### Using pip ```bash pip install mistral-vibe ``` ## Table of Contents - [Features](#features) - [Built-in Agents](#built-in-agents) - [Subagents and Task Delegation](#subagents-and-task-delegation) - [Interactive User Questions](#interactive-user-questions) - [Terminal Requirements](#terminal-requirements) - [Quick Start](#quick-start) - [Usage](#usage) - [Interactive Mode](#interactive-mode) - [Trust Folder System](#trust-folder-system) - [Programmatic Mode](#programmatic-mode) - [Voice Mode](#voice-mode) - [Slash Commands](#slash-commands) - [Built-in Slash Commands](#built-in-slash-commands) - [Custom Slash Commands via Skills](#custom-slash-commands-via-skills) - [Skills System](#skills-system) - [Creating Skills](#creating-skills) - [Skill Discovery](#skill-discovery) - [Managing Skills](#managing-skills) - [Configuration](#configuration) - [Configuration File Location](#configuration-file-location) - [API Key Configuration](#api-key-configuration) - [OpenTelemetry Tracing](#opentelemetry-tracing) - [Custom System Prompts](#custom-system-prompts) - [Custom Agent Configurations](#custom-agent-configurations) - [Tool Management](#tool-management) - [MCP Server Configuration](#mcp-server-configuration) - [Session Management](#session-management) - [Update Settings](#update-settings) - [Custom Vibe Home Directory](#custom-vibe-home-directory) - [Editors/IDEs](#editorsides) - [Resources](#resources) - [Data collection & usage](#data-collection--usage) - [License](#license) ## Features - **Interactive Chat**: A conversational AI agent that understands your requests and breaks down complex tasks. - **Powerful Toolset**: A suite of tools for file manipulation, code searching, version control, and command execution, right from the chat prompt. - Read, write, and patch files (`read`, `write_file`, `edit`). - Execute shell commands, with managed shell sessions, polling, and stdin helpers available during rollout. - Recursively search code with `grep` (with `ripgrep` support). - Manage a `todo` list to track the agent's work. - Ask interactive questions to gather user input (`ask_user_question`). - Delegate tasks to subagents for parallel work (`task`). - **Project-Aware Context**: Vibe automatically scans your project's file structure and Git status to provide relevant context to the agent, improving its understanding of your codebase. - **Advanced CLI Experience**: Built with modern libraries for a smooth and efficient workflow. - Autocompletion for slash commands (`/`) and file paths (`@`). - Image attachments via `@` mentions — `.png`, `.jpg`, `.jpeg`, `.gif`, `.webp` files are sent to vision-capable models (e.g. Mistral Medium 3.5) as native multimodal content. - Persistent command history. - Beautiful Themes. - **Highly Configurable**: Customize models, providers, tool permissions, and UI preferences through a simple `config.toml` file. - **Safety First**: Features tool execution approval. - **Multiple Built-in Agents**: Choose from different agent profiles tailored for specific workflows. ### Built-in Agents Vibe comes with several built-in agent profiles, each designed for different use cases: - **`ask`**: Requires approval for tool executions. - **`plan`**: Read-only agent for exploration and planning. Auto-approves safe tools like `grep` and `read`. - **`accept-edits`**: The default agent. Auto-approves file edits only (`write_file`, `edit`). Useful for code refactoring. - **`auto-approve`**: Auto-approves all tool executions. Use with caution. Use the `--agent` flag to select a different agent: ```bash vibe --agent plan ``` To change the default agent used when `--agent` is not passed, set `default_agent` in your `config.toml`: ```toml default_agent = "plan" ``` Valid values are `ask`, `plan`, `accept-edits`, `auto-approve`, `lean` (only when listed in `installed_agents`), or the name of any custom agent file in `~/.vibe/agents/` or the project's `.vibe/agents/` directory. Subagents such as `explore` are not accepted. > Note: `default_agent` applies in both interactive and programmatic > (`-p` / `--prompt`) sessions. Pass `--auto-approve` or `--yolo` with any > agent when a run should approve all tool calls without prompting. ### Subagents and Task Delegation Vibe supports subagents for delegating tasks. Subagents run independently and can perform specialized work without user interaction, preventing the context from being overloaded. The `task` tool allows the agent to delegate work to subagents: ``` > Can you explore the codebase structure while I work on something else? 🤖 I'll use the task tool to delegate this to the explore subagent. > task(task="Analyze the project structure and architecture", agent="explore") ``` Create custom subagents by adding `agent_type = "subagent"` to your agent configuration. Vibe comes with a built-in subagent called `explore`, a read-only subagent for codebase exploration and skill loading used internally for delegation. ### Interactive User Questions The `ask_user_question` tool allows the agent to ask you clarifying questions during its work. This enables more interactive and collaborative workflows. ``` > Can you help me refactor this function? 🤖 I need to understand your requirements better before proceeding. > ask_user_question(questions=[{ "question": "What's the main goal of this refactoring?", "options": [ {"label": "Performance", "description": "Make it run faster"}, {"label": "Readability", "description": "Make it easier to understand"}, {"label": "Maintainability", "description": "Make it easier to modify"} ] }]) ``` The agent can ask multiple questions at once, displayed as tabs. Each question supports 2-4 options plus an automatic "Other" option for free text responses. ## Terminal Requirements Vibe's interactive interface requires a modern terminal emulator. Recommended terminal emulators include: - **WezTerm** (cross-platform) - **Alacritty** (cross-platform) - **Ghostty** (Linux and macOS) - **Kitty** (Linux and macOS) Most modern terminals should work, but older or minimal terminal emulators may have display issues. ## Quick Start 1. Navigate to your project's root directory: ```bash cd /path/to/your/project ``` 2. Run Vibe: ```bash vibe ``` 3. If this is your first time running Vibe, it will: - Use built-in defaults without creating a configuration file until you save a setting - Prompt you to enter your API key if it's not already configured - Save your API key to `~/.vibe/.env` for future use Alternatively, you can configure your API key separately using `vibe --setup`. 4. Start interacting with the agent! ``` > Can you find all instances of the word "TODO" in the project? 🤖 The user wants to find all instances of "TODO". The `grep` tool is perfect for this. I will use it to search the current directory. > grep(pattern="TODO", path=".") ... (grep tool output) ... 🤖 I found the following "TODO" comments in your project. ``` ## Usage ### Interactive Mode Simply run `vibe` to enter the interactive chat loop. - **Multi-line Input**: Press `Ctrl+J` or `Shift+Enter` for select terminals to insert a newline. - **File Paths**: Reference files in your prompt using the `@` symbol for smart autocompletion (e.g., `> Read the file @src/agent.py`). - **Shell Commands**: Prefix any command with `!` to execute it directly in your shell, bypassing the agent (e.g., `> !ls -l`). - **External Editor**: Press `Ctrl+G` to edit your current input in an external editor. - **Tool Output Toggle**: Press `Ctrl+O` to toggle the tool output view. - **Todo View Toggle**: Press `Ctrl+T` to toggle the todo list view. - **Debug Console**: Press `Ctrl+\` to toggle the debug console. - **Agent Selection**: Press `Shift+Tab` to cycle through agents (ask, plan, ...). - **Exit**: Type `/exit`, `exit`, `quit`, `:q`, or `:quit` in the input box, or press `Ctrl+C` / `Ctrl+D` twice within ~1 second. Set `ask_confirmation_on_exit = false` (or toggle it in `/config`) to make `Ctrl+D` quit on the first press; `Ctrl+C` always requires confirmation. ### Copying & Text Selection - **Copy**: Use `Ctrl+Y` or `Ctrl+Shift+C` to copy the current selection to clipboard. With autocopy enabled (default via `autocopy_to_clipboard = true`), mouse selection automatically copies on release and shows a brief confirmation. - **Multi-click selection**: Double-click selects a word, triple-click selects the paragraph. Dragging extends the selection at the same granularity. You can start Vibe with a prompt using the following command: ```bash vibe "Refactor the main function in cli/main.py to be more modular." ``` ### Trust Folder System Vibe includes a trust folder system to ensure you only run the agent in directories you trust. When you first run Vibe in a new directory which contains a `.vibe` subfolder, it may ask you to confirm whether you trust the folder. Trusted folders are remembered for future sessions. You can manage trusted folders through its configuration file `~/.vibe/trusted_folders.toml`. This safety feature helps prevent accidental execution in sensitive directories. ### Programmatic Mode You can run Vibe non-interactively by piping input or using the `--prompt` flag. This is useful for scripting. ```bash vibe --prompt "Refactor the main function in cli/main.py to be more modular." ``` By default, it uses your configured `default_agent` (`accept-edits` unless changed). To approve all tool calls without prompting, pass `--auto-approve` or `--yolo` (also available for interactive sessions): ```bash vibe --prompt "Refactor the main function in cli/main.py to be more modular." --auto-approve ``` #### Programmatic Mode Options When using `--prompt`, you can specify additional options: - **`--max-turns N`**: Limit the maximum number of assistant turns. The session will stop after N turns. - **`--max-price DOLLARS`**: Set a maximum cost limit in dollars. The session will be interrupted if the cost exceeds this limit. - **`--max-tokens N`**: Set a maximum cumulative LLM token budget for the session, counting both prompt and completion tokens. The session will be interrupted if usage exceeds this limit. - **`--agent NAME`**: Select the agent profile for this run. - **`--auto-approve`, `--yolo`**: Approves all tool calls without prompting, including in interactive sessions. Can be combined with any `--agent` value. - **`--enabled-tools TOOL`**: Enable specific tools. In programmatic mode, this disables all other tools. Can be specified multiple times. Supports exact names, glob patterns (e.g., `bash*`), or regex with `re:` prefix (e.g., `re:^serena_.*$`). - **`--disabled-tools TOOL`**: Disable specific tools after `--enabled-tools` filtering. Can be specified multiple times. Supports exact names, glob patterns (e.g., `bash*`), or regex with `re:` prefix (e.g., `re:^serena_.*$`). - **`--output FORMAT`**: Set the output format. Options: - `text` (default): Human-readable text output - `json`: All messages as JSON at the end - `streaming`: Newline-delimited JSON per message Example: ```bash vibe --prompt "Analyze the codebase" --max-turns 5 --max-price 1.0 --max-tokens 50000 --output json ``` ## Voice Mode > [!WARNING] > Voice mode is experimental and may change in future releases. Voice mode allows you to dictate input using your microphone instead of typing. ### Activating Voice Mode Toggle voice mode on or off with the `/voice` slash command: ``` > /voice ``` ### Recording Shortcuts | Shortcut | Action | | -------- | ---------------- | | `Ctrl+R` | Start recording | | Any key | Stop recording | | `Escape` | Cancel recording | | `Ctrl+C` | Cancel recording | ## Slash Commands Use slash commands for meta-actions and configuration changes during a session. ### Built-in Slash Commands Vibe provides several built-in slash commands. Use slash commands by typing them in the input box: ``` > /help ``` If a model response is interrupted by a backend error, use `/retry` to continue from the partial response. Add optional guidance after the command, for example `/retry keep the conclusion concise`. ### Custom Slash Commands via Skills You can define your own slash commands through the skills system. Skills are reusable components that extend Vibe's functionality. To create a custom slash command: 1. Create a skill directory with a `SKILL.md` file 2. Set `user-invocable = true` in the skill metadata 3. Define the command logic in your skill Example skill metadata: ```markdown --- name: my-skill user-invocable: true --- ``` Custom slash commands appear in the autocompletion menu alongside built-in commands. ## Skills System Vibe's skills system allows you to extend functionality through reusable components. Skills can add new tools, slash commands, and specialized behaviors. Vibe follows the [Agent Skills specification](https://agentskills.io/specification) for skill format and structure. ### Creating Skills Skills are defined in directories with a `SKILL.md` file containing metadata in YAML frontmatter. For example, `~/.vibe/skills/code-review/SKILL.md`: ```markdown --- name: code-review license: MIT compatibility: Python 3.12+ user-invocable: true allowed-tools: - read - grep - ask_user_question --- # Code Review Skill This skill helps analyze code quality and suggest improvements. ``` ### Skill Discovery Vibe discovers skills from multiple locations: 1. **Custom paths**: Configured in `config.toml` via `skill_paths` 2. **Standard Agent Skills path** (project root, trusted folders only): `.agents/skills/` — [Agent Skills](https://agentskills.io) standard 3. **Local project skills** (project root, trusted folders only): `.vibe/skills/` in your project 4. **Global skills directories**: `~/.vibe/skills/` and `~/.agents/skills/` ```toml skill_paths = ["/path/to/custom/skills"] ``` ### Managing Skills Enable or disable skills using patterns in your configuration: ```toml # Enable specific skills enabled_skills = ["code-review", "test-*"] # Disable specific skills disabled_skills = ["experimental-*"] ``` Skills support the same pattern matching as tools (exact names, glob patterns, and regex). ## Configuration ### Configuration File Location Vibe is configured via a `config.toml` file. It looks for this file first in `./.vibe/config.toml` and then falls back to `~/.vibe/config.toml`. ### Theme The default `auto` theme follows the terminal background when it can be detected, then the operating-system light/dark preference. Choose another theme with `/theme` or set it explicitly: ```toml theme = "dracula" ``` ### API Key Configuration To use Vibe, you'll need a Mistral API key. You can obtain one by signing up at [https://console.mistral.ai](https://console.mistral.ai). You can configure your API key using `vibe --setup`, or through one of the methods below. Vibe supports multiple ways to configure your API keys: 1. **Interactive Setup (Recommended for first-time users)**: When you run Vibe for the first time or if your API key is missing, Vibe will prompt you to enter it. The key will be securely saved to `~/.vibe/.env` for future sessions. 2. **Environment Variables**: Set your API key as an environment variable: ```bash export MISTRAL_API_KEY="your_mistral_api_key" ``` 3. **`.env` File**: Create a `.env` file in `~/.vibe/` and add your API keys: ```bash MISTRAL_API_KEY=your_mistral_api_key ``` Vibe automatically loads API keys from `~/.vibe/.env` on startup. Environment variables take precedence over the `.env` file if both are set. **Note**: The `.env` file is specifically for API keys and other provider credentials. General Vibe configuration should be done in `config.toml`. ### Custom Domains If you use a Mistral-compatible deployment instead of the default `console.mistral.ai` / `api.mistral.ai`, you can point browser sign-in at it. The credential is still a Mistral API key. Run `vibe --setup`, choose **Launch browser** then **Other**, enter your login domain, and sign in through the browser. A bare domain is prefixed with `https://`, and the auth API base is derived as `DOMAIN/api`. The overridden `mistral` provider is saved to your user config so subsequent runs reuse it. **Note**: the wizard reads any custom `browser_auth_base_url` already set in `config.toml`. Choosing **Other** pre-fills that configured domain so you can confirm or edit it. Choosing **Mistral AI** while a custom domain is configured warns you first — press **Enter** again to confirm the reset to the default domain, which is then persisted. ### TLS and Corporate Certificate Authorities By default, Vibe uses the bundled `certifi` certificate roots for outbound HTTPS requests. If your organization installs private certificate authorities in the operating system trust store, you can opt in to the system trust store in `config.toml`: ```toml enable_system_trust_store = true ``` `SSL_CERT_FILE` and `SSL_CERT_DIR` are still supported and are loaded as additional trust anchors. ### OpenTelemetry Tracing Vibe can export traces for agent, model, and tool operations over OTLP/HTTP. Enable tracing in `config.toml`: ```toml enable_otel = true ``` By default, Vibe sends traces to the telemetry endpoint associated with the configured Mistral provider and authenticates with that provider's API key. `enable_telemetry` must also remain enabled. To send traces to another collector, configure its base URL. Vibe appends `/v1/traces`; configure authentication with the standard `OTEL_EXPORTER_OTLP_*` environment variables when needed. ```toml enable_otel = true otel_endpoint = "https://collector.example.com:4318" ``` Span attributes are redacted on the client before export. The default mode redacts sensitive values, `strict` redacts sensitive attributes entirely, and `none` disables redaction: ```toml otel_redaction = "default" # "default", "strict", or "none" ``` Use `none` only when the collector is trusted to receive potentially sensitive prompt, response, and tool data. ### Custom System Prompts You can create `AGENTS.md` files to add custom instructions. You can also replace the entire system prompt. Place `AGENTS.md` files in: - `~/.vibe/AGENTS.md` — user-level instructions for all projects - Project directories — project-specific instructions, loaded from cwd up to the trust root Priority: closer directories override more distant ones. Instructions in `AGENTS.md` override the default system prompt. Files are only loaded for trusted folders. Custom system prompts entirely replace the default one (`prompts/cli.md`). Create a markdown file in the `~/.vibe/prompts/` directory with your custom prompt content. To use a custom system prompt, set the `system_prompt_id` in your configuration to match the filename (without the `.md` extension): ```toml # Use a custom system prompt system_prompt_id = "my_custom_prompt" ``` This will load the prompt from `~/.vibe/prompts/my_custom_prompt.md`. Project-local prompts in `.vibe/prompts/` are also supported and override user-level prompts with the same name. This applies to all custom prompts (system and compaction). ### Custom Compaction Prompts Compaction uses the built-in prompt at `prompts/compact.md` by default. You can replace it with a custom prompt from `~/.vibe/prompts/` (or `.vibe/prompts/`) using the same resolution rules as system prompts. To use a custom compaction prompt, set `compaction_prompt_id` in your configuration to match the filename (without the `.md` extension): ```toml # Use a custom compaction prompt compaction_prompt_id = "my_compaction_prompt" ``` Any extra instructions passed to `/compact ...` are appended after the configured compaction prompt. Compaction keeps the same session and visible conversation. Later model requests use the latest compacted context followed by newer messages. ### Custom Agent Configurations You can create custom agent configurations for specific use cases (e.g., red-teaming, specialized tasks) by adding agent-specific TOML files in the `~/.vibe/agents/` directory. To use a custom agent, run Vibe with the `--agent` flag: ```bash vibe --agent my_custom_agent ``` Vibe will look for a file named `my_custom_agent.toml` in the agents directory and apply its configuration. Example custom agent configuration (`~/.vibe/agents/redteam.toml`): ```toml # Custom agent configuration for red-teaming active_model = "mistral-medium-3.5" system_prompt_id = "redteam" # Disable some tools for this agent disabled_tools = ["edit", "write_file"] # Override tool permissions for this agent [tools.bash] permission = "always" [tools.read] permission = "always" ``` Note: This implies that you have set up a redteam prompt named `~/.vibe/prompts/redteam.md`. ### Tool Management The built-in shell surface is controlled by the `managed_shell_tools_enabled` config field and the `vibe_cli_managed_shell_tools` GrowthBook experiment. The default variant keeps the legacy one-shot `bash` tool, including its existing Windows behavior. The managed variant exposes OS-native shell tools: POSIX systems, including WSL where Vibe runs as Linux, get managed `bash`, `bash_output`, `bash_stdin`, `bash_sessions`, and `bash_log_file`; native Windows gets `git_bash`, `git_bash_output`, `git_bash_stdin`, `git_bash_sessions`, and `git_bash_log_file` when Git Bash is available. If Git Bash is unavailable, native Windows falls back to `powershell`, `powershell_output`, `powershell_stdin`, `powershell_sessions`, and `powershell_log_file`. Managed shell sessions return a `session_id`, inline output, a cursor for polling more output, and a log path under `~/.vibe/shell-tool/sessions/`. Long-running commands can be left alive with `background = true`, and interactive commands can be driven with the matching stdin tool. POSIX `bash` reads permissions, allowlists, and denylists from `[tools.bash]`. Native Windows `git_bash` reads them from `[tools.git_bash]`; native Windows `powershell` reads them from `[tools.powershell]`. Neither Windows tool reads `[tools.bash]`. Git Bash is preferred when Vibe can resolve a usable `bash.exe` from PATH, Git for Windows, or standard Git install locations. If Git Bash is unavailable, the PowerShell resolution order is `pwsh.exe`, then `powershell.exe`. `cmd.exe` is not used by the managed Windows shell tools. ```toml [tools.git_bash] permission = "ask" shell = "C:\\Program Files\\Git\\bin\\bash.exe" [tools.powershell] permission = "ask" shell = "powershell.exe" ``` The rollout assignment is server-managed and is not a `config.toml` option. #### Enable/Disable Tools with Patterns You can control which tools are active using `enabled_tools` and `disabled_tools`. These fields support exact names, glob patterns, and regular expressions. When both are set, `enabled_tools` first narrows the tool set, then `disabled_tools` removes matching tools from that set. Examples: ```toml # Only enable tools that start with "serena_" (glob) enabled_tools = ["serena_*"] # Regex (prefix with re:) — matches full tool name (case-insensitive) enabled_tools = ["re:^serena_.*$"] # Disable a group with glob; everything else stays enabled disabled_tools = ["mcp_*", "grep"] ``` Notes: - MCP tool names use underscores, e.g., `serena_list` not `serena.list`. - Regex patterns are matched against the full tool name using fullmatch. ### MCP Server Configuration You can configure MCP (Model Context Protocol) servers to extend Vibe's capabilities. Add MCP server configurations under the `mcp_servers` section: Remote MCP servers can be added non-interactively from the shell. Static auth is selected when `--api-key-env` or `--header` is provided; otherwise the server uses OAuth and starts browser login by default. ```bash vibe mcp add mistralai \ --url https://api.mistral.ai/mcp \ --transport streamable-http \ --api-key-env MISTRAL_API_KEY vibe mcp add linear \ --url https://mcp.linear.app/mcp vibe mcp remove mistralai ``` Use `--no-login` to persist an OAuth server without starting login. Static auth also supports repeatable `--header`, `--api-key-header`, `--api-key-format`, `--startup-timeout-sec`, and `--tool-timeout-sec`. Run `vibe mcp add --help` for the complete command reference. `vibe mcp remove ` removes the server from the user configuration. Removing an OAuth server also deletes its stored tokens, client information, and configuration fingerprint when available. Hosted OAuth MCP servers can also be added from inside Vibe: ```text /mcp add https://mcp.linear.app/mcp /mcp add https://mcp.example.com/mcp --name docs --scope read --transport http --no-login ``` `/mcp add` is OAuth-only. It writes `auth.type = "oauth"` with optional scopes and starts login by default. It uses `transport = "streamable-http"` unless you pass `--transport http`. Pass `--no-login` to add the server without starting OAuth login. The shortcut supports `streamable-http` and `http` transports. ```toml # Example MCP server configurations [[mcp_servers]] name = "my_http_server" transport = "http" url = "http://localhost:8000" [mcp_servers.auth] type = "static" headers = { "X-Client" = "vibe" } api_key_env = "MY_API_KEY_ENV_VAR" api_key_header = "Authorization" api_key_format = "Bearer {token}" [[mcp_servers]] name = "my_streamable_server" transport = "streamable-http" url = "http://localhost:8001" [mcp_servers.auth] type = "static" headers = { "X-Client" = "vibe" } [[mcp_servers]] name = "fetch_server" transport = "stdio" command = "uvx" args = ["mcp-server-fetch"] env = { "DEBUG" = "1", "LOG_LEVEL" = "info" } ``` Supported transports: - `http`: Standard HTTP transport - `streamable-http`: HTTP transport with streaming support - `stdio`: Standard input/output transport (for local processes) Key fields: - `name`: A short alias for the server (used in tool names) - `transport`: The transport type - `url`: Base URL for HTTP transports - `headers`: Additional HTTP headers - `api_key_env`: Environment variable containing the API key - `command`: Command to run for stdio transport - `args`: Additional arguments for stdio transport - `startup_timeout_sec`: Timeout in seconds for the server to start and initialize (default 10s) - `tool_timeout_sec`: Timeout in seconds for tool execution (default 60s) - `env`: Environment variables to set for the MCP server of transport type stdio HTTP MCP servers can use either static auth or OAuth. Both use an `auth` block; legacy top-level `api_key_env` / `headers` keys are still accepted and promoted to static auth when Vibe loads the configuration. ```toml [[mcp_servers]] name = "linear" transport = "streamable-http" url = "https://mcp.linear.app/mcp" [mcp_servers.auth] type = "oauth" scopes = [] ``` MCP tools are named using the pattern `{server_name}_{tool_name}` and can be configured with permissions like built-in tools: ```toml # Configure permissions for specific MCP tools [tools.fetch_server_get] permission = "always" [tools.my_http_server_query] permission = "ask" ``` MCP server configurations support additional features: - **Environment variables**: Set environment variables for MCP servers - **Custom timeouts**: Configure startup and tool execution timeouts Example with environment variables and timeouts: ```toml [[mcp_servers]] name = "my_server" transport = "http" url = "http://localhost:8000" env = { "DEBUG" = "1", "LOG_LEVEL" = "info" } startup_timeout_sec = 15 tool_timeout_sec = 120 ``` ### Hooks Hooks wire arbitrary shell commands into Vibe's lifecycle to gate, audit, or rewrite agent behavior. No flag is required — declaring a hook is enough. Declared in `/.vibe/hooks.toml` (project, loaded first; trusted only) and `~/.vibe/hooks.toml` (user-global, loaded second; duplicates by `name` lose to the project entry): ```toml [[hooks]] name = "deny-rm-rf" type = "pre_tool" match = "bash" # tool-name matcher (fnmatch glob + `re:` regex escape, case-insensitive) command = "uv run python /path/to/guard-bash" timeout = 60.0 # seconds; default 60 for all hooks strict = false # tool hooks only: turn failures into denials (pre) / text-clears (post) description = "Reject dangerous shell commands." ``` Subagents inherit the parent's hook config so policies apply transitively. #### Common ground Every hook is spawned with a JSON invocation on **stdin** (UTF-8) containing the session context: `session_id`, `parent_session_id`, `transcript_path`, `cwd`, plus `hook_event_name` discriminating the hook type. Tool hooks add tool-specific fields (below). Every hook signals back via its **exit code** and **stdout**. The contract on stdout is strict: either empty (do nothing), or a JSON object matching the schema below. Use **stderr** for diagnostics / debug logs. - **Exit `0`, empty stdout** — passthrough. - **Exit `0`, valid JSON object on stdout** — structured response. Universal top-level fields: - `system_message` (string, optional) — shown to the user in the UI. - `decision` (`"allow"` | `"deny"`, optional, default `"allow"`) — the effect of `"deny"` depends on the hook type. - `reason` (string, optional) — accompanies `decision: "deny"`. - Event-specific payload under `hook_specific_output`. - **Exit `0`, non-empty but non-conforming stdout** (free-form text, broken JSON, JSON scalar/array, schema mismatch) — treated as a hook failure with the parse error as the message. Warning by default; escalated to deny / clear under `strict = true` on a tool hook. - **Any non-zero exit / timeout / spawn failure** — same failure path. Diagnostic taken from stderr (falling back to stdout, then the exit code). Unknown JSON fields are tolerated at every level (forward-compatible). Fields that aren't meaningful for the current hook type are silently ignored. #### `post_agent` Fires after every assistant turn that ends without pending tool calls. - **Receives** (in addition to the session context): no extra fields. - **Can return**: - `decision: "deny"` + `reason` — `reason` is injected as a new user message asking for a retry. Capped at **3 retries per hook per user turn**; further denies become terminal warnings. - `system_message` — UI-only. #### `pre_tool` Fires per tool call, **before** the user permission prompt. First deny short-circuits remaining `pre_tool` hooks for that call. - **Receives** (in addition to the session context): `tool_name`, `tool_call_id`, `tool_input` (the model's raw arguments). - **Can return**: - `decision: "deny"` + `reason` — denies the tool call; `reason` becomes the tool error the LLM sees. - `hook_specific_output.tool_input` (object) — **full replacement** of the model's arguments. Re-validated against the tool's schema (validation failure → synthesized denial). Rewrites compose left-to-right across hooks. The rewritten arguments are also what the permission prompt displays, what the tool runs with, and what subsequent LLM turns see on the assistant message. - `system_message` — UI-only. #### `post_tool` Fires per tool call **if and only if the tool body actually ran**. `tool_status` is `success`, `failure`, or `cancelled` (cancellation during the tool body — cancellation is shielded so audit hooks still run). Does not fire when the tool never executed: `pre_tool` denial, user denial at the approval prompt, permission `NEVER`, or cancellation before the body started. - **Receives** (in addition to the session context): `tool_name`, `tool_call_id`, `tool_input` (post-rewrite), `tool_status`, `tool_output` (structured result dict; null on failure), `tool_output_text` (the running text the LLM will see, mutable by prior hooks), `tool_error`, `duration_ms`. - **Can return**: - `decision: "deny"` + `reason` — replaces `tool_output_text` with `reason`. Pipeline continues; subsequent hooks see the replacement. - `hook_specific_output.additional_context` (string) — **appended** (with a `\n` separator) to `tool_output_text`. Composes with a same-hook deny: deny replaces first, then `additional_context` is appended to the replacement. - `system_message` — UI-only. ### Session Management #### Session Continuation and Resumption Vibe supports continuing from previous sessions: - **`--continue`** or **`-c`**: Continue from the most recent saved session - **`--resume`**: Open an interactive session picker - **`--resume SESSION_ID`**: Resume a specific session by ID (supports partial matching) - **`/resume`** or **`/continue`**: Open the session picker from inside Vibe; press `D` twice to delete a local saved session. The active session cannot be deleted from this picker. ```bash # Continue from last session vibe --continue # Open session picker vibe --resume # Resume specific session vibe --resume abc123 ``` Session logging must be enabled in your configuration for these features to work. #### Working Directory Control Use the `--workdir` option to specify a working directory: ```bash vibe --workdir /path/to/project ``` This is useful when you want to run Vibe from a different location than your current directory. Use `--add-dir` (repeatable) to make additional directories available to the agent for the duration of the session: ```bash vibe --add-dir /path/to/other-project --add-dir /path/to/library ``` Each path is implicitly trusted (no trust prompt) and contributes its `AGENTS.md` and `.vibe/` configuration (tools, skills, agents, prompts, hooks) to the session. File-tool permissions treat each `--add-dir` path the same way as your primary working directory — reads and writes inside them don't require the "outside workdir" prompt. Nested paths collapse: passing `/repo` and `/repo/sub` is equivalent to passing just `/repo`. Use `--worktree NAME` to create (or reuse) a [git worktree](https://git-scm.com/docs/git-worktree) and run inside it: ```bash vibe --worktree my-feature ``` The worktree lives under `$VIBE_HOME/worktrees/-/NAME` and is checked out on a branch named `NAME` (created if it doesn't exist, attached if it does). Vibe `cd`s into it before the session starts and trusts it for the session (no trust prompt). If you start Vibe from a subdirectory, Vibe enters the matching subdirectory inside the worktree. Existing worktrees are reused only when they belong to the same git repository and are checked out on branch `NAME`; otherwise Vibe exits with an error instead of running in the wrong checkout. Pass `--worktree` with no name to have Vibe name one for you: ```bash vibe "Fix the login bug" --worktree # -> fix-the-login-bug, on vibe/fix-the-login-bug vibe --worktree # no prompt -> a random slug, e.g. brave-quiet-otter ``` The name comes from your prompt, shortened to whole words. Without a prompt — or when the prompt has nothing usable in it, such as emoji only — Vibe generates a random slug instead. Unlike the named form, this never reuses an existing worktree: Vibe claims a free name, adding `-2`, `-3` and so on if needed, so two sessions started at once can never land in the same checkout. The branch is always `vibe/`, matching the worktrees Le Chat Desktop creates. Order matters, because `--worktree` takes an optional value: `vibe --worktree "Fix the login bug"` reads the prompt as the *name*. Put the prompt first, or separate it with `--`: ```bash vibe --worktree -- "Fix the login bug" ``` Automatic cleanup only applies to worktrees Vibe created this run, and only after a session actually started — a startup failure (bad config, `--continue` with no sessions) never deletes anything, and a reused worktree is always left in place. When an interactive session exits, Vibe removes the worktree directory automatically if there are no uncommitted changes, untracked files, or commits beyond the commit where the worktree session started. If any of those exist, Vibe asks whether to keep or remove the worktree. When Vibe created the branch it is deleted alongside the worktree; a branch that already existed and was merely attached is kept unless you confirm its deletion. Keeping preserves the directory and branch so you can return later; removing force-deletes them, discarding changes, untracked files, and commits. Programmatic runs (`vibe -p ... --worktree NAME`) do not clean up automatically because there is no exit prompt; remove them manually with `git worktree remove`. `--worktree` is ignored with `--setup` and `--check-upgrade`. Sessions are scoped per directory, so `-c`/`--continue` and the `--resume` picker only see sessions started inside that worktree. To carry a session across worktrees, resume it explicitly by ID with `--resume `. ### Update Settings Vibe checks PyPI at most once per day during a session. When a newer version is found, the next launch shows an update prompt before opening the chat, offering to either update immediately (via `uv tool upgrade mistral-vibe` or `brew upgrade mistral-vibe`) or continue with the current version. Run `vibe --check-upgrade` to check PyPI immediately, prompt to install a newer version if one exists, and exit. To disable the daily check entirely, add this to your `config.toml`: ```toml enable_update_checks = false ``` ### Notification Settings Vibe can notify you when the agent needs your attention (awaiting approval, asking a question, or task complete). This is useful when you switch to another window while the agent works. To disable notifications: ```toml enable_notifications = false ``` ### Custom Vibe Home Directory By default, Vibe stores its configuration in `~/.vibe/`. You can override this by setting the `VIBE_HOME` environment variable: ```bash export VIBE_HOME="/path/to/custom/vibe/home" ``` This affects where Vibe looks for: - `config.toml` - Main configuration - `.env` - API keys - `connector_bootstrap_cache.json` - Short-lived connector discovery cache - `agents/` - Custom agent configurations - `prompts/` - Custom system and compaction prompts - `tools/` - Custom tools - `logs/` - Session logs ## Editors/IDEs Mistral Vibe can be used in text editors and IDEs that support [Agent Client Protocol](https://agentclientprotocol.com/overview/clients). See the [ACP Setup documentation](docs/acp-setup.md) for setup instructions for various editors and IDEs. ## Resources - [CHANGELOG](CHANGELOG.md) - See what's new in each version - [CONTRIBUTING](CONTRIBUTING.md) - Guidelines for feature requests, feedback and bug reports ## Data collection & usage Use of Vibe is subject to our [Privacy Policy](https://legal.mistral.ai/terms/privacy-policy) and may include the collection and processing of data related to your use of the service, such as usage data, to operate, maintain, and improve Vibe. You can disable telemetry and crash reporting in your `config.toml` by setting `enable_telemetry = false`. ## License Copyright 2025 Mistral AI Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the [LICENSE](LICENSE) file for the full license text. --- ## File: docs/adr/0001-architecture-principles.md # 0001 Architecture Principles ## Decision Vibe should evolve toward pragmatic hexagonal architecture: core behavior depends on stable models and ports, while UI, filesystem, network, provider SDKs, subprocesses, and protocols live at the edges. The code should optimize for: - Fast startup and responsive interactive use. - Minimal, simple changes that fit the existing boundary. - Limited blast radius when a bug or feature changes. - Replaceable modules where integrations, transports, or providers may change. - Clear ownership of side effects. ## Rationale Vibe is an interactive CLI agent. Slow startup, broad edits, and tangled boundaries are user-visible. Architecture should make the common path fast and make change local. Ports are useful when they protect a real boundary or make tests simpler. They are not required for every small helper. ## Agent Guidance - Keep model execution, tool execution, and surface-neutral domain state in `vibe/core`. - Keep cross-surface session, runtime, resource, and protocol orchestration in `vibe.app_server`; delivery surfaces consume its public client API. - Put adapters for Textual, ACP, HTTP, files, subprocesses, provider SDKs, and local platform behavior outside the pure decision-making path when practical. - Prefer small edits in one owning module over scattered updates across many files. - Add an abstraction only when it reduces coupling, replaces duplication, or matches an existing boundary. - Preserve startup time: avoid eager imports, eager network calls, broad filesystem scans, and heavyweight initialization on the launch path. ## Flag To User When - A change is easy in the current code only by coupling two surfaces or spreading behavior across many files. - A delivery surface needs a live core object instead of an app-server request, event, or public resource view. - A new dependency would run on startup, global import, or every turn without a clear need. - A shortcut makes future replacement of a provider, tool, UI, or storage layer harder. --- ## File: docs/adr/0002-core-engine-and-delivery-surfaces.md # 0002 Core Engine And Delivery Surfaces ## Decision `vibe/core` supplies reusable private engine implementations: agent loop, tools, LLM backends, config, sessions, skills, hooks, telemetry types, and shared domain models. `vibe/app_server` is the harness and runtime composition root that owns those objects for delivery surfaces. Delivery surfaces adapt that engine: - `vibe/cli` owns the Textual app, terminal UX, widgets, slash-command presentation, voice UI, and local interactive affordances. Model-visible shell execution crosses the app server. - `vibe/acp` owns thin Agent Client Protocol translation and ACP-specific presentation updates over the app-server client API. - `vibe/setup` owns first-run and onboarding flows. - Programmatic entry points consume the app-server client API without depending on Textual, ACP internals, or a live core object. `AppServerHost` is the passive pre-session facade for listing, reading, deleting, trust, and session opening. `AppServerSession` is the attached facade for turns, callbacks, resources, and live events. Textual, ACP, and programmatic mode use these public facades rather than private app-server modules. ## Rationale The same engine must serve multiple clients. UI or protocol behavior should not leak into core decisions because it makes the engine harder to test, reuse, and replace. ## Agent Guidance - Add user-interface behavior in `vibe/cli`, not `vibe/core`. - Add protocol translation in `vibe/acp`, not `vibe/core`. - Keep core events and models surface-neutral. Let surfaces render or translate them. - Route every delivery surface through typed app-server requests, public events, and resource views; do not create a second core adapter for ACP or `-p` mode. - Shared behavior belongs in core only when it is truly independent of the delivery surface. - When a surface needs special behavior, prefer an adapter or subclass at that surface boundary. ## Flag To User When - Implementing a feature requires `vibe/core` to import Textual, ACP schema objects, or setup UI code. - A protocol-specific or UI-specific workaround is being added to a core model. - A change makes programmatic mode depend on interactive terminal assumptions. --- ## File: docs/adr/0003-event-driven-agent-loop.md # 0003 Event Driven Agent Loop ## Decision The agent loop communicates through typed events and streaming async generators. It owns model and tool execution. The app server owns the external session, turn, callback, and delivery lifecycle and projects the core stream into public client events. Events are the contract for assistant output, reasoning, user messages, tool calls, tool streams, tool results, approvals, compaction, plan review, title updates, hooks, teleport, and related lifecycle changes. ## Rationale Streaming keeps the CLI responsive and lets ACP/programmatic consumers observe the same underlying process. Typed events keep UI rendering, protocol translation, logging, and tests from reaching into agent-loop internals. ## Agent Guidance - Prefer adding or extending typed events over adding surface-specific callbacks into the agent loop. - Keep event payloads small, serializable where practical, and meaningful outside one UI. - Use `asyncio.create_task` and queues for explicit concurrent flows; avoid hiding orchestration in broad `gather` calls. - Keep long-running work cancellable and make cancellation visible through existing event/result paths. - Do not make consumers inspect private agent-loop state to understand what happened. - Route core events to delivery surfaces through `vibe.app_server`; a delivery surface must not consume `AgentLoop.act()` directly. - Keep public session event IDs monotonic. Duplicate notifications are ignored; a gap is recovered by replacing the client projection from `session/read`. ## Flag To User When - A feature requires the UI or ACP layer to read or mutate private loop state. - A new event is useful for one surface but would be confusing or impossible for another surface to consume. - A change blocks streaming responsiveness or delays visible feedback until a whole turn completes. --- ## File: docs/adr/0004-typed-permissioned-tools.md # 0004 Typed Permissioned Tools ## Decision Tools are typed, permissioned ports into side effects. A tool has Pydantic args, result, config, and state types, and runs through `BaseTool`. Permission policy is part of the tool contract. Tools that touch files, processes, network, or external services must declare and honor permissions consistently. Surface-specific tool behavior should adapt the same core contract rather than fork the domain semantics. Shell permission analysis covers semantic execution rather than only top-level command text. It recursively inspects nested command constructs and normalizes shell-native paths before workspace-boundary checks, including MSYS drive paths on Windows. Auto-allowlisted readers still require outside-directory approval when their path arguments leave the workspace. One public app-server effect entry represents the tool call, streaming output, approval blocking, result, duration, and terminal state. Public effect kinds are semantic presentation categories, not a registry of tool names. Arbitrary MCP, connector, custom, and future tools use the generic effect projection without adding app-server dispatch code. Tools that support client-hosted filesystem or terminal operations use `ToolIOPort`, and the app server sends typed `clientTool/*` requests. The server still owns tool validation, permissions, lifecycle, public effects, and model-visible results. Tools without that port keep their server-side execution semantics. ## Rationale Tools are the highest-risk extension point. Typed contracts make LLM calls, validation, UI display, session logging, ACP translation, and tests agree on one shape. Permission handling limits blast radius. ## Agent Guidance - Implement new tools under the existing `BaseTool` pattern with typed args/results/config/state. - Raise `ToolError` for user-facing failures and `ToolPermissionError` for authorization failures. - Keep permission resolution close to the tool behavior it protects. - Keep path-inspection coverage at least as broad as shell reader allowlists, and test nested commands and platform-specific path forms. - Prefer core tool semantics in `vibe/core/tools`; put ACP or UI adaptation in surface-specific layers. - Keep tool output bounded and safe for LLM context, logs, and session transcripts. - Extend the tool presentation contract for a genuinely new semantic renderer; do not add switches on individual tool names in the app server or TUI. - Project approvals as typed callback entries related to the same effect. Do not install UI callbacks on tools or the agent loop. ## Flag To User When - A tool bypasses the permission model because it is easier for one caller. - A tool returns ad-hoc dictionaries or strings where a typed result should exist. - UI or ACP behavior would require changing the core tool contract in a surface-specific way. - A new tool requires app-server registration only to make its ordinary call or result visible. --- ## File: docs/adr/0005-layered-configuration.md # 0005 Layered Configuration ## Decision Configuration is layered, validated as a coherent snapshot, and model-driven. `VibeConfigSchema` is the canonical effective server schema. Every field has an explicit merge strategy, and external data is parsed through Pydantic rather than ad-hoc dictionary walks. For an attached session, the app server owns the `ConfigOrchestrator`, effective configuration, persistence target, reloads, and config-derived runtime state. Textual, ACP, and programmatic clients use typed app-server resources. They do not read or edit `config.toml`, receive the orchestrator, or mutate a live config object. ## Current layer stack The effective order is: 1. `DefaultConfigLayer`, materialized from `VibeConfigSchema` defaults; 2. `GrowthbookLayer`, materialized from remote or hydrated experiment assignments; 3. the user TOML layer (`~/.vibe/config.toml`) when the `"user"` source is enabled, followed by the project TOML layer (a discovered trusted `.vibe/config.toml`) when the `"project"` source is enabled; 4. `VIBE_*` environment values; 5. session/runtime overrides; 6. the active agent profile layer (`AgentProfileLayer`); and 7. the enforced admin layer (`AdminConfigLayer`), which shadows every layer below it. `AgentProfileLayer` ships as a statically installed empty slot positioned just below the admin layer. `AgentManager` owns its contents: it fills the slot for the initial agent and, on a profile switch, replaces the layer in place with the new overrides, then rebuilds the orchestrator synchronously (`rebuild`, a `run_sync` bridge over `build`). Because the slot is replaced in place its priority is fixed by the stack definition, so the admin layer always outranks a profile. The effective config is the single merged result; there is no separate profile-free "base" config. The user and project TOML layers are installed together so a trusted project config inherits unspecified values from the user config; per-field merge strategies (`REPLACE` / `UNION` / `CONCAT` / `SHALLOW` / `DEEP`) decide how overlapping fields combine, with the project layer taking priority. An untrusted or absent project layer is skipped from the merge by the builder while the user layer still contributes. The default write target is selected from the installed layers: - a discovered and trusted project layer is the default write target; - project-only composition uses the project layer, creating `.vibe/config.toml` on first write when absent; - otherwise `~/.vibe/config.toml` is selected when the user source is enabled; - composition without a persistent source uses the runtime override layer. An untrusted project layer loads empty and is skipped by the merge builder. When the user source is enabled, implicit writes fall back to the user layer. A user or project TOML value overrides the corresponding GrowthBook assignment. The default, GrowthBook, user, project, environment, override, agent-profile, and admin layers are all part of the default orchestrator stack. The agent-profile layer is statically installed but ships empty; `AgentManager` fills it in place when a profile is selected and rebuilds the orchestrator. Code must follow the live stack rather than assume a fixed layer set, since optional TOML layers may be absent. A/B test assignments that affect runtime behavior are configuration inputs. They must be mapped into config fields by `GrowthbookLayer`, then consumed from the effective `VibeConfigSchema`. Runtime code must not branch directly on `ExperimentManager` variants for behavior that can be represented as config, otherwise TOML, environment, and session override precedence is bypassed. Session options such as enabled tools, disabled tools, and ephemeral MCP servers are override-layer values. Forks and child sessions receive independent orchestrator copies. Child-only values, such as child session logging, are written to the copied override layer rather than persisted into the parent's TOML. ## App-server config boundary `ConfigView` is a redacted public projection, not a second writable config schema. It contains only values a client must render or apply and never contains resolved API keys, tokens, connector credentials, or arbitrary environment values. Clients must not infer writable paths from its shape. The current resource methods are defined by `vibe.app_server.protocol`: - `config/read` returns the effective redacted view (a single merged config; no separate base view); - `config/reload` re-reads configured sources and optionally rebuilds runtime state; - `config/write` validates and persists JSON-pointer edits, applying `set` and `remove` ops that each optionally target a named layer; - `config/proxy/read` and `config/proxy/write` manage the supported global proxy and certificate `.env` entries; and - `config/schema` exposes the live schema used by ACP settings clients. The proxy resource is deliberately separate from the TOML orchestrator. `config/schema` is configuration-form metadata; it is not a list of valid `config/write` paths and is not the public app-server protocol schema. For `config/write`, the server: 1. requires the session to be idle; 2. converts all ops into one schema-aware patch; 3. validates the prospective merged config; 4. writes the selected layer once; 5. replaces the effective config with a newly validated snapshot; 6. invalidates or rebuilds derived runtime state; and 7. returns a canonical `RuntimeSnapshot` and emits `runtime/updated`. One TOML-layer write uses a temporary file, `fsync`, and atomic replacement. The general orchestrator is not transactional across several target layers; each op may name a target layer, and ops without one route to the selected writable layer. The current public API does not expose an explicit user/project write scope, resource revision, complete provenance, or per-field runtime-impact metadata. It accepts generic `{op, path, value, target_layer}` ops and a client-supplied `reloadRuntime` choice. Do not emulate missing config primitives with shadow state in `vibe.app_server`; add them to the configuration substrate before projecting them through the public resource. ## Client-local application Persistence ownership and runtime application are separate: - model, agent, permission, tool, MCP, connector, hook, workspace, and session settings are applied by the server; - committed theme, clipboard, terminal-notification, and audio settings are applied from accepted server state; temporary presentation previews may remain local; and - microphone and speaker enumeration, recording, playback, and device failures remain client-local state. Audio managers consume the redacted public view. They do not import the private config schema. A local hardware failure may produce a client warning, but it must not silently mutate server config. Before a session is attached, CLI and ACP launchers still load dotenv values, create initial files, run onboarding, and read startup config for process-level setup. This is bootstrap staging, not a second attached runtime. After attachment, live config reads, writes, reloads, trust decisions, and derived resource refreshes are server operations. ## Rationale Vibe must combine defaults, persisted preferences, trusted project policy, environment values, session options, agents, tools, MCP, connectors, and other extensions without making delivery surfaces understand persistence. Schema-aware layering provides deterministic merge behavior and one validated effective snapshot. App-server ownership prevents the UI, ACP, and runtime from becoming competing sources of truth. ## Agent Guidance - Add fields to the relevant Pydantic config model with explicit defaults, validation, and merge metadata. - Preserve deterministic layer ordering and keep session overrides separate from persisted defaults. - Route A/B-tested runtime behavior through `GrowthbookLayer` config mappings; do not read `ExperimentManager` directly when a config field can represent the behavior. - Mutate attached-session config through app-server resources or server-owned orchestrator calls, never from Textual. - Return canonical server state after a mutation; clients replace their cache instead of optimistically merging arbitrary dictionaries. - Keep redaction in the server projector. Public views expose only what the client needs. - Keep config migration and persisted-format compatibility near config models and layers. - Avoid loading optional integrations during startup unless active config requires them. ## Flag To User When - A feature needs hidden global state instead of config or session state. - A config value is parsed manually or persisted from more than one owner. - A new public write needs explicit scope, provenance, conflict detection, or runtime-impact semantics that the current substrate does not provide. - A client needs a private config object, TOML path, secret, or orchestrator. - A new config path would make startup slower for users who do not use the feature. --- ## File: docs/adr/0006-local-sessions.md # 0006 Local Sessions ## Decision Sessions are durable local records of conversation state, metadata, tool availability, stats, and resumability data. Compaction keeps the current session identity and transcript. The compacted context is stored as an injected message marked with `context_boundary = "compaction"`. Model requests include the current system message, the latest marked compaction message, and everything written after it. Public history still projects the complete transcript and renders each marked message as a compaction checkpoint. Session persistence should be append-friendly for ordinary message writes, atomic for metadata, tolerant of old transcript shapes through migrations, and independent of one delivery surface. An explicitly selected in-place rewind is the exception: it rewrites the current session transcript to an earlier boundary. Private session storage and the public app-server projection are different contracts. Only the server reads or writes session files. Clients receive a lossy `PublicSessionState`, page public history through opaque cursors, and use stable session, turn, entry, callback, effect, and child-session IDs. Public events are not a persistence format. The current local format restores completed transcript state, metadata, statistics, and persisted child links. A reconnect to the same live harness can recover its snapshot and open callbacks; a new process does not restore an in-flight turn, open callback future, or live event sequence from JSONL. Do not present live reconnect behavior as crash recovery. Rewind has two explicit persistence modes: - A forked rewind preserves the source session and attaches a new session derived from the selected history prefix. - An in-place rewind keeps the current session identity and persists the truncated prefix under that identity. The discarded suffix is intentionally removed from durable session history and cannot be recovered by resuming that session. In both modes, the app-server response contains the authoritative public state after the rewind. Clients replace their projection from that state instead of editing the visible history locally. File restoration is an independent rewind choice and does not determine the persistence mode. ## Rationale Users rely on resume, rewind, titles, transcript inspection, and continuity across runs. Forked rewind supports exploration without losing the original; in-place rewind supports users who deliberately want to discard the abandoned tail without creating another session. Session files are also a boundary between current code and older Vibe versions, so changes must be conservative and destructive behavior must remain explicit. ## Agent Guidance - Persist messages and metadata through the session layer, not directly from UI code. - Route list, read, resume, continue, fork, rewind, clear, compact, rename, delete, and history operations through app-server session resources. - Keep session data serializable and migration-friendly. - Treat old transcript formats as real inputs unless a migration intentionally drops support. - Do not store surface-only widget state in core session transcripts. - Keep image/session attachment behavior explicit about what is persisted and what remains memory-only. - Keep compaction in the current session and append its marked context message through the normal session logger. - Treat context-clear session replacement as an explicit handoff: atomically adopt the returned session ID, public state, event watermark, and session-log summary. - Treat every rewind result as an authoritative state replacement, even when the session ID does not change. - For a forked rewind, preserve the source session and adopt the returned replacement session identity. - For an in-place rewind, retain the current session identity and persist the truncated transcript, including an empty conversation when rewinding to the first user message. - Never infer in-place rewind from a missing option. The destructive persistence mode must be selected explicitly; callers that do not expose a choice should preserve the source session. - Treat clear as a replacement-session operation when it returns a new identity. The replacement may derive from an earlier history prefix, but the original stored session remains intact. - Represent subagents as linked child sessions. Do not embed a live child runtime in a public parent model. ## Flag To User When - A change breaks existing session resume or requires users to discard old transcripts. - Compaction changes the session identity, rewrites earlier transcript entries, or sends messages before the latest compaction boundary to the model. - A rewind would destructively update a session without an explicit in-place choice. - UI state is being added to core transcript data. - Metadata updates are no longer atomic or ordinary message writes are no longer append-safe outside the explicit in-place rewind path. - A client needs to read `messages.jsonl`, session metadata, or private loader APIs to render or control a session. --- ## File: docs/adr/0007-extension-mechanisms.md # 0007 Extension Mechanisms ## Decision Vibe extends through explicit mechanisms: agents, subagents, skills, hooks, MCP servers, connectors, custom tools, and config layers. Extensions should be discoverable, filterable, typed where possible, and isolated from core startup and core control flow unless actively configured. For attached sessions, the app server owns extension discovery results, lifecycle, authentication state, process cleanup, and public projections. Clients use typed agent, skill, MCP, connector, and tool resource methods; hook state is projected through runtime diagnostics and events. Clients do not receive registries or managers. Subagents are server-owned child sessions with independent IDs and public projections. The parent timeline links to the child through a subagent effect; the client never constructs or stores a child `AgentLoop`. ## Rationale Extension mechanisms let users customize Vibe without editing core code. Isolation keeps third-party or local project behavior from destabilizing the default experience. ## Agent Guidance - Prefer an existing extension mechanism before adding a new one. - Keep discovery deterministic and cheap; defer expensive integration work until needed. - Reserve built-in names and avoid silently overriding built-ins with local extensions. - Report configuration issues without crashing the whole app when safe to continue. - Keep hooks and external processes bounded by timeouts and typed invocation/response models. - Return canonical public resource views after mutations and refresh client state through app-server notifications. ## Flag To User When - A feature adds a new extension path instead of using skills, agents, hooks, MCP, connectors, tools, or config. - Extension discovery would run expensive work during startup. - Local project behavior can override built-ins without an explicit rule. - A delivery surface needs separate extension discovery, MCP, connector, hook, or subagent lifecycle logic. --- ## File: docs/adr/0008-feature-instrumentation.md # 0008 Feature Instrumentation ## Decision Every feature must ship with analytics instrumentation. Telemetry is not optional or a follow-up — it is part of the feature work. Before creating new events, search the event registry (`datalake-dbt/event_registry/.yml`) and neighboring services for existing events that already capture the same or similar user action. Extend an existing event with new properties rather than creating a parallel one. Only create a new event when nothing existing fits. Every event must carry the standardized metadata block (`properties.metadata`). ## Rationale Features without telemetry are invisible to product and data teams. Fragmenting events across duplicates makes downstream queries unreliable and increases maintenance cost. Reusing existing events keeps the datalake consistent and composable. ## Agent Guidance - When implementing any feature, use the `instrument-feature-analytics` skill to plan the telemetry. - Search existing events broadly before defining new ones. - Verify instrumentation locally (`DEBUG_LEVEL=1 uv run vibe`), then in staging (`logs_events_staging`), then in production (`logs_events`). ## Flag To User When - A feature is being implemented without any telemetry plan. - A new event duplicates or overlaps with an existing one in the registry. - An event is missing the standardized metadata block. --- ## File: docs/adr/0009-app-server-boundary.md # 0009 App Server as the Harness Boundary ## Decision `vibe.app_server` is Vibe's harness and the only runtime boundary used by delivery surfaces. It owns live sessions and composes reusable engine implementations from `vibe.core`. Textual, ACP, and programmatic mode are clients of its typed JSON-RPC 2.0 protocol. Delivery surfaces never construct, receive, or inspect an `AgentLoop`. The client and server exchange serialized JSON values, including when both run in one Python process. ```mermaid flowchart LR Surfaces["Textual / ACP / programmatic"] Client["Public app-server client API"] Wire["Serialized JSON-RPC"] Harness["vibe.app_server harness"] Core["Reusable vibe.core engine"] Surfaces --> Client Client <--> Wire Wire <--> Harness Harness --> Core ``` `vibe.core` and `vibe.app_server` are source-module boundaries, not peer services. Core owns surface-neutral model and tool execution. The app server owns cross-surface session, turn, callback, resource, persistence-access, and cleanup lifecycles. Related decisions: - event-driven engine execution: [0003](0003-event-driven-agent-loop.md); - tools, permissions, effects, and client-hosted I/O: [0004](0004-typed-permissioned-tools.md); - configuration ownership: [0005](0005-layered-configuration.md); - private session storage and public session state: [0006](0006-local-sessions.md); and - agents, subagents, skills, hooks, MCP, and connectors: [0007](0007-extension-mechanisms.md). ## Rationale Vibe serves several delivery surfaces with one engine. A real serialized boundary gives the runtime one owner, prevents UI and protocol adapters from depending on Python object identity, and lets every surface observe the same turn, callback, effect, resource, and cleanup semantics. Public projections deliberately differ from private runtime and storage state. That keeps secrets and implementation details server-side while giving clients stable values they can reduce, render, page, and recover after an event gap. ## Ownership The server is authoritative for state that can affect the model, workspace, session durability, or shared runtime: | Server or harness ownership | Client ownership | | --- | --- | | Root and child runtime construction | Widgets and layout | | Session identity, turns, and execution reservations | Keyboard and prompt editing | | Private session storage and public projections | Rendering public models | | Canonical cwd, workspace roots, trust, and prompt preparation | Display aliases and autocomplete presentation | | Effective config, persistence, agents, and model selection | Applying an accepted theme | | Tools, permissions, effects, and model-visible results | Clipboard integration | | Skills, hooks, MCP, connectors, and subagents | Microphone, speaker, recording, and playback | | Scheduled loops, shell effects, reviews, and integrations | Advertised client-hosted filesystem or terminal calls | | Account, feedback, telemetry, diagnostics, and cleanup | Other explicitly client-local presentation services | Client-hosted filesystem or terminal execution, for tools that support it, is a capability rather than a transfer of harness ownership. The server still validates the tool, resolves permission, orchestrates execution, projects one public effect, and supplies the result to the model. Persisted settings remain server-owned even when the client applies their accepted value to local hardware or presentation. ## Package and dependency boundaries The dependency direction is strict: - `vibe.core` does not import `vibe.app_server`, Textual, or ACP protocol types. - Private server modules under `vibe.app_server` may import core implementations because they compose and project them. - Public models, protocol envelopes, transports, client facades, client state, and reducers do not import `vibe.core`. - Attached runtime code in `vibe.cli`, `vibe.acp`, and programmatic mode uses public app-server facades. Feature code must not import private app-server modules. - `vibe.app_server._runtime` is the construction boundary for `AgentLoop`. Delivery surfaces pass serialized launch intent, not prebuilt core objects. Launcher, setup, and authentication code still performs pre-session bootstrap before a runtime is attached. That code may load startup configuration, but it must not own or mutate the live attached runtime or its config. Production code under `vibe/cli/textual_ui` has no core imports and no `agent_loop` references, including through helper modules. The public package exports a narrow client API: - `AppServerHost` for passive pre-session operations and opening a session; - `AppServerSession` for an attached session, turns, resources, and live events; - `ClientToolHandler` for explicitly advertised client-hosted operations; and - `SessionExitSummary` for delivery-surface shutdown presentation. ### Type ownership There is one definition for each public concept: - `vibe.app_server.models` owns public session, history, callback, effect, and shared resource values; - focused modules such as `vibe.app_server.config` own redacted public views; - `vibe.app_server.protocol` owns request, response, error, and notification envelopes and imports the public values; and - client events wrap those public values without redefining their fields. A public model is separate from a core model only when it is intentionally redacted, aggregated, transport-oriented, or has a different lifecycle. Translation belongs in server-only projectors and handlers. Identical concepts use one dependency-neutral definition rather than parallel model hierarchies. `ProtocolModel` defines strict camel-case serialization and rejects unknown fields. It is a serialization policy, not a second domain type system. The app server reuses core config, session storage, agent management, tools, permissions, skills, hooks, MCP, connectors, and utilities. It must not grow shadow managers, parallel persistence, copied config schemas, or untyped reconstruction of core events. ## Serialized protocol lifecycle The implemented transports are serialized in-process queues and newline- delimited stdio. Both pass through the same JSON-RPC client, server, models, and handlers. In-process calls are not allowed to bypass serialization. Both transports use bounded queues. Stdio has one writer that preserves message order and applies backpressure. In stdio mode, stdout is reserved for JSON-RPC; human logs use the configured log file or stderr. ### Initialization and attachment Every connection follows this order: 1. The client sends `initialize` with `ClientInfo` and capabilities. 2. The server returns its identity, protocol version, methods, callback kinds, and transports. 3. The client sends `initialized`. 4. Passive host requests may run without a live root runtime. 5. `session/start`, `session/resume`, or `session/continue` lazily creates or loads the root runtime and attaches the connection. 6. The client reads the canonical runtime snapshot and consumes live events. No ordinary request is accepted before initialization. Initialization occurs once per connection. Creating a server does not eagerly create an agent runtime or load a workspace session. `AppServerHost` supports passive session list/read/history/delete, config-schema, and workspace-trust operations. Opening a session transfers that connection to `AppServerSession`; the same facade is not both a passive host and an attached runtime client. One `AppServer` instance owns one attached root runtime and its child-session registry. The protocol does not currently model several simultaneous attached observers of one runtime. ### Message directions The protocol has three explicit directions: 1. Clients send typed requests for session actions and resource operations. 2. The server sends typed notifications for public state and resource changes. 3. The server sends typed requests when it needs client participation: `callback/call` and advertised `clientTool/*` operations. The response to `callback/call` acknowledges delivery only. The semantic answer always returns in a client-to-server `callback/respond` request. Client-tool responses carry the result of the requested filesystem or terminal operation. Methods are explicit and typed. There is no generic slash-command execution, `executeCommand`, or generic session-event request. The current method catalogue is `vibe.app_server.protocol.SERVER_METHODS`; its Pydantic parameter and result models are the source of truth. Accepted actions write their response before notifications caused by that action. Attach operations establish the returned snapshot and live event route as one lifecycle transition so accepted events are not lost between them. ## Sessions and turns Session creation and user execution are separate: - `session/start` creates and attaches an empty session; - `session/resume` loads saved state and attaches it; - `session/continue` resolves and attaches the latest eligible session; - `session/read`, `session/list`, and `session/history/list` are passive reads; - `session/fork` creates a session from an existing public boundary; - `session/close` flushes and closes the attached runtime; - `turn/start` begins structured user input and may mark harness instructions as injected so they remain hidden from public history; - `turn/steer` adds input to the active turn; and - `turn/interrupt` interrupts the active turn. A session has at most one active turn. Steering and interruption include the expected turn identity so stale control requests fail instead of affecting a new turn. Turn input is structured content. The server owns normalization into model-visible input and persistence. Delivery surfaces do not construct private messages or maintain a second prompt renderer. Compaction preserves the active session identity and appends a checkpoint to the same public history. Plan-context clearing may replace the active session while preserving the turn. For replacement operations, the server emits a typed handoff containing the old ID, replacement `PublicSessionState`, event watermark, and session-log summary. The client adopts all of those values atomically before processing later events. Root creation and replacement are serialized lifecycle transitions. A staged replacement is either adopted after the previous root closes or is itself closed on failure; requests never observe two authoritative roots or a half-replaced runtime. Derived runtimes, including forks and child sessions, inherit experiment state before deferred tool discovery or system-prompt rendering begins. Tool availability and prompt instructions therefore come from the same experiment assignment from the first rendered prompt. ## Public state and event reduction `PublicSessionState` is a lossy, renderable projection. It contains: - a format identifier and per-session event watermark; - public session metadata; - a page of public history; - currently open callback entries; and - the active or most recently terminal turn. It is not the private persistence format and is not sufficient to reconstruct the engine. Only the server reads session files. Within a session, public history is an append-only timeline of these closed variants: - message; - reasoning; - effect; - callback; - checkpoint; and - notice. Entries have stable IDs and generation status. An in-progress entry may receive typed patches. A completed entry is immutable. Compaction appends a checkpoint without replacing the session. Rewind and clear may create a replacement session derived from an earlier boundary plus a checkpoint. The original stored session is not rewritten, and the client adopts the returned replacement snapshot rather than editing its existing projection. One effect entry owns the complete visible lifecycle of work: call, streaming output, approval blocking, result, duration, and terminal state. Tool names are data, not app-server dispatch keys. Semantic presentation kinds enable bounded rich renderers, and arbitrary tools use a generic effect fallback. Core engine events remain canonical inside the server. The app server consumes their async stream and projects only client-relevant semantics. It does not mirror every core event into a second private hierarchy. Projection-changing notifications carry a positive, monotonic event ID scoped to the session. The snapshot's `eventId` is its watermark. The client reducer: 1. ignores IDs at or below the watermark; 2. accepts only the next ID; 3. treats a larger ID as a gap; and 4. recovers by replacing state from `session/read` and reconciling public events. The core notification families are `session/snapshot`, session handoffs, `session/updated`, `history/entryAdded`, `history/entryUpdated`, `turn/started`, `turn/completed`, and `session/statsUpdated`. Warnings, errors, and resource notifications remain typed rather than using a generic envelope. ## Callbacks and client participation Approvals and user questions originate as typed core request events with stable request IDs. The server projects them as callback history entries and related effect state. Neither Textual nor the app server installs callback functions, message observers, or listeners on `AgentLoop`. The live callback lifecycle is: 1. record the open callback in the public session projection; 2. mark the related effect and session as blocked when applicable; 3. send `callback/call` to a client that advertised the callback kind; 4. receive delivery acknowledgement; 5. accept the semantic result through `callback/respond`; 6. resolve the original core request exactly once; and 7. complete or cancel the callback and related effect. An identical semantic response retry is a duplicate no-op. A conflicting second response is rejected. Open callbacks remain visible in `activeCallbacks` and are re-delivered when a connection resumes the same live runtime. Client-hosted tool I/O follows the same explicit participation rule. The client advertises filesystem and/or terminal capability during initialization. The server adapts those requests through `ToolIOPort`; unsupported operations fail instead of silently switching ownership or implementation. ## Server-owned resources State outside the main timeline uses typed resource families. Current families include runtime/config, agents, skills, tools, MCP, connectors, diagnostics, statistics, session logs, scheduled loops, workspace trust and prompt preparation, account, feedback, narration, review, telemetry, shell, and Vibe Code operations. The client receives public views, not managers or registries. Mutations return authoritative results. When a change affects several derived views, `runtime/updated` carries one canonical `RuntimeSnapshot`; clients replace their cached config, agents, tools, skills, hook count, MCP, connectors, config issues, and statistics together. Slash commands are presentation affordances over these resources. Client-only commands may change presentation locally. Any command that changes session, workspace, model-visible, persisted, or integration state calls an explicit app-server resource method. ## Subagents and child sessions Subagents are server-owned child sessions. Each child has its own session ID, runtime, and public projection. The parent exposes a subagent effect containing the child session ID; it does not embed the child's runtime or history. The parent-child link is persisted when session logging is enabled. The server registry routes child events and lifecycle operations. Child execution uses the server-owned `ToolIOPort`, including client-hosted I/O when advertised. Clients may read a child session by ID and render a tree, but they do not construct child loops, reduce child core events, or duplicate subagent result handling. ## Delivery adapters Textual, ACP, and programmatic mode share the same app-server session and resource APIs: - Textual renders public models and owns terminal-local facilities. - ACP translates ACP requests, callbacks, content, client tools, and updates to and from the app-server API. - Programmatic mode starts turns and consumes the same public event stream without depending on Textual. No attached delivery-surface runtime owns a parallel agent loop, config manager, session loader, tool execution path, or extension lifecycle. ## Reconnect, shutdown, and security The in-process harness can replace a failed memory connection. The client initializes the new connection, resumes the attached session, replaces its projection from the returned snapshot, and receives still-open callbacks. Stdio EOF closes its server process; process restart uses normal persisted-session resume semantics and does not imply restoration of in-flight execution. A successful `session/close` is the delivery surface's durability and cleanup boundary. The server records eligible last-session state, flushes session-owned data, closes child runtimes, interrupts or rejects pending work, and releases owned runtime resources such as MCP clients, model backends, experiments, managed terminals, Vibe Code operations, and telemetry before shutdown finishes. Security rules: - the server enforces tool, workspace, network, MCP, and connector policy; - public rendering metadata cannot grant permission; - public projections redact secrets, credentials, model context, and unsafe internal errors; - config views expose environment variable names, not resolved values; - workspace and tool policy is enforced server-side where applicable; - callback and client-tool results are validated before runtime state changes; and - unknown methods, fields, variants, notifications, or unsolicited responses fail explicitly. ## Consequences - Interactive startup constructs the harness behind `vibe.app_server`; Textual receives only client-facing services. - Session files and private config objects are never read by Textual. - Agent, model, permission, config, MCP, connector, skill, hook, and subagent changes are server operations. - ACP and programmatic mode do not maintain parallel runtime implementations. - Public models and private core types may differ only for a real semantic boundary. - A feature is not migrated merely because an RPC wrapper exists. Ownership, persistence, projection, cleanup, reconnect behavior, and tests must all sit on the correct side. ## Agent Guidance - Start changes from the owning app-server resource or session facade, not from a Textual widget's access to core state. - Add explicit typed methods and models. Do not add generic command or event payloads. - Consume core events in server-only projectors. Do not reconstruct core events from strings or dictionaries in the client. - Keep one public definition for each concept and one server-only translation point where semantics differ. - Keep arbitrary tool support generic; rich rendering is keyed by bounded semantic presentation kinds. - Preserve monotonic event sequencing, immutable completed entries, and response-before-notification ordering. - Treat callbacks and client tools as explicit server-to-client requests with validated response lifecycles. - Return or notify canonical resource state after mutations rather than maintaining client/server shadow state. - Keep blocking serialization, file I/O, subprocess work, and discovery off the shared UI event loop. ## Flag To User When - A delivery surface needs a live `AgentLoop`, manager, registry, config object, or session loader. - A change introduces a second runtime, persistence path, reducer, or extension lifecycle. - A new public type is field-for-field identical to an existing dependency-neutral type. - A new tool requires dispatch by tool name instead of the generic effect path. - A server-owned mutation has no explicit typed method or cannot return an authoritative public result. - A feature assumes multiple attached clients, a new transport, or persistence of in-flight execution that the current implementation does not provide. ## Enforcement Existing boundary and protocol tests guard these representative invariants and must remain in place as the architecture evolves: - Textual has no core imports or `agent_loop` references; - only the server runtime composition constructs `AgentLoop`; - memory and stdio use the same serialized protocol models; - initialization precedes other requests and runtime creation is lazy; - unknown protocol shapes fail strictly; - public event IDs are monotonic and gaps trigger snapshot recovery; - completed entries cannot be patched and one effect spans the full lifecycle; - callback delivery and semantic response are separate and resolve core requests once; - reconnect re-adopts a snapshot and open callbacks; - session handoffs atomically replace identity and projection; - derived runtimes hydrate inherited experiments before deferred initialization; - child callbacks, client-hosted I/O, result projection, and cleanup remain server-routed; and - root and child cleanup attempts all owned runtimes even when one cleanup fails. The architecture boundary suite lives under `tests/cli/textual_ui/test_app_server_boundary.py`, with protocol, event, session, callback, transport, resource, ACP, and programmatic behavior covered by focused tests under `tests/app_server`, `tests/acp`, and `tests`.