Writing AI workflows & prompting
Writing CLAUDE.md and AGENTS.md for analytics repos
What belongs in a project memory file for a coding agent, what doesn't, how Claude Code chooses between CLAUDE.md and AGENTS.md, and two worked examples.
A project memory file is the note you’d leave a capable contractor on their first morning: how to run things, what the house rules are, what counts as finished, and where the floorboards are loose. It’s read at the start of every session, so every line costs context every time. The job is to make it short, specific and true.
This article covers how Claude Code actually loads these files (including AGENTS.md, which it now reads directly), what to put in them, and two examples: a Python analytics repo and a Power BI project saved as TMDL.
How Claude Code loads memory files
I checked this against the memory documentation in September 2026, because the AGENTS.md support is recent.
CLAUDE.md locations. Claude Code reads instructions from several places, broadest first:
| Scope | Location |
|---|---|
| Organisation (managed policy) | /etc/claude-code/CLAUDE.md on Linux; set by IT |
| You, in every project | ~/.claude/CLAUDE.md |
| The project, shared through git | ./CLAUDE.md or ./.claude/CLAUDE.md |
| You, in this project only | ./CLAUDE.local.md (add it to .gitignore) |
Files in the directory where you start Claude Code and every directory above it load at launch. Files in subdirectories load later, when Claude reads files in those folders. They’re concatenated, not overridden, so a rule in a parent folder and a contradicting rule in a child folder will both be in context, and Claude may follow either.
It’s context, not configuration. The content is delivered as a message after the system prompt. Claude reads it and tries to follow it, but nothing enforces it. If something must always or never happen, use a permission rule or a hook instead.
Imports. A line like @docs/metrics.md pulls another file in at launch, up to four levels of nested imports. It helps organisation but doesn’t save context, because imported files load in full.
Checking what loaded. /context lists the memory files in the current session, and /memory lists them and opens one for editing.
AGENTS.md: what I found
AGENTS.md is the file name other coding agents use for the same purpose. Claude Code now reads it directly, from version 2.1.277. The default behaviour is simple once you see it:
| Your repository has | Claude Code reads |
|---|---|
AGENTS.md, and no CLAUDE.md or CLAUDE.local.md in the working directory or above it |
AGENTS.md |
AGENTS.md and a CLAUDE.md or CLAUDE.local.md |
The CLAUDE.md files only |
A CLAUDE.md containing the line @AGENTS.md |
Both, through the import |
Three details catch people out:
- A personal
CLAUDE.local.mdcounts. Adding one to a repo that relies onAGENTS.mdstops Claude readingAGENTS.md, unless you change the setting below. Your~/.claude/CLAUDE.mddoesn’t count, and loads alongsideAGENTS.md. - You can change the rule. In
/config, the Project instructions setting takesclaude-md-or-agents-md(the default),claude-md-and-agents-md,claude-mdormanaged-only. - Some files aren’t read at all:
AGENTS.local.md,AGENTS.override.mdand anything under.agents/.
If you share a repo with people using other agents, the most portable setup is one AGENTS.md that everyone edits, plus, only if you need Claude-specific lines, a CLAUDE.md next to it that starts with @AGENTS.md. The docs note that keeping this import never makes Claude read the file twice, and it also covers sessions that can’t read AGENTS.md directly, such as older versions.
My own workspace works this way. It has an AGENTS.md and no CLAUDE.md. Beyond the house rules (use uv, work in a .venv, remove dead code with ruff, ty and vulture), it records how skills are stored for Pi, another agent harness I use, which looks for them in its own folders.
What belongs in the file
Four kinds of content earn their place.
Commands Claude can’t guess. Not “run the tests” but the exact command, including the flags that matter. uv run pytest -q tests/unit, not pytest.
Conventions that differ from the defaults. Claude already knows PEP 8 and what a CTE is. It doesn’t know that your team prefixes staging models with stg_, or that every measure needs a display folder.
A definition of done. This is the line most memory files are missing and the one I’d write first. Tests pass, linter clean, row counts compared, output pasted into the conversation. Without it, “done” means “looks done”.
Gotchas. The timestamp that’s in local time when everything else is UTC. The source table that’s reloaded at 07:00, so morning runs see yesterday’s data. Anything a new analyst would learn the hard way in their first month.
What doesn’t belong
- Anything Claude can read from the code. Folder trees, dependency lists, file-by-file descriptions. It will explore the repo itself.
- Tutorials and long explanations. Link to the doc instead.
- Things that change weekly. Sprint goals and current ticket numbers go stale fast, and stale instructions are worse than none.
- Secrets. The file is committed to git and read into the model’s context. Connection strings belong in
.env, with a deny rule on reading it. - Procedures. A ten-step release checklist is a skill, not a memory entry. A skill lives at
.claude/skills/<name>/SKILL.md, runs when you type/<name>or when Claude decides it’s relevant, and only its short description sits in context until then. Skills for repeatable analysis in Claude Code has two worked examples. - Rules for one corner of the repo. Put them in
.claude/rules/with apaths:list in the front matter, and they load only when Claude works on matching files.
Keep it short and current
The documentation’s target is under 200 lines per file. The test I use for each line comes from the same docs: would removing it cause Claude to make a mistake? If not, delete it. Long files don’t just waste context; the important rules get lost among the unimportant ones, and adherence drops.
Two habits keep it current:
- Add a line when you correct the same thing twice. That’s the signal the docs recommend, and it’s the right one. A correction that only lives in chat is gone next session.
- Review it when you review code. Keep it in git and change it in pull requests. When a tool, command or folder changes, the memory file changes in the same commit.
A cautionary example from this site’s own repository. An audit in September 2026 found that its AGENTS.md was a generic guide to Docker base images and Dockerfiles, in a repo that has no Dockerfile. It wasn’t wrong in general, just wrong for this project, and generic rules that contradict the actual setup produce churn.
Block-level HTML comments (<!-- like this -->) are stripped before the file reaches Claude, so you can leave notes for human maintainers without spending context on them.
Example: a Python analytics repo
# Project
Weekly ED and outpatient marts, built in Python and SQL, loaded to the
reporting warehouse by the nightly job.
# Commands
- Setup: `uv sync`. Never pip install; add packages with `uv add`.
- Tests: `uv run pytest -q`. Single file: `uv run pytest -q tests/test_ed.py`
- Lint: `uv run ruff check . && uv run ruff format --check .`
- Build to dev: `uv run python -m pipeline.build --target dev`
# Conventions
- SQL in `sql/`: stg_ (one per source), int_ (joins), mart_ (reporting grain).
- Every mart has a grain comment on line 1 and a uniqueness test.
- Metric definitions live in `docs/metrics.md`; change the doc with the code.
# Done means
- Tests, ruff check and ruff format pass; paste the output.
- For any changed mart: row count and key totals before vs after.
- No new dependency without saying why.
# Gotchas
- `arrival_ts` is local time; `triage_ts` is UTC.
- `ref_clinic` has retired codes with no end date; filter on `is_active`.
- Only synthetic data under `data/`. Never read from `/mnt/warehouse`.
About 25 lines. Everything in it is specific to this repo and checkable.
Example: a Power BI project saved as TMDL
When a Power BI Project (PBIP) is saved in TMDL format, the semantic model becomes a definition/ folder of text files, with a tables/ folder holding one file per table. That makes it something an agent can edit, and also something an agent can break in ways Power BI Desktop only reports when you open it. The memory file has to carry that knowledge.
# Project
RTT waiting list semantic model. PBIP, saved in TMDL format.
Model: `Waiting List.SemanticModel/definition/`. Report: `Waiting List.Report/`.
# How changes are checked
- You can't run Power BI Desktop. After editing TMDL, list every object
you changed; I apply the external changes in Desktop and check for errors.
- Before editing, ask me to confirm Desktop has no unsaved changes:
applying external edits overwrites them.
# Conventions
- Measures live in the `_Measures` table, one display folder per theme.
- Every measure has a formatString and a description.
- Use DIVIDE(), not "/". Variables for anything used twice.
- British English in descriptions and display names.
# Don't touch
- `diagramLayout.json`, and anything under `.pbi/` (editor settings, cache).
- Relationships, unless the task says so; list any you think are wrong.
# Gotchas
- Weeks are ISO weeks from the Date table, not calendar weeks.
- "Waiting" means clock still running at the snapshot date, not open referral.
Two things make this example work. The definition of done admits the agent can’t verify its own change in Power BI Desktop, so it hands the verification step to a person explicitly instead of pretending. And the gotchas are business definitions, which are exactly what an agent can’t infer from the files. For more on the TMDL side, see TMDL and version control for Power BI semantic models.
The memory file is the cheapest improvement you can make to an agent’s work. It’s also the one that decays fastest if nobody owns it. For how it fits into a full working session, see A Claude Code workflow for analysts and BI developers.
Tags
- claude-code
- agents-md
- claude-md
- tmdl
- python