Coddy reads standing instructions from two places and injects them into the system prompt, separate from skills ({{.Skills}}).
- The workspace: the rule folders of the session working directory and its
AGENTS.md, which travel with the checkout and apply to whoever opens it; - your agent home (
~/.coddy):AGENTS.md,DESIGN.mdandrules/next toconfig.yaml, which belong to the person running Coddy and apply in every workspace.
Nothing has to be configured for either. The AGENTS.md and DESIGN.md documents come in three layers that are always read and that no setting turns off - yours, the workspace's, and those of the folders the agent works in (AGENTS.md and DESIGN.md). The rules that always apply reach the model through {{.Rules}}, and a rule scoped to paths arrives with the tool result or the message that first touches a matching path. instructions.files is empty by default and only adds files below the documents, in {{.Instructions}}.
From top to bottom in the rendered system message:
- Tools
- Skills
- Plan context (mode-dependent)
- Rules: your
AGENTS.mdandDESIGN.md, the workspace's, then the rules that always apply - Project instructions: the files of
instructions.files, in the order listed - Session memory
A template under prompts.dir decides where its blocks go, but not whether the documents arrive: what it rendered is checked for them, so one that prints neither {{.Rules}} nor {{.Instructions}}, or prints them only under a condition that is false, still gets the documents and the files of instructions.files (in its instructions slot when it has one, otherwise in a "## Project instructions" block right after it). Rules are not moved: the ones that always apply appear only where the template prints {{.Rules}}, and a template whose source does not name {{.Rules}} gets no rule scoped to paths either; a rule named with @rule:name still arrives. A template that cannot be read or rendered is replaced by a short fallback prompt, and the documents, the rules that always apply and the instruction files all follow it.
A provider caches a request by its prefix, and the system message opens every request: one byte that
changes there throws away the cached copy of the whole conversation behind it. So the rules block of
the system message holds only what does not depend on what the session touches - the AGENTS.md and
DESIGN.md pairs and the rules that always apply - and it is rendered once, when the session
starts, then reused by every turn byte for byte. An AGENTS.md edited during the session, by the
agent or by you, does not move it either: the block is read again after a
compaction, which rewrites the cached history anyway, after a config reload or a
workspace switch, and when a session is opened in a new process. To hand the model an edited file
right away, mention it: @AGENTS.md attaches its current text to your message, later in the
conversation than the older text the system message keeps until the next generation.
A rule scoped to paths never enters the system message. It rides in the conversation, where the conversation already grows, and costs its own length once:
- a tool call that reads or writes a matching file brings it in the result of that call, after
the output, under a one-line lead; a nested
AGENTS.mdcomes the same way with the call that enters its folder; - a mention brings it in the user's message as an attachment (
kind="rule"): a mention-only rule the user names, and a path-gated rule or a nestedAGENTS.mdthat a mentioned file or folder activates. See Mentions.
Either way it arrives once: while an earlier result or message that carries it with the same
text is still sent to the model, a later match attaches nothing. When a compaction folds that result
or message into its summary, or the rule's file has changed since, the next tool call or mention
that matches brings the rule again, with its current text. A call that reads or writes a rule
document itself - read internal/api/AGENTS.md, an edit of .cursor/rules/go.mdc - does not get
that document back as a rule: the output already carries its text, or the model is writing it. The
result shown in the web UI, the console and an editor is the tool's own output; the rules travel with
it only to the model.
When rules.auto_discover is true (default), Coddy reads your own folder and one project folder:
| System | Path | Notes |
|---|---|---|
user |
~/.coddy/rules/ |
Yours, not the project's: read in every workspace, never part of a checkout. A project rule of the same file name overrides it |
coddy |
.coddy/rules/ |
Coddy's own folder, the first project folder looked at |
agents-dir |
.agents/rules/ |
The tool-neutral .agents/ tree, next to .agents/skills: rules meant for every agent that works on the project |
cursor |
.cursor/rules/ |
Cursor's folder, in the .mdc dialect Coddy's own rules are written in |
claude |
.claude/rules/ |
Claude Code's folder |
codex |
.codex/rules/ |
Last; Codex keeps its *.rules command policy here, which is not a prompt rule, so only .md and .mdc files count |
agents |
nested **/AGENTS.md and **/DESIGN.md |
Not a rule folder: read whatever rules.auto_discover and rules.systems say (AGENTS.md and DESIGN.md). agents.md convention, plus the design note beside it; read on demand, only from the folders a tool enters, never walked (see Activation); hidden dirs, node_modules, vendor are not entered |
The project folders form a chain, in the order of the table: Coddy reads the first one that holds a rule file and none after it. Every agent keeps its rules in a folder of its own, and a project that works with several of them keeps the same rules in each - .cursor/rules/workflow.mdc and its Claude Code mirror .claude/rules/workflow.md - so reading every folder would hand the model each rule twice. A project with .coddy/rules/ is read from there alone; one that writes its rules for every agent in .agents/rules/ is read from there; otherwise Coddy borrows Cursor's folder, and Claude Code's when there is no Cursor folder either. A folder with no .md or .mdc file in it - empty, or holding only Codex's *.rules - does not count, and the chain moves on.
Every folder read is scanned recursively for .md and .mdc files; those are small rule folders, and nothing else of the workspace is read at session start. Your own folder joins whichever project folder was read, and when a file name appears in both, the project's file wins. Nested documents are keyed by full path, so they never collapse into each other.
Nested AGENTS.md files are read on demand, the way Codex reads the AGENTS.md chain of the folder it works in, and a DESIGN.md beside one is read with it - a folder that describes itself is read whichever of the two it wrote, the same pair the workspace root and the agent home are read for. Nothing walks the tree to find them: the first time a filesystem tool call or an attached file:// path targets a path, every such document on the chain of folders from the project root (exclusive) down to that path's directory is read and arrives with that call's result, or in the message that attached the path. Reading a/b/c/f.go pulls in a/AGENTS.md, a/b/AGENTS.md and a/b/c/AGENTS.md, plus whichever of those folders also has a DESIGN.md; a sibling folder nobody enters is never opened, and a file written after the session started is picked up the moment a tool enters its folder. Hidden directories, node_modules and vendor end the chain. run_command does not activate anything: a shell string cannot be attributed to a directory reliably.
This is what keeps a repo with vendored sibling checkouts usable — 45 nested files loaded unconditionally cost ~131k tokens of system prompt before the first question — and what keeps a session anchored on a home directory from stalling: a walk over ~ (a macOS home carries hundreds of thousands of entries under ~/Library alone, read cold and behind folder-access prompts) once held the console before its first frame. Bodies over 256 KiB are truncated, as in the first two layers.
The root pair is not part of this set: it is the second layer of the documents, always in the system prompt (AGENTS.md and DESIGN.md).
CLI: coddy rules list [--cwd DIR] prints the discovered catalog: the source folder (SOURCE), the dialect each file was read with (FORMAT), the activation mode (APPLY: auto or mention), whether the rule is in every prompt (ALWAYS: an auto rule with no patterns and no directory scope) and what activates the others (ACTIVATES ON). Under the table, Project rules folder: names the folder the project rules came from, Not read: names the folders further down the chain that hold rules too - usually another agent's copy of the same rules - and Only in a folder not read: lists the files of those folders whose name the folder read has no file for, the rules a session goes without rather than mirrors of rules it has. Could not read: names a folder of the chain that exists and could not be read; the chain passed over it. Nested AGENTS.md and DESIGN.md files are not in the table, since listing them would mean walking the workspace; a line under the table says they are read on demand from the folders a tool enters.
12 rule(s) under .
Project rules folder: .cursor/rules
Not read: .claude/rules (one project folder is read: the first of .coddy/rules, .agents/rules, .cursor/rules, .claude/rules, .codex/rules that holds a rule file)
The file extension selects the dialect a rule is read with. Both dialects are accepted in every folder, so .agents/rules/ can hold Cursor and Claude Code rules side by side and a file copied from .cursor/rules/ or .claude/rules/ keeps its meaning.
| Extension | Format | Frontmatter keys | Header without alwaysApply and without patterns |
|---|---|---|---|
.mdc |
Cursor (docs) | description, globs (comma-separated string or a list), alwaysApply |
Manual: Cursor defaults alwaysApply to false, so the body enters only on @name |
.md |
Claude Code (docs) | paths (list of globs), optional description |
Loaded unconditionally, as Claude Code does |
Shared by both dialects:
- A file without frontmatter is active from the first turn.
- Patterns (
globsorpaths; either key is accepted in either dialect) gate the rule: the body reaches the model the first time a matching file comes into play, with the tool result or the message that brought it. This is Cursor's auto-attach and Claude Code's path scoping; Coddy applies the same gate toalwaysApply: truerules that carry patterns, to keep the prompt small. - An explicit
alwaysApplyis the master switch for a header without patterns:trueis active immediately,falseis mention-only. - CRLF line endings, quoted or unquoted values,
[flow, lists],- itemlists and trailing# commentsare all read.
Cursor's .mdc headers are frequently not valid YAML: globs: **/*.go starts with the YAML alias character. Coddy reads a header as YAML first and falls back to a line reader that knows these keys, so such a file keeps its description, globs and alwaysApply instead of being treated as if it had no frontmatter.
---
description: HTTP layer conventions
globs: external/httpserver/**/*.go, docs/reference/http-api.md
alwaysApply: false
------
description: HTTP layer conventions
paths:
- "external/httpserver/**/*.go"
- "docs/reference/http-api.md"
---The two headers above mean the same thing in http-layer.mdc and http-layer.md: auto-attached once one of those files is attached or touched.
Patterns follow Cursor and Claude Code: * matches within one path segment, ** any number of directories, {a,b} alternatives, [abc] character classes. They are anchored at the project root (the session cwd): a file is matched by its path relative to the root, so internal/**/*.go matches internal/agent/react.go but not external/x.go, *.md names markdown files in the root only (use **/*.md for any depth), and a file outside the workspace, such as a sibling checkout or a module cache, never matches anything. In .mdc files several patterns are separated by commas (docs/**/*.md, README.md); a comma inside braces (*.{ts,tsx}) does not split.
Rule files already on disk may change mode after this release; coddy rules list shows the result.
- The rule folders of the project are no longer merged: only the first of
.coddy/rules,.agents/rules,.cursor/rules,.claude/rulesand.codex/rulesthat holds a rule file is read. A project that kept different rules in, say,.cursor/rulesand.claude/rulesnow gets the Cursor ones alone;coddy rules listnames the folder it skipped under the table. Put what Coddy should read in.coddy/rulesor.agents/rules, or narrow the chain withrules.systems. - A rule gated by patterns, and a nested
AGENTS.md, no longer move into the system prompt once they have matched: they arrive once with the tool result or the message that brought their path in, and again after a compaction. AGENTS.md,DESIGN.mdand the files ofinstructions.filesare read when the session starts and after a compaction, a config reload or a workspace switch, not on every turn. Mention@AGENTS.mdto hand the model an edit right away.- The
AGENTS.mdandDESIGN.mdlayers no longer depend on any setting:rules.auto_discover: false, arules.systemslist withoutagentsand aprompts.dirtemplate without{{.Rules}}used to drop the nested documents (and such a template the home pair as well); they now come in every case.instructions.filesdefaults to an empty list instead of["AGENTS.md", "DESIGN.md"], and a list you write adds files after the documents rather than replacing a default; entries naming the documents are skipped, so an existing list keeps working. A file ofinstructions.filesis now cut at 256 KiB; earlier releases sent it whole. alwaysApply: falsetogether withglobsis auto-attached once a matching file is attached or read. Earlier releases kept such a rule mention-only. Drop theglobsto keep a rule manual..mdrules withoutalwaysApplyfollow Claude Code: unconditional withoutpaths, path-gated with them. Earlier releases treated them as mention-only. WritealwaysApply: falseto keep a.mdrule manual.- A directory-less pattern such as
*.gomatches files in the project root only. Earlier releases matched it against the file name at any depth; write**/*.gofor that. - Cursor headers that are not valid YAML (
globs: **/*.go) are now read. Earlier releases dropped the whole header, so those rules were always on with no globs; they now honour theiralwaysApplyand wait for their globs like any other rule.
| Rule | Behavior |
|---|---|
Patterns (globs / paths), any alwaysApply |
Arrives once, the first time a matching file comes into play: a filesystem tool call (read, edit, write, ...) that targets a matching file brings the body in its result, and a mentioned file or folder (@src/app.go, an editor's file:// attachment) in that user message. A folder a call lists or searches (grep, glob over a directory) brings no glob rule; the call that reads or writes a matching file inside it does. After a compaction folds it away, or once its file has changed and been read again, the next match brings it again |
alwaysApply: true without patterns |
Active immediately for the session |
alwaysApply: false without patterns |
Never auto-included. Its body rides in the user message that names it: @ruleName or @rule:ruleName |
No alwaysApply, no patterns, .mdc |
Mention-only (Cursor's default) |
No alwaysApply, no patterns, .md |
Active immediately (Claude Code loads it unconditionally) |
| No frontmatter | Active immediately |
Nested AGENTS.md, DESIGN.md |
Read on demand, whatever rules.auto_discover, rules.systems and the template say. The first filesystem tool call inside its directory reads it (with every such document on the chain of folders above it) into that call's result, and the first mention of a path there into that user message; like a glob rule, it comes once, and again after a compaction or when the file has changed - an edit made during the session reaches the model with the next call in that folder. A file the system prompt already carries (named in instructions.files, or reached through a link) does not come again. Nothing is read for folders no tool enters and no mention names |
Mention-only rules use @name (file stem) or @rule:name; @rule:name also attaches any other rule of the catalog by name. They are not slash commands and do not appear in the skills catalog. run_command activates nothing: a shell string cannot be attributed to a path reliably.
A folder describes itself with a pair of files, AGENTS.md and DESIGN.md, and Coddy reads them in three layers, always and in this order:
~/.coddy/AGENTS.md, then~/.coddy/DESIGN.md- your agent home, in every workspace, so what you want is in front of the model before the checkout starts describing itself;AGENTS.md, thenDESIGN.mdof the session folder;- the pair of every folder on the chain from below the session folder down to a file the agent works with, read the moment a filesystem tool or a mention reaches that file, the way Codex reads the
AGENTS.mdchain of the folder it works in, and attached to that tool result or message (Activation).
Whichever file exists is read; a home with only a DESIGN.md contributes that one and nothing else, and a missing file costs nothing. The first two layers sit in the system prompt, the third arrives later in the conversation, since the system message does not move once a session has started (Rules and the prompt cache). Each file is cut at 256 KiB, and a hidden folder, node_modules or vendor ends the chain.
None of this is configured and none of it can be turned off: no key in config.yaml names these files, rules.auto_discover: false and rules.systems concern rule folders only, and a template under prompts.dir gets the documents whatever it prints (Prompt order). A session that should see no documents runs in a folder that has none, under an agent home that has none either. The chain starts at the session folder: nothing above it is read, not even the root of a git repository when the session was opened in one of its subfolders, and a file outside the session folder brings no documents.
Every file once. A file reaches the model at most once per rules generation, known by its real path: links are resolved, and the case of the path is ignored on Windows and macOS. The first occurrence in layer order wins, because its text is already in the prompt above anything that would repeat it:
- a session opened in
~/.coddyitself reads the pair once, as yours; - a project
AGENTS.mdthat is a link to yours is your file, read once; - an
instructions.filesentry naming a document the layers carry adds nothing, which is why a configuration that still listsAGENTS.mdandDESIGN.mdworks as before; - a nested document the system prompt already carries, through
instructions.filesor because the folder is the agent home, is not attached again when a tool enters its folder.
The two layers of the system prompt are read when the session starts and kept for as long as its rules are: an edit reaches the prompt after the next compaction, a config reload or a workspace switch, or right away through @AGENTS.md. A nested document a compaction folded away comes again with the next tool call or mention that reaches its folder; that is not a repeat.
A checkout describes itself: how it is built, what its conventions are, which commands are dangerous in it. What you want from every session - the language to answer in, the style you carry between projects, the commands you never want run - is not the project's business and does not belong in its files. Those live in your agent home, ~/.coddy, and are read in every workspace:
| File | Reaches the prompt as | Read when |
|---|---|---|
~/.coddy/AGENTS.md, ~/.coddy/DESIGN.md |
the first layer of the documents, above the project's own pair (AGENTS.md and DESIGN.md) | when a session starts, and again after a compaction, in every workspace |
~/.coddy/rules/*.md, *.mdc |
like any project rule: {{.Rules}} when they always apply, otherwise the tool result or message that brings their path in, or the message that names them |
per their own frontmatter (always on, glob-gated or @mention) |
Nothing is configured for either. Writing the file is the switch, deleting it is the off switch, and no key in config.yaml mentions them:
mkdir -p ~/.coddy/rules
$EDITOR ~/.coddy/AGENTS.mdYour pair is read first, so what you want is in front of the model before the checkout starts describing itself, and the project's own AGENTS.md and DESIGN.md follow below - they join, neither replaces the other. Write one of the two or both; the file that is not there costs nothing.
Your rules are ordinary rule files and follow the same dialects, globs and activation modes as a project's - a go.mdc with globs: **/*.go under ~/.coddy/rules/ waits for a Go file in whichever project the session opened, because globs are anchored at the workspace, not at the folder the rule came from. coddy rules list shows them with source user, and names the folder under the table. Your folder is read next to whichever project folder the chain picked, never instead of it. The project still has the last word: when a project file and one of yours share a file name, the project's wins.
Nothing here is trusted differently from a project file: both are text that steers the model, and neither can run anything by itself (see Security). The difference is who wrote it - your own folder needs no trust receipt because nothing arrives in it with a git clone.
instructions.files names files a session reads after all of the documents above, into {{.Instructions}}, at the same moments the first two layers are read. It is empty by default and only adds - the documents are read whether or not it names them - so a team or a machine lists what is particular to it:
instructions:
files:
- "/srv/agents/house-style.md" # an absolute path, shared by a fleet
- "${CWD}/docs/conventions.md" # this workspace
- "${CODDY_HOME}/team.md" # next to your config.yamlA leading ~ expands, and so do the two placeholders the config understands: ${CODDY_HOME} is the agent home (~/.coddy) and ${CWD} the workspace of the session reading the file. An absolute entry is read as it stands and a relative one resolves against the session working directory. Files are read in the order listed, at the end of the documents (the end of the "## Project instructions" block, before the session memory), each under a ### <path> heading that names it the way the documents are named: the model knows which file a passage came from, so "follow infrastructure.md" does not send it to read a file it already holds. A relative or ${CWD} entry whose file is not in the workspace is skipped without a word - which is how a single list can serve workspaces that do not all carry the same files. An entry that names the same file in every workspace (an absolute path, ~, ${CODDY_HOME}) is how several agents with configurations of their own share one set of instructions, and when its file cannot be read - it is not there for this process, as with a folder not mounted into its container, it may not be read under the account Coddy runs as, it is a folder or it is empty - the session still starts without it, but says so: a warning instructions file not read in the log with the entry, the path and the reason, the file marked (not read: <reason>) under [Context] in the console header, and a warning row for the entry in coddy --dry-run (Dry run). A workspace file that is there and cannot be read is reported the same way. Each file is cut at 256 KiB, like the documents. A file the prompt already carries is not read twice. An AGENTS.md or DESIGN.md of a subfolder that is listed here but missing when the session starts still arrives with its folder once it appears, as a nested document; any other file missing at the start is read with the next generation (a compaction, a config reload or a workspace switch).
Use /rpa-gen-rules, one of the skills of the standard delivery, so it is there on a fresh install. It reads the specs, docs and code first and derives the rules from what it finds, rather than asking you to describe the project: layered architecture from lower dependencies upward, BDD/TDD delivery, and Rules Sync for deliberate mirrors.
Root AGENTS.md carries the common baseline. Cursor .mdc and Claude Code .md rules are paired native representations with equivalent bodies and activation intent. When Codex support is requested, the skill installs a fail-open project hook under .codex/ that reads .cursor/rules/*.mdc directly; it creates neither a manual Codex index nor a third copy of rule bodies. Critical constraints stay in root policy or executable enforcement, and other host integrations are preserved only when the repository already documents and tests them.
Coddy reads one project folder, the first of the chain that holds rules (Discovery): a project the skill set up for Cursor and Claude Code is read from .cursor/rules/, and its Claude Code mirror is left alone rather than loaded twice. Rules written for Coddy itself go to .coddy/rules/, or to .agents/rules/ when every agent should read them.
There is no coddy rules generate CLI subcommand.
After each agent turn, Coddy estimates tokens per category (systemPrompt, toolDefinitions, rules, skills, mcp, conversation) and exposes them on GET /coddy/sessions/{id}/stats as contextBreakdown. The composer context ring opens a breakdown popover on click. rules counts everything this page describes as standing in the system prompt: the AGENTS.md and DESIGN.md documents, the rules that always apply and the files of instructions.files.
instructions:
# Files added after the AGENTS.md and DESIGN.md documents, which are always
# read and are not named here. Read in the order listed; ${CODDY_HOME}, ${CWD}
# and ~ expand, an absolute entry is read as it stands, a relative one
# resolves against the session working directory. Empty by default.
files: []
rules:
auto_discover: true
# Optional filter: user, coddy, agents-dir, cursor, claude, codex. A project
# folder left out is not part of the chain; of the ones admitted, the first
# that holds a rule file is read. "agents" is still accepted and changes
# nothing, the nested AGENTS.md files being always read; a list holding only
# "agents" still reads no rule folder, an empty list reads them all.
systems: []- Cursor Rules
- Claude
.claude/rules - Codex hooks
- Codex
AGENTS.md - Implementation:
internal/rules/*(the folder chain infactory.go, dialects and glob matching inmarkdown.go, directory scoping inscope.go, the document layers and the dedupe inproject_docs.goanddockey.go, the nested chain inagents.go), the entries ofinstructions.filesininternal/session/instructions_load.go, the per-session rendering of the standing parts ininternal/session/rules_load.goandinternal/agent/rules_prompt.go, their placement in a template ininternal/agent/system_prompt.go; tool-path activation and the rules a tool result carries ininternal/agent/rules_activation.goandinternal/tools/fs/toolpaths.go - Specs:
features/rules_one_folder.feature,features/rules_agents_dir.feature,features/agents_md_scoping.feature,features/global_instructions.feature, and the prompt-cache groupfeatures/prompt_cache_rules.featureandfeatures/prompt_cache_prefix.feature(make test-cache); the context a session over this repository's own rules costs is measured byBenchmarkContextOfThisRepositoryRules(make test-perf BENCH=Context) - End-to-end against a real model:
examples/cli/cli_e2e_rules.py(a project glob rule through the console) andexamples/cli/cli_e2e_global_instructions.py(anAGENTS.mdand a rule in an isolatedCODDY_HOME, a workspace with nothing in it)