vibe/AGENTS.md
Clément Drouin 632ea8c032
v2.9.0 (#641)
Co-authored-by: Antoine <33425718+anth2o@users.noreply.github.com>
Co-authored-by: Bastien <bastien.baret@gmail.com>
Co-authored-by: Clément Sirieix <clement.sirieix@mistral.ai>
Co-authored-by: Kim-Adeline Miguel <51720070+kimadeline@users.noreply.github.com>
Co-authored-by: Mathias Gesbert <mathias.gesbert@mistral.ai>
Co-authored-by: Maxime Dolores <maxime.dolores@ext.mistral.ai>
Co-authored-by: Michel Thomazo <51709227+michelTho@users.noreply.github.com>
Co-authored-by: Nelson PROIA <144663685+Nelson-PROIA@users.noreply.github.com>
Co-authored-by: Pierre Rossinès <pierre.rossines@mistral.ai>
Co-authored-by: Quentin <quentin.torroba@mistral.ai>
Co-authored-by: Robin Gullo <robin.gullo@mistral.ai>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
2026-04-28 17:44:07 +02:00

6.6 KiB

AGENTS.md

Conventions for AI agents and humans contributing to Mistral Vibe — a Python 3.12+ CLI coding assistant managed with uv.

Layout: vibe/core is the engine (agent loop, tools, LLM backends, config); vibe/cli is the Textual TUI; vibe/acp bridges to the Agent Client Protocol; vibe/setup runs first-run wizards. Tests live in tests/ with autouse fixtures in conftest.py and test doubles in tests/stubs/.

Commands

Always go through uv — never invoke bare python or pip.

  • uv run vibe / uv run vibe-acp — the two entry points.
  • uv run pytest — full suite (parallel via pytest-xdist).
  • uv run pyright — strict type check.
  • uv run ruff check --fix . and uv run ruff format . — run both after every code change and report the files modified.
  • uv run pre-commit run --all-files — full lint pass. Install once with uv tool install pre-commit && uv run pre-commit install.
  • Useful uv basics: uv sync --all-extras, uv add <pkg>, uv remove <pkg>.

Project layout & module conventions

  • __init__.py exposes the public API via an explicit __all__.
  • Private modules are prefixed with _ (e.g. _settings.py, _config.py).
  • Pydantic models live in models.py; configuration in _settings.py / _config.py.
  • Abstract interfaces use the _port.py suffix (hexagonal-style ports).
  • Tests mirror the source layout; test doubles in tests/stubs/ are named Fake*.

Python style

  • Prefer match / case over long if / elif chains.
  • Use the walrus operator := only when it shortens code and improves clarity.
  • Be a never-nester: early returns and guard clauses over nested blocks.
  • Modern type hints only: built-in generics (list, dict) and | unions. Never import Optional, Union, Dict, List from typing.
  • Use pathlib.Path (and anyio.Path in async paths) instead of os.path.
  • Use f-strings, comprehensions, and context managers; follow PEP 8.
  • Enums: StrEnum / IntEnum with auto() and UPPERCASE members. For type-mixing, the mix-in type comes before Enum in the bases. Add methods or @property rather than parallel lookup tables.
  • Write declarative, minimalist code: express intent, drop boilerplate.
  • Never call a private method from outside of it's class

Typing & imports

  • Pyright is strict and gates CI; fix types at the source.
  • No relative imports — ban-relative-imports = "all". Always from vibe.core.x import ….
  • No inline # type: ignore or # noqa. Fix with refined signatures (TypeVar, Protocol), isinstance guards, typing.cast when control flow guarantees the type, or a small typed wrapper at the boundary.

Pydantic

  • Parse external data via model_validate, field_validator, or model_validator(mode="before") — never ad-hoc getattr / hasattr walks or custom from_sdk constructors.
  • Set ConfigDict(extra=…) explicitly. Use validation_alias (or field aliases) for kebab-case TOML keys.
  • Discriminated unions (e.g. MCP transport): use sibling final classes plus a shared base/mixin, and compose with Annotated[Union[...], Field(discriminator=...)]. Never narrow the discriminator field in a subclass — it violates LSP and pyright will reject it.
  • Document Raises: only for exceptions the function actually raises (or that propagate from public API calls). Don't list speculative built-ins.

Async

  • asyncio is the orchestration runtime in the agent loop and tool execution. Use asyncio.create_task + queues for concurrent work, not blanket gather.
  • Use anyio.Path for file I/O on async paths.
  • Streaming surfaces return AsyncGenerator[Event, None], not coroutines.
  • HTTP via httpx.AsyncClient; mock with respx in tests.

Tools

  • Subclass BaseTool from vibe/core/tools/base.py with a Pydantic args model and a BaseToolConfig generic parameter.
  • Implement async def run(args, ctx: InvokeContext) and yield events progressively.
  • Raise ToolError for user-facing failures; raise ToolPermissionError for authorization failures.
  • Declare permission with ToolPermission (ALWAYS / ASK / NEVER); honor it consistently.

Logging & errors

  • Use from vibe.core.logger import logger — stdlib logging with StructuredLogFormatter, not structlog.
  • Configure via env: LOG_LEVEL (default WARNING), DEBUG_MODE, LOG_MAX_BYTES. Logs land in ~/.vibe/logs/vibe.log.
  • Pass variables as keyword args, not interpolated into the message: prefer logger.error("Failed to fetch", url=url) over logger.error(f"Failed to fetch {url}").
  • Define module-local exception hierarchies. Always chain with raise NewError(...) from e. Rich exceptions expose a _fmt() helper for human-readable output.

File I/O

  • Prefer vibe.core.utils.io.read_safe / read_safe_async / decode_safe over raw Path.read_text(), Path.read_bytes().decode(), or open().
  • They return ReadSafeResult(text, encoding) and try UTF-8, then BOM detection, then locale, then charset_normalizer lazily.
  • Pass raise_on_error=True only when callers must distinguish corrupt files from valid ones; the default replaces undecodable bytes with U+FFFD.

Tests

  • Stack: pytest + pytest-asyncio + pytest-textual-snapshot + respx.
  • Mark async tests with @pytest.mark.asyncio. Mock outbound HTTP with respx.
  • Rely on the autouse fixtures in tests/conftest.py (config_dir, tmp_working_directory) for filesystem and home-dir isolation.
  • No docstrings on test functions, methods, or classes — descriptive names like test_create_user_returns_403_when_unauthorized carry the intent. Pytest displays docstrings instead of node IDs when present, which hurts.
  • Tests are exempt from the ANN and PLR ruff rules (see per-file-ignores).

Git

  • Never use git commit --amend, git push --force, or git push --force-with-lease.
  • Always create new commits and push with a plain git push.
  • If a push is rejected due to upstream changes, rebase onto the updated remote branch — never merge and never force-push.

Editor tip

In Cursor / Pyright, the "Add import" quick fix is missing — use the workspace snippets acpschema, acphelpers, vibetypes, vibeconfig to insert the import line, then rename the symbol.

Autoimprovement

  • Suggest to add new rules to AGENTS.md based on user input or PR comments, when a change request could be generalized as a rule.
  • Suggest updates to the README.md file according to feature changes or additions
  • Keep the builtin Vibe Skill (vibe/core/skills/builtins/vibe.py) up-to-date. It documents the CLI's features, such as args, flags, config options and persistence, commands, built-in agents, file discovery logic.