docs/
Outline
---
title: Three clean code rules for agents, enforced by tools
slug: clean-code-rules
lang: en
published: 2026-10-31
words: 453
lines: 75
size: 2.6 KB
read: 3 min read
reel: In the age of agents, clean code matters more than ever
---
You used to write the code yourself and remember why each decision was made. An agent starts every session from zero and copies whatever pattern it finds. A messy repo costs you tokens on every session, and its output gets messier.
So I keep three hard rules, and a machine checks each one. A rule nobody enforces is only a wish.
Rule 1: each part does its own job
An agent making a small change should only read a few files. In my backend the direction of every import is fixed, and code goes through layers in one direction:
api > schemas > services > selectors > models
import-linter fails the build if a layer reaches the wrong way. This is the real contract from my pyproject.toml:
[[tool.importlinter.contracts]]
name = "inside each app: api > schemas > services > selectors > models"
type = "layers"
containers = ["apps.accounts", "apps.content"]
layers = ["(api)", "(schemas)", "(services)", "(selectors)", "(models)"]
PYTHONPATH=src uv run lint-imports
Contracts: 5 kept, 0 broken.
Rule 2: comments are correct, every function has a docstring
The agent trusts what it reads. A wrong comment sends it the wrong way, so comments explain why, and every public function gets a Google style docstring:
def minutes(self, body_en: str, body_fa: str) -> int:
"""Return the reading time of the longer body.
Args:
body_en: English Markdown source.
body_fa: Persian Markdown source.
Returns:
Whole minutes, at least 1.
"""
Ruff enforces it, along with everything else it can check:
[tool.ruff.lint]
select = ["ALL"]
Rule 3: no dead code
When the agent sees an old login beside the current one, it has to guess which is real, and sometimes it guesses wrong. Removed code gets deleted, not commented out and not flagged off. Git remembers it. Ruff's ERA rules catch commented out code, which select = ["ALL"] already turns on.
Pay once
Keeping a repo this clean used to be expensive, so most teams lived with the mess. Now an agent does the cleanup. You pay once, and every later session reads less and gets more right.
Copy this into your CLAUDE.md
## Code rules
- Each module does one job. Respect the import direction: api > services > selectors > models.
- Every public function, class and module has a Google style docstring.
- Comments explain why, never what. Fix or delete a comment that is wrong.
- No dead code. Delete what is unused, including old versions. Git keeps the history.
- Before you finish, run the linters and the tests, and report their real output.
Run the checks in CI too, so no session can skip them.
| You used to write the code yourself and remember why each decision was made. An agent starts every session from zero and copies whatever pattern it finds. A messy repo costs you tokens on every session, and its output gets messier. |
| So I keep three hard rules, and a machine checks each one. A rule nobody enforces is only a wish. |
| ## Rule 1: each part does its own job |
| An agent making a small change should only read a few files. In my backend the direction of every import is fixed, and code goes through layers in one direction: |
| ```text |
| api > schemas > services > selectors > models |
| ``` |
| `import-linter` fails the build if a layer reaches the wrong way. This is the real contract from my `pyproject.toml`: |
| ```toml |
| [[tool.importlinter.contracts]] |
| name = "inside each app: api > schemas > services > selectors > models" |
| type = "layers" |
| containers = ["apps.accounts", "apps.content"] |
| layers = ["(api)", "(schemas)", "(services)", "(selectors)", "(models)"] |
| ``` |
| ```bash |
| PYTHONPATH=src uv run lint-imports |
| ``` |
| ```text |
| Contracts: 5 kept, 0 broken. |
| ``` |
| ## Rule 2: comments are correct, every function has a docstring |
| The agent trusts what it reads. A wrong comment sends it the wrong way, so comments explain why, and every public function gets a Google style docstring: |
| ```python |
| def minutes(self, body_en: str, body_fa: str) -> int: |
| """Return the reading time of the longer body. |
| Args: |
| body_en: English Markdown source. |
| body_fa: Persian Markdown source. |
| Returns: |
| Whole minutes, at least 1. |
| """ |
| ``` |
| Ruff enforces it, along with everything else it can check: |
| ```toml |
| [tool.ruff.lint] |
| select = ["ALL"] |
| ``` |
| ## Rule 3: no dead code |
| When the agent sees an old login beside the current one, it has to guess which is real, and sometimes it guesses wrong. Removed code gets deleted, not commented out and not flagged off. Git remembers it. Ruff's `ERA` rules catch commented out code, which `select = ["ALL"]` already turns on. |
| ## Pay once |
| Keeping a repo this clean used to be expensive, so most teams lived with the mess. Now an agent does the cleanup. You pay once, and every later session reads less and gets more right. |
| ## Copy this into your CLAUDE.md |
| ```markdown |
| ## Code rules |
| - Each module does one job. Respect the import direction: api > services > selectors > models. |
| - Every public function, class and module has a Google style docstring. |
| - Comments explain why, never what. Fix or delete a comment that is wrong. |
| - No dead code. Delete what is unused, including old versions. Git keeps the history. |
| - Before you finish, run the linters and the tests, and report their real output. |
| ``` |
| Run the checks in CI too, so no session can skip them. |





