From a5bf560efececad4ceaa174214c75566af175dfd Mon Sep 17 00:00:00 2001 From: Luke Mainwaring Date: Tue, 2 Jun 2026 09:55:45 -0400 Subject: [PATCH] docs(agents): add code conventions for AI navigability Codify existing practice as guardrails against drift: behavioral naming, intent-carrying module docstrings, and where rationale lives. Written generically for any coding agent and grounded in this repo's real symbols (detect_content_type, build_chat_instructions, agents/hooks.py). ADRs noted as the planned rationale home, deferred to the upcoming grill-with-docs pass. Wire a one-line pointer into AGENTS.md (CLAUDE.md symlinks to it) alongside the .claude/rules pointers. Co-Authored-By: Claude Opus 4.8 --- AGENTS.md | 1 + docs/agents/conventions.md | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 docs/agents/conventions.md diff --git a/AGENTS.md b/AGENTS.md index 0d9ac5c..75f495d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -97,3 +97,4 @@ Key patterns: - Editing backend Python? See `.claude/rules/backend/code-conventions.md` first — filename, typing, naming conventions, SQLAlchemy/Pydantic patterns, module re-export convention. - Editing the frontend chat UI, `useChat`, message rendering, or tool-call panels? See `.claude/rules/frontend/vercel-ai-sdk.md` first — pinned UI docs at `docs/vercel-ai-sdk-ui.txt` cover the UI surface only; the backend SSE stream is the source of truth; `ChatMessage` typing must stay threaded through every `UseChatHelpers` site. - Editing frontend code in general? See `.claude/rules/frontend/code-conventions.md` first — import-alias scope, shadcn/ui usage, kebab-case filenames, `cn()` from `@/lib/utils`, memoization policy. +- Writing code in any language here? See `docs/agents/conventions.md` — the cross-cutting "why" behind naming, intent-carrying docstrings, and where rationale lives. The language-specific `.claude/rules/` files carry the detail. diff --git a/docs/agents/conventions.md b/docs/agents/conventions.md new file mode 100644 index 0000000..b25cf14 --- /dev/null +++ b/docs/agents/conventions.md @@ -0,0 +1,30 @@ +# Code Conventions + +How to write code in this repo so a coding agent — any of them, not just one tool — can find it by `grep` and read its intent without guessing. These codify existing practice and act as guardrails against drift; expand as patterns recur. + +Language-specific detail lives in `.claude/rules/backend/code-conventions.md` and `.claude/rules/frontend/code-conventions.md`. This file is the cross-cutting "why" those rules share. + +## Names are how agents find code + +Agents `grep` for what they're trying to do before they read. Name for behavior, not layer. + +- **Functions and behavioral modules:** verb-first and specific — `detect_content_type`, `build_chat_instructions`, `discover_markdown_files`, `chunk_pdf_pages`. Not `process`, `handle`, `manage`. Frontend hooks follow the same rule: `useThreads`, not `useData`. +- **No grab-bag modules.** Never `utils.py`, `helpers.py`, `misc.py`, `common.py`. Code with no home names a missing concept — find it. (This repo's `utils/` already obeys this: every file is concept-named, e.g. `message_serialization.py`.) +- **Keep framework-convention names.** `app.py`, `config.py`, `schemas.py`, `deps.py`, `hooks.py`, and the files under `routers/` are what an agent expects in a FastAPI / Pydantic AI repo. Don't "behavioralize" these. Frontend filenames stay kebab-case (`rag-sources-dialog.tsx`) per the Vercel template lineage. + +## Docstrings carry intent; comments carry local "why" + +An agent retrieves a symbol, then reads its docstring — so the docstring is the contract. The recipe (see `agents/hooks.py` for the model: one line of *what*, then the non-obvious *why* — that an uncaught tool exception would crash the stream mid-response): + +1. One line: what it is. +2. The non-obvious *why* — the thing an agent would otherwise get wrong. +3. Cross-ref the governing decision when one exists (a future `docs/adr/` entry, a `services/` module that owns the rule). +4. A **"do not"** wherever the code looks fixable but isn't — e.g. the round-trip ordering in `utils/message_serialization.py`, or the recovery routing in `agents/hooks.py` that looks like dead defensiveness but keeps the stream alive. Agents act on negative constraints; state intentional weirdness explicitly or it gets "fixed." + +This complements — does not contradict — the existing rule against name-restating docstrings (`backend/code-conventions.md`). A module docstring that states intent earns its place; a function docstring that just re-says the function name does not. Keep docstrings to slow-changing content (contracts, invariants, rationale), not step-by-step logic that drifts out of sync. Inline comments only for non-obvious *why* at a specific line — never to restate *what*. + +## Rationale lives in durable docs, not the root agent file + +Keep `CLAUDE.md` lean: invariants, entry points, and pointers. Detailed conventions already live in `.claude/rules/`; the *why* behind a non-obvious choice belongs in a decision record, and the docstring that implements it cites that record — so an agent is pointed at the rationale before it "simplifies" the choice away. + +ADRs are the planned home for that rationale (`docs/adr/`), to be established in the upcoming `grill-with-docs` pass. Until then, capture the *why* in module docstrings and cross-reference it there. Don't let resolved rationale pile up as prose in the root agent file.