File: README.md
Mistral Vibe
██████████████████░░
██████████████████░░
████ ██████ ████░░
████ ██ ████░░
████ ████░░
████ ██ ██ ████░░
██ ██ ██░░
██████████████████░░
██████████████████░░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
curl -LsSf https://mistral.ai/vibe/install.sh | bashWindows
First, install uv
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"Then, use uv command below.
Using uv
uv tool install mistral-vibeUsing pip
pip install mistral-vibeTable of Contents
- Features
- Terminal Requirements
- Quick Start
- Usage
- Voice Mode
- Slash Commands
- Skills System
- Configuration
- Editors/IDEs
- Resources
- Data collection & usage
- 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(withripgrepsupport). - Manage a
todolist to track the agent's work. - Ask interactive questions to gather user input (
ask_user_question). - Delegate tasks to subagents for parallel work (
task).
- Read, write, and patch files (
- 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,.webpfiles are sent to vision-capable models (e.g. Mistral Medium 3.5) as native multimodal content. - Persistent command history.
- Beautiful Themes.
- Autocompletion for slash commands (
- Highly Configurable: Customize models, providers, tool permissions, and UI preferences through a simple
config.tomlfile. - 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 likegrepandread.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:
vibe --agent planTo change the default agent used when --agent is not passed, setdefault_agent in your config.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_agentapplies in both interactive and programmatic
(-p/--prompt) sessions. Pass--auto-approveor--yolowith 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
Navigate to your project's root directory:
bashcd /path/to/your/projectRun Vibe:
bashvibeIf 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/.envfor future use
Alternatively, you can configure your API key separately using
vibe --setup.- Use built-in defaults without creating a configuration file until you
Start interacting with the agent!
text> 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+JorShift+Enterfor 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+Gto edit your current input in an external editor. - Tool Output Toggle: Press
Ctrl+Oto toggle the tool output view. - Todo View Toggle: Press
Ctrl+Tto toggle the todo list view. - Debug Console: Press
Ctrl+\to toggle the debug console. - Agent Selection: Press
Shift+Tabto cycle through agents (ask, plan, ...). - Exit: Type
/exit,exit,quit,:q, or:quitin the input box, or pressCtrl+C/Ctrl+Dtwice within ~1 second. Setask_confirmation_on_exit = false(or toggle it in/config) to makeCtrl+Dquit on the first press;Ctrl+Calways requires confirmation.
Copying & Text Selection
- Copy: Use
Ctrl+YorCtrl+Shift+Cto copy the current selection to clipboard. With autocopy enabled (default viaautocopy_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:
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.
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):
vibe --prompt "Refactor the main function in cli/main.py to be more modular." --auto-approveProgrammatic 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--agentvalue.--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 withre:prefix (e.g.,re:^serena_.*$).--disabled-tools TOOL: Disable specific tools after--enabled-toolsfiltering. Can be specified multiple times. Supports exact names, glob patterns (e.g.,bash*), or regex withre:prefix (e.g.,re:^serena_.*$).--output FORMAT: Set the output format. Options:text(default): Human-readable text outputjson: All messages as JSON at the endstreaming: Newline-delimited JSON per message
Example:
vibe --prompt "Analyze the codebase" --max-turns 5 --max-price 1.0 --max-tokens 50000 --output jsonVoice 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:
> /voiceRecording 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:
> /helpIf 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:
- Create a skill directory with a
SKILL.mdfile - Set
user-invocable = truein the skill metadata - Define the command logic in your skill
Example skill metadata:
---
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 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:
---
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:
- Custom paths: Configured in
config.tomlviaskill_paths - Standard Agent Skills path (project root, trusted folders only):
.agents/skills/— Agent Skills standard - Local project skills (project root, trusted folders only):
.vibe/skills/in your project - Global skills directories:
~/.vibe/skills/and~/.agents/skills/
skill_paths = ["/path/to/custom/skills"]Managing Skills
Enable or disable skills using patterns in your configuration:
# 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:
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.
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:
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/.envfor future sessions.Environment Variables: Set your API key as an environment variable:
bashexport MISTRAL_API_KEY="your_mistral_api_key".envFile: Create a.envfile in~/.vibe/and add your API keys:bashMISTRAL_API_KEY=your_mistral_api_keyVibe automatically loads API keys from
~/.vibe/.envon startup. Environment variables take precedence over the.envfile 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:
enable_system_trust_store = trueSSL_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:
enable_otel = trueBy 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.
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:
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):
# 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):
# 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:
vibe --agent my_custom_agentVibe 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):
# 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, andgit_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 Windowspowershell 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, thenpowershell.exe. cmd.exe is not used by the managed Windows shell tools.
[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, thendisabled_tools removes matching tools from that set.
Examples:
# 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_listnotserena.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.
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 mistralaiUse --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 <name> 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:
/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.
# 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 transportstreamable-http: HTTP transport with streaming supportstdio: Standard input/output transport (for local processes)
Key fields:
name: A short alias for the server (used in tool names)transport: The transport typeurl: Base URL for HTTP transportsheaders: Additional HTTP headersapi_key_env: Environment variable containing the API keycommand: Command to run for stdio transportargs: Additional arguments for stdio transportstartup_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.
[[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:
# 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:
[[mcp_servers]]
name = "my_server"
transport = "http"
url = "http://localhost:8000"
env = { "DEBUG" = "1", "LOG_LEVEL" = "info" }
startup_timeout_sec = 15
tool_timeout_sec = 120Hooks
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):
[[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) — accompaniesdecision: "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 understrict = trueon 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—reasonis 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;reasonbecomes 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— replacestool_output_textwithreason. Pipeline continues; subsequent hooks see the replacement.hook_specific_output.additional_context(string) — appended (with a\nseparator) totool_output_text. Composes with a same-hook deny: deny replaces first, thenadditional_contextis appended to the replacement.system_message— UI-only.
Session Management
Session Continuation and Resumption
Vibe supports continuing from previous sessions:
--continueor-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)/resumeor/continue: Open the session picker from inside Vibe; pressDtwice to delete a local saved session. The active session cannot be deleted from this picker.
# Continue from last session
vibe --continue
# Open session picker
vibe --resume
# Resume specific session
vibe --resume abc123Session logging must be enabled in your configuration for these features to work.
Working Directory Control
Use the --workdir option to specify a working directory:
vibe --workdir /path/to/projectThis 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:
vibe --add-dir /path/to/other-project --add-dir /path/to/libraryEach 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 and run inside it:
vibe --worktree my-featureThe worktree lives under $VIBE_HOME/worktrees/<repo-name>-<repo-hash>/NAME and is checked out on a branch named NAME (created if it doesn't exist, attached if it does). Vibe cds 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:
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-otterThe 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/<name>, 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 --:
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:
enable_update_checks = falseNotification 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:
enable_notifications = falseCustom Vibe Home Directory
By default, Vibe stores its configuration in ~/.vibe/. You can override this by setting the VIBE_HOME environment variable:
export VIBE_HOME="/path/to/custom/vibe/home"This affects where Vibe looks for:
config.toml- Main configuration.env- API keysconnector_bootstrap_cache.json- Short-lived connector discovery cacheagents/- Custom agent configurationsprompts/- Custom system and compaction promptstools/- Custom toolslogs/- Session logs
Editors/IDEs
Mistral Vibe can be used in text editors and IDEs that support Agent Client Protocol. See the ACP Setup documentation for setup instructions for various editors and IDEs.
Resources
- CHANGELOG - See what's new in each version
- CONTRIBUTING - Guidelines for feature requests, feedback and bug reports
Data collection & usage
Use of Vibe is subject to our 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.0Unless 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 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/cliowns the Textual app, terminal UX, widgets, slash-command presentation, voice UI, and local interactive affordances. Model-visible shell execution crosses the app server.vibe/acpowns thin Agent Client Protocol translation and ACP-specific presentation updates over the app-server client API.vibe/setupowns 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, notvibe/core. - Add protocol translation in
vibe/acp, notvibe/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-pmode. - 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/coreto 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_taskand queues for explicit concurrent flows; avoid hiding orchestration in broadgathercalls. - 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 consumeAgentLoop.act()directly. - Keep public session event IDs monotonic. Duplicate notifications are ignored;
a gap is recovered by replacing the client projection fromsession/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 useToolIOPort, 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
BaseToolpattern with typed args/results/config/state. - Raise
ToolErrorfor user-facing failures andToolPermissionErrorfor 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:
DefaultConfigLayer, materialized fromVibeConfigSchemadefaults;GrowthbookLayer, materialized from remote or hydrated experiment assignments;- 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; VIBE_*environment values;- session/runtime overrides;
- the active agent profile layer (
AgentProfileLayer); and - 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, arun_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.tomlon first write when absent; - otherwise
~/.vibe/config.tomlis 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 onExperimentManager 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/readreturns the effective redacted view (a single merged config;
no separate base view);config/reloadre-reads configured sources and optionally rebuilds runtime
state;config/writevalidates and persists JSON-pointer edits, applyingsetandremoveops that each optionally target a named layer;config/proxy/readandconfig/proxy/writemanage the supported global
proxy and certificate.enventries; andconfig/schemaexposes 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 validconfig/write paths and is not the public app-server protocol schema.
For config/write, the server:
- requires the session to be idle;
- converts all ops into one schema-aware patch;
- validates the prospective merged config;
- writes the selected layer once;
- replaces the effective config with a newly validated snapshot;
- invalidates or rebuilds derived runtime state; and
- returns a canonical
RuntimeSnapshotand emitsruntime/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-suppliedreloadRuntime 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
GrowthbookLayerconfig mappings;
do not readExperimentManagerdirectly 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 withcontext_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/<service>.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-analyticsskill 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.
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 --> Corevibe.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;
- tools, permissions, effects, and client-hosted I/O:
0004; - configuration ownership: 0005;
- private session storage and public session state:
0006; and - agents, subagents, skills, hooks, MCP, and connectors:
0007.
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.coredoes not importvibe.app_server, Textual, or ACP protocol types.- Private server modules under
vibe.app_servermay import core implementations
because they compose and project them. - Public models, protocol envelopes, transports, client facades, client state,
and reducers do not importvibe.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._runtimeis the construction boundary forAgentLoop.
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 noagent_loop references, including through helper modules.
The public package exports a narrow client API:
AppServerHostfor passive pre-session operations and opening a session;AppServerSessionfor an attached session, turns, resources, and live events;ClientToolHandlerfor explicitly advertised client-hosted operations; andSessionExitSummaryfor delivery-surface shutdown presentation.
Type ownership
There is one definition for each public concept:
vibe.app_server.modelsowns public session, history, callback, effect, and
shared resource values;- focused modules such as
vibe.app_server.configown redacted public views; vibe.app_server.protocolowns 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:
- The client sends
initializewithClientInfoand capabilities. - The server returns its identity, protocol version, methods, callback kinds,
and transports. - The client sends
initialized. - Passive host requests may run without a live root runtime.
session/start,session/resume, orsession/continuelazily creates or
loads the root runtime and attaches the connection.- 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 toAppServerSession; 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:
- Clients send typed requests for session actions and resource operations.
- The server sends typed notifications for public state and resource changes.
- The server sends typed requests when it needs client participation:
callback/calland advertisedclientTool/*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/startcreates and attaches an empty session;session/resumeloads saved state and attaches it;session/continueresolves and attaches the latest eligible session;session/read,session/list, andsession/history/listare passive reads;session/forkcreates a session from an existing public boundary;session/closeflushes and closes the attached runtime;turn/startbegins structured user input and may mark harness instructions as
injected so they remain hidden from public history;turn/steeradds input to the active turn; andturn/interruptinterrupts 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:
- ignores IDs at or below the watermark;
- accepts only the next ID;
- treats a larger ID as a gap; and
- recovers by replacing state from
session/readand 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:
- record the open callback in the public session projection;
- mark the related effect and session as blocked when applicable;
- send
callback/callto a client that advertised the callback kind; - receive delivery acknowledgement;
- accept the semantic result through
callback/respond; - resolve the original core request exactly once; and
- 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_loopreferences; - 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 undertests/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.