Skill, rule, or memory file: picking the mechanism
A fact, a scoped convention and a procedure belong in three different files. The rule for telling them apart, and what picking wrong costs you every turn.
The file that ate everything
Okay, so usually the memory file is the first thing created as part of the onboarding docs because they list only this mechanism (we didn't know about rules back then), and then everything that follows is put there, there's nowhere else to put it, after six months it's a 400-lines-long file in which half of the release procedure hasn't been run since March and a third of it is being actually followed.
And the reversed scenario: we split that file up, write a skill, and it never gets triggered — not once — because skills get into the session only when sth makes them relevant, and a plain fact has no moment where it becomes relevant.
The problem in both scenarios is the same — putting content in a mechanism that is not suitable for this type of content in terms of when it gets loaded to the context window; well, in theory it's all Markdown being sent in the same prompt so why do we need to distinguish between "rule" and "skill"? Sort of, partially. Because the thing is that the content arrives in a different point in time (which is measurable), and this is what's crucial. Let's have a look:
The only axis that matters
There are three mechanisms and for all of them there's an answer to the same question — when does their content appear in the context window?
Memory file — on startup it fetches everything at and above the starting directory, and then on every turn (you chose it)
Rule — depends on its scope, if it's not scoped it gets loaded on startup just like the memory file, but if it is it gets there once the agent touches a file that matches your pattern (the path has chosen it for you)
Skill — on invocation, either by typing its name or when the model decides that the description of the procedure is suitable based on the request (in this case it's the moment itself that has chosen it)
Everything else about these three — the file extension, the frontmatter, the directory it lives in — follows from that answer. Once you know which of the three your content wants, you know where it goes.
What always-on costs, measured
If you're using the Codex CLI tool, you can print the prompt it would send without sending it,
codex debug prompt-input "hello"so let's create a directory and put a 100B-sized AGENTS.md file and two skills into it (with the Codex CLI set to 0.146.0), and as we can see the memory file is inlined there in full,
# AGENTS.md instructions for /private/tmp/which-mechanism-check
<INSTRUCTIONS>
# Project notes
Migrations in `db/migrations/` are generated. Edit `db/schema.sql` and regenerate.
</INSTRUCTIONS>whilst every skill appears as a single line within <skills_instructions> (the name of the skill, its description and where to find the file).
- release-notes: Assemble release notes from the commit range since the last tag. Use when the user
asks for release notes or a changelog entry. (file: /private/tmp/which-mechanism-check/.agents/
skills/release-notes/SKILL.md)The main thing here is that the procedure itself, which is the body of SKILL.md, doesn't appear in the prompt and is being read during runtime. OpenAI calls it progressive disclosure. I've compared this for five skills already installed on my machine and the ratio is between 8:1 and 17:1 — for a 12 KB procedure we pay around 750 characters in the prompt.
There are two figures to remember here:
Claude Code cuts off the
descriptionandwhen_to_useat 1,536 characters combined (in the listing), on purpose, to control these costsThe maximum size of project docs that Codex can load is defined by the
project_doc_max_bytesconfig parameter, set to 32 KiB by default. Once the combined size crosses it, Codex stops adding memory files — so the cap bites exactly where you've written more standing instruction than it will carry
The picking table
So, to summarise:
| What you're holding | Where it goes | Why |
|---|---|---|
| Repo fact that's true on every task | Memory file | Must be there before you know you need it, and is cheap when short |
| Convention that applies to one directory or one file type | Scoped rule | Gets loaded when the agent touches a file matching your pattern, costs nothing otherwise |
| Multi-step procedure like a release, review or migration | Skill | The body stays on the disk, we pay just a single line until it's used |
| Something that needs to be launched if its name is typed | Skill with model invocation disabled | Same storage, but doesn't trigger itself |
| Something that must always happen no matter what the model thinks | Hook | None of the above is enforceable |
| Piece of information from some external system | MCP tool | The knowledge in docs gets outdated, a tool call doesn't |
I've added the last two rows as people tend to replace the first three with them. A line like this in the memory file: "Run the formatter before committing" is a request, whilst a pre-commit hook isn't.
When it is genuinely ambiguous
If you're uncertain, ask yourself the following questions, and pick the solution that's the first you can answer yes to:
Would it be helpful on any turn (not connected with it)? A hazard like this one in the migrations directory: "The migrations directory is generated, edit the schema" is helpful on every turn as it's the turn when the agent doesn't know there's a migration nearby that matters. A release checklist isn't helpful on any turn but the release, so the first point goes to the memory file.
Can you write this glob? If yes — it's a rule and you can put the pattern in frontmatter, if no — it's a memory file and you shouldn't lie about it.
Is it a procedure or a state of the world? If it's the latter then it's a fact for which the best place is the memory file, otherwise it's a procedure and you need to use a skill. (Anthropic draws the line in exactly this place in their docs — reach for a skill "when a section of
CLAUDE.mdhas grown into a procedure rather than a fact")
As a matter of fact Cursor also draws the line in an unusual way, by shipping with a skill which migrates rules into skills and the criterion for this action is that the rule has a description but no globs nor alwaysApply: true. If you were to look at it from the opposite perspective then you could say that:
Always-on things should be memory-file-shaped "rules"
Glob-scoped things should be rules
Descriptions that the model uses to choose an appropriate procedure are skills, they have been from the very beginning
In Claude Code
In terms of this harness, all three of these are available under their own names — the most separated I've seen
| Mechanism | Where is it defined? | When is it loaded? |
|---|---|---|
| Memory file | CLAUDE.md in either ./CLAUDE.md or ./.claude/CLAUDE.md, plus ~/.claude/CLAUDE.md | Always, on every session |
| Rule | Files under .claude/rules/*.md and ~/.claude/rules/ | When the tool starts, with the same priority as CLAUDE.md (so you can have a more organised setup, not save context), or whenever a path matching one of the rules is being accessed |
| Skill | SKILL.md in either .claude/skills/<name>/SKILL.md or ~/.claude/skills/<name>/SKILL.md | When it's being used |
As said, rules can be conditional by defining an array of paths in the file's frontmatter
---
paths:
- "src/api/**/*.ts"
---
# API rules
- Every endpoint validates its input.
- Errors use the standard response shape.Without paths, it'll be loaded upon tool's start with the same priority as CLAUDE.md so you get a tidier setup, not save any context. With paths — it'll only load when a file with path matching any of the rules is accessed.
Skills can have a frontmatter flag to indicate it's meant to be used only when you ask
---
description: Deploy the application to production
disable-model-invocation: true
---Only for this harness, two things:
Claude Code reads
CLAUDE.md, notAGENTS.md. If you already have the latter for other tools, put@AGENTS.mdat the top of aCLAUDE.mdinstead of maintaining two files@imports don't save tokens — they're inlined during the tool's start as if you were to just paste the imported content; splitting a big memory file into multiple ones helps with its readability and that's all
To see which memory files were loaded by the current session, run /context
In Codex CLI
The Codex CLI uses two mechanisms, not three, which are AGENTS.md and skills from the .agents folder. There's no separate rules file, instead the scope of an agent is defined by where you put its AGENTS.md
AGENTS.md ← the whole repo
services/payments/AGENTS.md ← sessions launched inside services/payments/
.agents/skills/release/SKILL.md ← the release procedureIn this example you can see that there's a file in the root of the project which will be used for all the sessions started in it, another one under services/payments which will be used for sessions started there for payments-related code, and a skill file for release under .agents/skills.
Whenever you run the CLI, it looks into the project root directory and then walks down to the cwd (current working directory) picking up at most one file per directory and merging them in a way that what's located deeper wins. That's how OpenAI recommends to address the case when multiple teams in an organisation need their own sets of rules
Although it's not as granular as with rules, it's worth to mention that the scope here is defined by the directory where you launch the session from (not what code you're working on), so all the AGENTS.md files from the root to cwd will be loaded no matter if the session operates in this area; and even if you were to set up a payments-specific one, it wouldn't have any effect for sessions started in the project's root
That said it doesn't mean that you can't nest memory files. It just means that this way you can configure which instructions will be loaded, but not when they'll actually consume the context (as they always do so during session's start). So still, even with well organised nested AGENTS.md files, it's always good to keep your procedures in skills
The CLI searches for skills in .agents/skills folders of all the directories from cwd up to the project's root as well as in $HOME/.agents/skills and /etc/codex/skills. In terms of a skill's metadata, the only thing you can set there is its name and description via SKILL.md frontmatter; if you want it to be invocable only manually, you can set that up in an adjacent agents/openai.yaml file:
policy:
allow_implicit_invocation: falseThat will make sure Codex won't invoke this skill automatically but you'll still be able to use it with the $deploy command. This file also serves to display the skill's metadata and its tool dependencies
The only thing to keep in mind is that the CLI stops loading AGENTS.md files once the combined size of them reaches project_doc_max_bytes, which is set to 32 KiB by default. Past that the instructions are simply not there, and the only note of it is a troubleshooting entry in the docs suggesting you raise the value
In GitHub Copilot CLI
Three types of mechanisms are all there — even though the names are GitHub's own, they don't represent the whole industry.
| Mechanism | Location |
|---|---|
| Memory file | AGENTS.md, .github/copilot-instructions.md |
| Rule | .github/instructions/**/*.instructions.md |
| Skill | .github/skills/, .agents/skills/, .claude/skills/, ~/.copilot/skills/, ~/.agents/skills/ |
You can run copilot init in a project and it will create the file .github/copilot-instructions.md by analysing your codebase without modifying anything. This is the starting point but keep in mind that the result is only a draft as it is based on already stated in the repo information, which is the kind of data the memory file should omit.
Path-scoped instructions - placed in .github/instructions/ and named like <name>.instructions.md — have globs defined in the frontmatter. It's good to focus on single topic per file to make it easily digestible.
For example, a glob for .ts and .tsx files with an instruction saying that under strict you shouldn't add any to make an error go away:
---
applyTo: "**/*.ts,**/*.tsx"
---
TypeScript in this repo compiles under `strict`. Do not add `any` to make an error go away.The repo-wide and path-scoped instructions are stacked instead of being replaced, so if the file being worked on matches, both sets are used.
Skills — the most important part is the list of sources, check for yourself what the binary outputs under its help section for the skill subcommand.
copilot skill --helpYou can see it fetches skills from Anthropic's .claude/skills/, GitHub's own places and the vendor-neutral .agents/skills/ location. If you are using multiple tools in a team I recommend to standardise on .agents/skills/ to make it easier for all of them to find the skills you create there.
Finally, worth noting that the subcommand for listing skills outputs what it has actually found, grouped into project, personal and builtin.
In Cursor
Cursor is the harness where these three arrived bundled into a single mechanism, and it's now on its way to splitting them, so it's good to follow it as it does it exactly the same way as I suggest here. It stores rules as .mdc files under .cursor/rules/, versioned in git. There are three frontmatter fields that control how the rule is loaded:
---
description: TypeScript conventions for this project
globs: **/*.ts
alwaysApply: false
---These are the four behaviours Cursor describes:
Always Apply
Apply to Specific Files
Apply Intelligently
Apply Manually
The first two are connected with the frontmatter fields I mentioned. For the first one — alwaysApply: true — the rule is loaded in every session, for the second — a globs pattern — if any of the files matching the given pattern is opened. The third behaviour — "Apply Intelligently" — is connected with having a description and not having globs. In this case, it's up to the model to decide based on the description if the rule applies.
Given the third behaviour above, Cursor has changed its mind compared to its past self. It turns out that for rules with a description but without globs or alwaysApply: true, it moves them to .cursor/skills/ using a built-in migrate-to-skills skill.
This way it's obvious that skills are all about having a description and for the model to decide if they apply based on it (the same as for the "Apply Intelligently" behaviour). They can either live in .cursor/skills/ or ~/.cursor/skills/. The same for slash commands in .cursor/commands/, they too get moved and have disable-model-invocation: true set so they stay type-invoked.
The line is now very clear — rules are about always-applied or glob-scoped features, skills either live in .cursor/skills/ or ~/.cursor/skills/ and are about the model deciding to use them based on their description. We can even replace a simple list of rules with AGENTS.md if it's more convenient for a team using multiple tools; generally, it's best to aim to keep rules under 50 lines which is stricter than in other tools but also very helpful.
In Gemini CLI
In the Gemini CLI, GEMINI.md covers the first of these jobs and partly the second, while skills cover the third — a top-level subcommand as of 0.46.0.
There's actually few levels of the GEMINI.md file: the one in ~/.gemini/, and for every project the ones in workspace directories and in their parent directories up to the trusted root. It's all get concatenated and used as the context in every prompt; also, there's a little feature in place that whenever a tool touches a file it tries to find a GEMINI.md in the directory of that file and in the higher-level directories until it finds the trusted root — so a nested GEMINI.md can be seen as an analogue of a rule bound to a path, being "activated" once the work touches some area. You can't target patterns with it, just directories though, but if you already have a repo on AGENTS.md you can point the context.fileName setting at both instead of maintaining copies.
{
"context": { "fileName": ["AGENTS.md", "GEMINI.md"] }
}In the GEMINI.md file there's also a special syntax for @file.md includes which you can use within the context, and the /memory show command shows you the current state of the memory.
The skills. They live in ~/.gemini/skills/ or ~/.agents/skills/, and .gemini/skills/ or .agents/skills/ within projects, with .agents/skills/ taking precedence over .gemini/skills/ inside each tier. There's gemini skills list, install, link, enable and disable.
Just a note that before assuming that some skill is bugged you should check if the project is trusted — there's a behaviour that if the project is not trusted it ignores project skills, showing a notice about skipping project agents until the project root is trusted:
Skipping project agents due to untrusted folder. To enable, ensure that the project root is trusted.Given that in such case the skills defined on the user level are loaded anyway, it creates an impression that some of the skills are missing instead of a trust issue.
In Kimi Code CLI
2 main parts of Kimi Code CLI are supported:
Persistent instruction file (it's
AGENTS.md, which is what the majority of other tools use as well)Skills
Memory file.
It supports it on a per-project basis (by having AGENTS.md file either in the root of the project or in .kimi-code/ subdirectory) and globally (using $KIMI_CODE_HOME/AGENTS.md — defaulting to ~/.kimi-code/AGENTS.md); if you use more than one harness, consider using ~/.agents/AGENTS.md in your actual home directory.
Skills.
It also supports skills, and their locations span the project level (.kimi-code/skills/ and .agents/skills/), the user level (~/.kimi-code/skills/ and ~/.agents/skills/) as well as any other directories listed under extra_skill_dirs in your config.toml.
You can invoke them using /skill:<name> and even pass some arguments to them, like here:
/skill:review-pr #1234There are two frontmatter fields that are responsible for the automatic triggering of instructions:
name: deploy
description: Push the current build to production.
type: flow
whenToUse: Only when the user explicitly asks to deploy.type— set it toflow(it's human-only, the model can't call it)whenToUse— this is an inverse control property, so here you can limit cases in which you want this instruction to be run by the model
No rule layer.
There's no rules layer here (no support for globs that would allow it to automatically load an instruction file when the agent enters a file with a given path), so we can't use the middle row of the table. There are two ways one could go about creating their own convention for a subdirectory:
add it to the always-loaded
AGENTS.mdfile, and bear the cost on every turncreate a skill with
whenToUseset to the name of the subdirectory, and make peace with the fact that the model will trigger it based on the description instead of the path
The latter is the best solution if it's a lengthy instruction, the first one might work for single-lines conventions (which is what majority of such conventions are about).
None of these is a guarantee
The thing is that all of these are pieces of context. The agent reads them and usually listens, but none of these enforce anything. As Anthropic says in their docs, the harness "treats them as context, not enforced configuration. To block an action regardless of what Claude decides, use a PreToolUse hook instead." On the same page they also say the memory file arrives as a user message after the system prompt, so "there's no guarantee of strict compliance."
This means we need to be mindful when it comes to wording too; if we were to write "never touch production" or "don't force-push", we'd ask a model to ignore its natural inclination to help. That's why such things should go into the layer of permissions and hooks I cover elsewhere in this course, whilst the memory file becomes an explanation rather than a safeguard.
Four ways this goes wrong
Facts as skills — we shouldn't be surprised they are never used, as there's no request that asks to familiarise with the repo's structure; things without trigger moments can't be triggered, so we should move them
Procedures in the memory file — every turn for the rest of the project's life (including thousands of turns not connected with releases) at a cost of the whole thing. This is actually the most common case, that's why memory files get bloated
Descriptions written in paragraphs — the description is an always-on part of skills, it's what the model reads to decide if it should incorporate the body into the session, and so the cost comes back (on every turn) and it's harder to grasp what actually triggers it
Rules with a glob that applies to everything —
**/*in apathsorglobsfield is a memory file with an overhead, and a minus that it lies in a file people don't read; if sth applies to everything we should store it with everything else universal
To sum up, for all of these we can do a ten-minute-long repair — open the memory file and ask yourself per section if you think a user would find it useful on an unrelated turn, and move everything that's not useful to a rule or a skill, asking yourself the two questions I've outlined earlier to decide which.