vibe/AGENTS.md
Mathias Gesbert 4e495f658d
v2.18.3 (#861)
Co-authored-by: Clément Drouin <clement.drouin@mistral.ai>
Co-authored-by: Laure Hugo <201583486+laure0303@users.noreply.github.com>
Co-authored-by: Paul VEZIA <166131032+le-codeur-rapide@users.noreply.github.com>
Co-authored-by: Peter Evers <peter.evers@mistral.ai>
Co-authored-by: Jules YZERD <newtonlormont@gmail.com>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
2026-06-30 15:03:21 +02:00

8.5 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 its class in production code. Accessing private methods in tests is acceptable.
  • Avoid comments and docstrings, except for when there's a hard to spot corner case

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), LOG_MAX_BYTES. Logs land in ~/.vibe/logs/vibe.log.
  • Pass variables as %s positional args, not f-string interpolation: prefer logger.error("Failed to fetch url=%s", url) over logger.error(f"Failed to fetch {url}"). This defers formatting to the logging framework (only formats if the message is emitted) and keeps messages grep-friendly.
  • 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.

Widgets

  • For selectable lists, use NavigableOptionList from vibe/cli/textual_ui/widgets/navigable_option_list.py instead of Textual's OptionList. It adds j/k cursor navigation on top of the arrow keys; the bare OptionList only handles arrows.

TCSS

  • When a rule sets color: $text-muted;, pair it with a nested &:ansi { text-style: dim; } so the muted intent survives under ANSI themes.
  • Never use ansi_* colors (e.g. ansi_red, ansi_bright_blue). Use Textual theme variables like $primary, $foreground, $surface, $error, etc. — see https://textual.textualize.io/guide/design/. ANSI themes are derived from these variables automatically.

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.
  • Reconciling with the upstream of the current branch (e.g. push rejected because origin/<current-branch> advanced): rebase the current branch onto its upstream — do not merge the upstream branch into the current one, never force-push.
  • Reconciling with the base branch (e.g. origin/main) once the PR is open: merge the base branch into the current branch — do not rebase, since rebasing rewrites already-pushed history and would require a force-push.
  • Run git commands through uv run (e.g. uv run git commit, uv run git push) so pre-commit hooks resolve the project's venv — bare git commit fails pre-commit with reportMissingImports because pyright can't find third-party packages.

CI / GitHub Actions

  • Pin every uses: to a full commit SHA with an exact version comment: uses: owner/action@<commit-sha> # vX.Y.Z.
  • Resolve to the commit, not the annotated-tag object: take the refs/tags/vX^{} line from git ls-remote --tags, or gh api repos/<owner>/<repo>/git/refs/tags/<tag> --jq .object peeled to a commit. Check with git cat-file -t <sha>commit, not tag. Never pin a moving major tag (v9).

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.