System Prompt, AGENTS.md, or Skill: Where Coding-Agent Instructions Belong
Every instruction you give a coding agent takes up context, and context is not free. A longer prompt costs more on every turn, and quality degrades as the window fills. Chroma’s context rot study measured that decline across current models. So the question isn’t only what to tell the agent but where: each place an instruction can live is loaded at a different time.
For coding agents there are now three standard places, plus the code itself.
1. The repository instruction file: always loaded
AGENTS.md is an open format read by Codex, Cursor and a growing list of other tools. Claude Code reads CLAUDE.md, which can pull in another file with an @AGENTS.md import, so one file can serve both. File discovery, directory scope and precedence vary by tool and invocation. Verify the loaded instructions rather than assuming every file enters every session.
That makes it the right place for facts that apply to almost every change in the repository:
- how to build, test and lint, with the exact commands;
- repository layout, when it isn’t obvious from the tree;
- conventions a reader couldn’t infer from the code (“all money is integer cents”, “never edit generated files under
gen/”); - what counts as done (“run
make checkbefore finishing; it must pass”).
And the wrong place for:
- Rare, specialised procedures. How to write an IAM policy for a new agent role matters on one change in fifty. On the other forty-nine, it’s noise in every session.
- Things the code already says. Restating the directory tree or listing every dependency costs tokens and drifts out of date.
- Aspirations. “Write clean, maintainable code” changes nothing. Specific rules the agent can check (“functions over 60 lines need a comment explaining why”) change behaviour.
A useful discipline: when an agent makes the same mistake twice, add one line to the file. When a line hasn’t prevented anything in months, delete it.
2. Skills: loaded when the task matches
A skill is a folder with a SKILL.md file: YAML frontmatter with a name and a description, then Markdown instructions, optionally with reference files and scripts alongside. The format started at Anthropic and is now an open specification supported natively by Claude Code, Codex and Cursor, among others.
The design is progressive disclosure. At startup the agent sees only each skill’s name and description, a few dozen tokens apiece. When the task matches a description, it reads the full SKILL.md. Reference files load only if the instructions point to them for the current situation.
That makes skills the right place for specialised procedures that matter a lot on a few tasks:
- writing IAM for a role that can launch other workloads;
- adding a database migration in this codebase’s particular style;
- preparing a release, with its checklist;
- reviewing code for a specific class of vulnerability.
Most of the work is in the description, because it decides whether the skill loads at all. Write it around the words people actually use when the skill is needed, not the skill’s own jargon. “Use when writing or reviewing IAM for Lambda, ECS or Step Functions that start other tasks, or when asked to ‘give the agent permission to…’” will be matched far more reliably than “Agent IAM best practices”. Codex’s docs make the same point: front-load the use case and trigger words in case descriptions get shortened.
Where each tool looks for skills:
| Tool | Project skills | Personal skills |
|---|---|---|
| Claude Code | .claude/skills/<name>/SKILL.md |
~/.claude/skills/ |
| Codex | .agents/skills/<name>/SKILL.md |
~/.agents/skills/ |
| Cursor | .cursor/skills/<name>/SKILL.md (also reads .agents/skills/) |
~/.cursor/skills/ |
Check your tool’s documentation before relying on this table: these paths have changed more than once in the past year.
3. The task prompt: loaded once
What you type for a specific task should hold what’s specific to that task: the goal, the constraints for this change, and what done looks like. If you find yourself pasting the same paragraph into prompts every week, it belongs in one of the two places above.
4. The code: executable checks, with coverage limits
Types, tests and linters provide executable checks, with coverage and configuration limits. An IAM check should scope actions that support resource-level permissions and flag unjustified wildcards; a blanket ban on Resource: "*" would reject valid policies for actions that require it. When a rule can be checked mechanically, make it a check, and keep the prose for the judgment calls the check can’t make.
A worked split
Here’s how a team building agents on AWS might divide its guidance:
AGENTS.md: build and test commands; “all infrastructure is Terraform underinfra/; never create resources in the console”; “every IAM role needs a blast-radius comment”; “runmake planand include the summary in the PR”.- Skill:
agent-iam-least-privilege: the detailed procedure for scoping roles that pass, assume or create other roles, with worked examples. It loads only when the agent is writing that kind of IAM. - Skill:
terraform-migration: how to move resources between modules withmovedblocks without destroying them. - CI: a Conftest policy that fails the plan on unconditioned
iam:PassRole(shown in this post). - Task prompt: “Add a worker role for the new summariser task; it only needs to read from the
docs-inboxbucket.”
The always-loaded file stays short. The specialised knowledge is there when it’s needed. The critical rule is enforced by CI whether or not either document is read.
Where this stops
None of these guarantees the agent follows the instructions. Models weigh instructions against everything else in context, and a skill whose description never matches will never load. Treat instructions as a way to make good output more likely, and checks as the way to make bad output fail. Then test the instructions the same way you’d test code: run the same task with and without them and compare what comes out.