Esc
Type to search posts, tags, and more...
Skip to content

AGENTS.md — a project constitution for AI-assisted Python development

AI coding agents work better when you give them project context upfront. Here is a practical AGENTS.md template for Python projects using UV.

Contents

Every AI coding agent — Claude Code, Cursor, Copilot Workspace — starts each session with zero knowledge of your project. It doesn’t know your package manager, your test command, your directory layout, or that you moved off pip six months ago. So it guesses. And it guesses wrong.

A project constitution fixes this. It’s a markdown file at the root of your repo that gives agents the context they need: commands, structure, conventions, constraints. Anthropic calls theirs CLAUDE.md. The vendor-neutral version is AGENTS.md. Same idea — a single file that turns a generic coding assistant into one that knows your project.

Why this matters for Network DevOps

Network automation repos tend to have non-obvious structure. You have inventory files, Jinja templates, device-specific modules, and test setups that talk to lab gear or mock devices. An agent without context will create files in the wrong place, use pip install instead of uv add, and generate code that ignores your existing patterns.

The fix is context engineering — designing the right instructions so the agent produces correct output on the first try. An AGENTS.md file is the most practical entry point. It loads automatically at the start of every session, and you maintain it like any other project doc.

What goes in it

Keep it short. Agents have limited context windows, and a 2,000-line constitution will get deprioritized. Focus on four things:

  • Commands — how to run, test, lint, and format. If the agent can verify its own work, it will.
  • Project structure — a quick tree so the agent knows where files go.
  • Conventions — naming, typing, error handling. Be specific: “use type hints on all public functions” beats “write good code.”
  • Constraints — what not to do. Ban pip, ban requirements.txt, ban print() debugging. Agents repeat mistakes you don’t explicitly forbid.

Start minimal. Add rules only when you see the agent making the same mistake twice. Over-engineering the constitution defeats the purpose.

The template

Here is a full AGENTS.md for a Python project using UV as the package manager and Ruff for linting and formatting. Adapt the project structure and dependencies to your own repo.

# AGENTS.md

> Project constitution for AI-assisted development.

## Commands

```bash
uv run pytest                # Run test suite
uv run pytest -x             # Stop on first failure
uv run ruff check .          # Lint
uv run ruff format .         # Format
uv run python src/main.py    # Run the application
```

> Do NOT use pip, pip install, or requirements.txt. This project uses UV.
> Do NOT create virtual environments manually. UV manages environments automatically.

## Project structure

```
my-project/
  pyproject.toml          # Single source of truth for deps and config
  uv.lock                 # Lockfile — do not edit manually
  src/
    main.py               # Entry point
    devices/
      inventory.py        # Device inventory loader
      connections.py      # Connection factory (Netmiko/NAPALM)
    templates/
      base_config.j2      # Jinja2 base template
    utils/
      logging.py          # Structured logging setup
  tests/
    conftest.py           # Shared fixtures
    test_inventory.py
    test_connections.py
  inventory/
    hosts.yaml            # Device inventory
```

## Dependencies

- Python 3.12+
- UV for package and environment management
- Ruff for linting and formatting
- pytest for testing

Adding a dependency:
```bash
uv add <package>           # Adds to pyproject.toml and updates uv.lock
uv add --dev <package>     # Dev dependency
```

## Code style

- **Type hints** on all function signatures. Use `from __future__ import annotations` at the top of every module.
- **Docstrings** on public functions and classes. Google style.
- **Snake_case** for functions, variables, and modules. **PascalCase** for classes.
- **f-strings** for string formatting. No `.format()` or `%` formatting.
- **pathlib.Path** for file paths. No `os.path`.
- **Structured logging** via the `logging` module. No bare `print()` statements.
- Functions should do one thing. If a function exceeds 30 lines, consider splitting it.

## Error handling

- Catch specific exceptions, never bare `except:`.
- Network operations (SSH, API calls) must have explicit timeout parameters.
- Use `contextlib.suppress()` for expected exceptions, not try/except/pass.
- Re-raise unexpected exceptions. Do not silently swallow errors.

## Design patterns

- **Protocols over inheritance.** Use `typing.Protocol` to define interfaces. Keeps coupling low and makes testing with fakes trivial.
- **Context managers** for anything that needs cleanup — files, connections, temporary state. Use `with` statements or `contextlib.contextmanager`.
- **Dataclasses for internal state, Pydantic for boundaries.** Use `dataclasses.dataclass` for value objects inside the app. Use Pydantic `BaseModel` when parsing external input (API responses, config files, CLI args).
- **Dependency injection** for testability. Pass collaborators as function parameters instead of constructing them internally. No patching needed in tests.
- **Composition over inheritance.** Prefer small, focused functions and classes that compose together. Deep class hierarchies are a maintenance burden.

## Testing

- Every new module gets a corresponding test file in `tests/`.
- Use fixtures for device connections and shared state.
- Mock external network calls in unit tests. Integration tests can hit lab devices.
- Test names describe the scenario: `test_inventory_loads_from_yaml`, not `test_inventory_1`.

## Constraints

- Do NOT use pip, virtualenv, poetry, or pipenv. UV only.
- Do NOT create requirements.txt files.
- Do NOT use `os.system()` or `subprocess.run()` for tasks UV can handle.
- Do NOT commit `.env` files or credentials. Use environment variables.
- Do NOT modify uv.lock manually.
- Ruff configuration lives in pyproject.toml, not in separate config files.
Why UV over pip?

UV is a Python package and project manager written in Rust by the Astral team (the same people behind Ruff). It replaces pip, pip-tools, pipx, poetry, pyenv, and virtualenv with a single binary that is 10-100x faster than pip. It uses pyproject.toml as the single source of truth and generates a uv.lock lockfile for reproducible builds. For new projects in 2026, there is no reason to reach for anything else.

Getting started

If you are creating a new project from scratch:

uv init my-project
cd my-project
uv add netmiko napalm jinja2
uv add --dev pytest ruff

Drop the AGENTS.md template into the root of your repo, edit the structure and conventions to match your setup, and every AI agent that touches the project will know the rules before writing a single line of code.

! Was this useful?