One Rules Source for Every Agent: AGENTS.md, Ruler and rulesync
Rules sync means keeping a single source for the instructions coding agents read, so Claude Code, Codex and Cursor follow the same build commands and conventions. For plain rules, an AGENTS.md imported from a one-line CLAUDE.md is enough. Ruler and rulesync generate each agent’s file from one source when you also sync MCP servers, skills or more agents.
Your repository has a 140-line CLAUDE.md that the Claude Code users keep current, an AGENTS.md that someone wrote for Codex in the spring, and a .cursor/rules/ folder nobody has opened since. Last week the test command changed from npm test to pnpm test:unit. Only CLAUDE.md got the edit, so the Codex and Cursor sessions still run the old command, fail, and “fix” the failure their own way.
This page is for the developer or tech lead who owns that repository’s agent setup. It assumes you already know what goes into a rules file (see AGENTS.md and CLAUDE.md: concise repository context). It shows the no-tool setup first, then rulesync and Ruler with commands we ran on 2026-09-26, and a CI job that fails the pull request when the generated files drift from the source.
What you get from a single rules source
Section titled “What you get from a single rules source”- A table of which file each agent reads, including the Claude Code rule that trips most teams:
AGENTS.mdis skipped when aCLAUDE.mdexists. - The zero-dependency setup:
AGENTS.mdplus a one-line@AGENTS.mdimport. - A worked rulesync migration:
import→ edit →generate --targets claudecode,codexcli,cursor. - The Ruler equivalent, and the default that hides its output from Git.
- A GitHub Actions job that fails a pull request when someone hand-edits a generated file.
- Where a path-scoped rule lands in each agent, including the one agent that loses the scope.
- Three copy-paste prompts: merge scattered rules into one file, migrate the repository to rulesync, and prove each agent loaded the rules.
Which rules file does each agent read?
Section titled “Which rules file does each agent read?”Each agent has a native file. The sync problem exists because they differ.
| Agent | Native file | Reads AGENTS.md? | Detail that matters |
|---|---|---|---|
| Claude Code | CLAUDE.md, .claude/CLAUDE.md, CLAUDE.local.md | Only when none of those three exists in the working directory or above it (v2.1.277, latest channel) | With both files present, Claude Code reads CLAUDE.md only, unless you import AGENTS.md from it |
| Codex | AGENTS.md | Yes, natively | Since 0.150.0 an untrusted project does not supply its project-level AGENTS.md; project_doc_max_bytes defaults to 32768 bytes (32 KiB) in the Codex source, and content past it is truncated |
| Cursor | Project rules in .cursor/rules/ | Not verified on 2026-09-26 | rulesync writes .cursor/rules/overview.mdc; Ruler writes only AGENTS.md for its cursor agent id |
Three Claude Code details come from its memory documentation and change the setup:
- The fallback is either-or by default. The Project instructions setting in
/configdefaults toclaude-md-or-agents-md.claude-md-and-agents-mdloads both,claude-mdignoresAGENTS.md, andmanaged-onlydrops every project file. - A repository cannot switch that setting for the team. The value lives under the built-in
agents-mdplugin’s entry inpluginConfigs, and Claude Code ignores it in project and local settings files. It is read from~/.claude/settings.json, a--settingsfile or managed settings. - The fallback depends on the release channel. On 2026-09-26 the
stablechannel is v2.1.274, which predates v2.1.277. A teammate onstablegets noAGENTS.mdfallback at all; only an import reaches them. The fallback also reached Amazon Bedrock, Google Cloud, Foundry, LLM gateways and telemetry-off sessions only in v2.1.281.
So the fallback helps a repository that ships AGENTS.md alone and a team that is all on latest. For every other case, use the import.
Do you need a sync tool at all?
Section titled “Do you need a sync tool at all?”Start with the smallest setup that covers your agents, and add a generator only when a row below applies.
| Your situation | Use | Why |
|---|---|---|
| Claude Code and Codex, plain Markdown rules | AGENTS.md + @AGENTS.md in CLAUDE.md | No generator to forget to re-run; one file, two agents |
You also need Cursor’s .cursor/rules/*.mdc files, glob-scoped rules or Copilot’s instructions file | rulesync | It writes each tool’s native rules format from one source |
| You want the same MCP servers, skills, subagents, hooks or permissions in every agent | rulesync | Its features cover rules, mcp, subagents, commands, skills, hooks, permissions |
| Five or more agents, rules as concatenated Markdown, minimal config | Ruler | 30+ agent ids; one folder of Markdown files concatenated into each agent’s file |
| One agent only | Its native file | A sync layer adds a moving part and buys nothing |
Share AGENTS.md with Claude Code through an import
Section titled “Share AGENTS.md with Claude Code through an import”This is the setup to try first. AGENTS.md is the source of truth; CLAUDE.md is one line plus anything that only Claude Code needs.
-
Move the shared rules into
AGENTS.mdat the repository root. Keep build and test commands, conventions, and “never do X” rules there. The prompt after these steps does the merge for you. -
Replace
CLAUDE.mdwith an import:@AGENTS.md## Claude Code only- Use the Explore subagent before editing more than three files.Claude Code expands
@pathimports at launch, reads the imported file first, then the rest ofCLAUDE.md. Relative paths resolve from the file that contains the import, and imports nest up to four hops. An@pathinside backticks or a fenced code block stays literal text. -
Check that each agent loaded the rules.
Start a new session, run
/contextand confirmCLAUDE.mdappears under Memory files. The import never loadsAGENTS.mdtwice, whatever the Project instructions value is.Confirm the project is trusted, because an untrusted project does not supply its
AGENTS.md(0.150.0). Then run the verification prompt below in a new session.Cursor’s
AGENTS.mdhandling could not be verified on 2026-09-26. Run the verification prompt below in a new Agent chat. If the answer does not quote your rules, generate.cursor/rules/with rulesync as shown in the next section.
A symlink (ln -s AGENTS.md CLAUDE.md) also works, with two costs from the Claude Code documentation: the Edit and Write tools refuse to write through it, and a Windows clone without core.symlinks checks it out as a one-line text file. Prefer the import in a mixed-OS team.
Sync rules to every agent with rulesync
Section titled “Sync rules to every agent with rulesync”rulesync keeps its source in .rulesync/ and writes each tool’s native files. This worked example migrates a repository whose rules live in CLAUDE.md and .claude/rules/ today, following the path import → edit → generate.
-
Install rulesync and pin the version. The package name is
rulesync, and the target ids areclaudecode,codexcliandcursor, notclaudeorcodex.Terminal window npm install -g rulesync@21.0.0# or: brew tap dyoshikawa/rulesync https://github.com/dyoshikawa/rulesync && brew install rulesyncInstall is the same for every agent; rulesync and Ruler are standalone CLIs, not plugins.
rulesync published majors 18, 19, 20 and 21 between 2026-09-24 and 2026-09-26 (npm registry), so an unpinned
npx rulesynccan change your output between two CI runs. -
Import what you have. Skip
rulesync initin a repository that already has rules (see the caution after these steps).Terminal window rulesync import --targets claudecodemv .rulesync/rules/CLAUDE.md .rulesync/rules/overview.mdObserved:
importcopiesCLAUDE.mdto.rulesync/rules/CLAUDE.mdas a root rule and each.claude/rules/*.mdfile to its own rule. The rename is optional; without it the Cursor file is calledCLAUDE.mdc. -
Write a minimal
rulesync.jsoncat the repository root, so every later command, including the CI check, needs no flags:{"$schema": "https://github.com/dyoshikawa/rulesync/releases/latest/download/config-schema.json","targets": ["claudecode", "codexcli", "cursor"],"features": ["rules"],"delete": false}"delete": falsekeeps rulesync from removing files it did not write. Add"mcp"tofeaturesonce.rulesync/mcp.jsoncholds your servers. -
Edit the source. The root rule after the rename looks like this:
---root: truetargets: ["*"]description: "Project overview, commands and hard rules"globs: ["**/*"]---# Commands- Install: `pnpm install --frozen-lockfile`- Test: `pnpm test:unit` (CI runs exactly this)- Typecheck: `pnpm typecheck`# Hard rules- Never bind a `Date` to D1; convert to an ISO string first.- Every pull request links an issue and lists the commands you ran.targets: ["*"]sends the rule to every tool; list tool ids to narrow it. A block such ascursor: { alwaysApply: true }in the frontmatter passes an option to one tool’s format (rulesync file-format reference). -
Generate the native files, preview first if you like:
Terminal window rulesync generate --dry-runrulesync generateObserved output:
CLAUDE.md,AGENTS.mdand.cursor/rules/overview.mdc, plus one file per non-root rule (next table). rulesync lists only files whose content changed, so right afterimportthe Claude Code files may not appear in the output. Runrulesync doctorfor a read-only configuration check. -
Commit
.rulesync/,rulesync.jsoncand the generated files together. Committed output is what Codex cloud tasks, review bots and a fresh clone see without running rulesync.
Where does a path-scoped rule land in each agent?
Section titled “Where does a path-scoped rule land in each agent?”A root rule loads everywhere. A non-root rule with globs is where the agents differ, and this is the detail the rulesync README does not spell out. We generated this rule and read the output:
---root: falsetargets: ["*"]globs: ["src/billing/**"]---Billing: amounts are integer cents.| Agent | Observed output (rulesync 21.0.0) | Scope kept? |
|---|---|---|
| Claude Code | .claude/rules/billing.md with paths: [src/billing/**] | Yes: loads when Claude reads a matching file |
| Cursor | .cursor/rules/billing.mdc with globs: src/billing/** | Scoped in the file; how Cursor applies it was not verified (cursor.com unreachable) |
| Codex | The rule text appended to the root AGENTS.md | No: every Codex session loads it, and it counts toward the 32 KiB budget |
So every scoped rule you add costs Codex context in every session. For a monorepo, the rulesync file-format reference documents an agentsmd.subprojectPath option that writes a nested AGENTS.md per package instead; we did not run it.
What each agent receives, and how to confirm it:
CLAUDE.md at the root and scoped rules in .claude/rules/. Because CLAUDE.md exists, the default Project instructions mode ignores the generated AGENTS.md, so the rules load once. Confirm with /context under Memory files. Do not switch to claude-md-and-agents-md here: it would load the same rules twice.
AGENTS.md at the root, its native file, with scoped rules appended. Keep it under the 32 KiB project_doc_max_bytes default (wc -c AGENTS.md), and trust the project so Codex reads it.
.cursor/rules/overview.mdc and one .mdc per scoped rule. Open a new Agent chat and run the verification prompt below.
Run the second prompt in a fresh session of each agent after every rules change. An agent that answers “not loaded” or reads the file first did not get the rules at startup.
Sync rules with Ruler
Section titled “Sync rules with Ruler”Ruler concatenates Markdown from .ruler/ and writes it to each agent’s file. The package is @intellectronica/ruler; npm ruler is an unrelated assertion library last published in 2013.
npm install -g @intellectronica/ruler@0.3.44 # Node.js ^20.19, ^22.12 or >=23 (package engines)ruler init # creates .ruler/AGENTS.md and .ruler/ruler.tomlruler apply --agents claude,codex,cursor # writes CLAUDE.md and AGENTS.md, and edits .gitignoreruler revert # restores .bak files and removes generated filesRuler’s agent ids are claude, codex and cursor, so a rulesync command pasted into Ruler fails, and the other way round.
Two more behaviours to know before you adopt it. Ruler reads a repository-root AGENTS.md first, before .ruler/AGENTS.md, so a generated root AGENTS.md is fed back in on the next apply unless it is ignored. And its last npm release is 0.3.44 from 2026-06-30, although the repository was pushed on 2026-09-23.
Ruler or rulesync?
Section titled “Ruler or rulesync?”| rulesync 21.0.0 | Ruler 0.3.44 | |
|---|---|---|
| Source | .rulesync/ + rulesync.jsonc | .ruler/*.md + ruler.toml |
| Claude Code / Codex / Cursor ids | claudecode, codexcli, cursor | claude, codex, cursor |
| Cursor output | .cursor/rules/*.mdc | AGENTS.md |
| Beyond rules | MCP, skills, subagents, commands, hooks, permissions | MCP, skills, subagents |
| Git default | You decide; rulesync gitignore adds entries on request | Adds generated files to .gitignore |
| Scoped rules | Native per tool; appended to AGENTS.md for Codex | Concatenated into each agent’s single file |
| Drift check | generate --check, exit 1 on drift (we ran it) | No check flag in apply --help; use git diff |
| Release pace | Four majors in three days (Sept 2026) | Last npm release 2026-06-30 |
Popularity as of 2026-09-26 (GitHub search API, our research pass): agentsmd/agents.md 24,617 stars, intellectronica/ruler 2,934, dyoshikawa/rulesync 1,474. The agents.md site says the format is “used by over 60k open-source projects”. Both sync tools are single-maintainer projects; stars measure attention, not reliability.
Fail the pull request when rules drift
Section titled “Fail the pull request when rules drift”A generator only helps if nobody edits its output by hand. This job runs on every pull request that touches rules and fails when the committed files differ from what the source generates. It uses the pull_request trigger, holds no secrets and needs only read access.
name: rules-drifton: pull_request: paths: - ".rulesync/**" - "rulesync.jsonc" - "CLAUDE.md" - "AGENTS.md" - ".claude/rules/**" - ".cursor/rules/**" - ".github/workflows/rules-drift.yml"permissions: contents: readjobs: check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: persist-credentials: false - uses: actions/setup-node@v4 with: node-version: 22 - name: Generated rules match .rulesync/ run: npx -y rulesync@21.0.0 generate --check--check reads targets and features from rulesync.jsonc, writes nothing, and exits 1 when a generated file would change. In our run, one line appended by hand to AGENTS.md produced Files are not up to date. Run 'rulesync generate' to update. and exit code 1; the untouched repository printed ✓ All files are up to date. and exit code 0.
If you make this job a required check, drop the paths: filter: GitHub leaves a required check pending on any pull request that the filter skips, and that pull request cannot merge.
The example pins actions/checkout and actions/setup-node by their v4 tag for readability. A tag can be moved, so on a protected branch pin each action to its full commit SHA with the version as a trailing comment.
A second pattern works for any generator, including Ruler. It is our suggestion, not a feature of either tool: regenerate, then let Git decide.
- name: Generated rules match the source (git variant) run: | npx -y rulesync@21.0.0 generate git diff --exit-code -- CLAUDE.md AGENTS.md .claude/rules .cursor/rules test -z "$(git status --porcelain -- CLAUDE.md AGENTS.md .claude/rules .cursor/rules)"The last line catches a generated file that is new and untracked, which git diff alone misses. For Ruler, replace the first line with npx -y @intellectronica/ruler@0.3.44 apply --agents claude,codex,cursor --no-gitignore. Add a CODEOWNERS entry for .rulesync/ (or .ruler/) so the rules owner approves every change to the source.
How do you know the agents follow the synced rules?
Section titled “How do you know the agents follow the synced rules?”A green drift check proves the files match. It does not prove that any agent obeys them. Verify the behaviour in three layers, cheapest first:
- Load check. The verification prompt above, in a fresh session of each agent, after every rules change. The rules owner runs it and pastes the three answers into the pull request.
- Enforce what matters in code, not prose. Rules are context, not enforcement; the Claude Code memory documentation calls them “context rather than enforced configuration”. Put “tests must pass” in CI and a pre-commit hook; keep the rule text as the explanation.
- Measure a rules change before you roll it out. Run a fixed set of tasks against the old and new rules and compare pass rates, as in evaluating CLAUDE.md changes.
The tech lead signs off on the source change; CI signs off on the generated files.
What does a shared rules file cost in context?
Section titled “What does a shared rules file cost in context?”Every line in the root rules file loads into every session of every agent before the first prompt. Claude Code’s documentation targets under 200 lines per CLAUDE.md, because longer files consume more context and reduce adherence. Codex truncates project docs at project_doc_max_bytes (32768 bytes by default in the source), so the rules at the end of a long generated AGENTS.md, which is where rulesync appends scoped rules, are the ones Codex loses. Imports do not reduce the cost: an @AGENTS.md import loads at launch like the file itself. The saving from a sync tool is duplication you avoid, not tokens. To cut tokens in Claude Code and Cursor, move file-type rules into glob-scoped rules (globs: in rulesync); for Codex, use nested AGENTS.md files, which Codex concatenates from the project root down to the directory it starts in (Codex source, 2026-09-26). Then follow pruning context files.
What breaks when you sync rules across agents?
Section titled “What breaks when you sync rules across agents?”Codex follows old rules after an edit. Symptom: AGENTS.md changed but Codex runs the old test command. Cause: someone edited CLAUDE.md or the source and never regenerated, or the project is untrusted in Codex. Recovery: run rulesync generate, commit, and add the drift job; mark the project trusted.
Claude Code ignores AGENTS.md for one teammate. Symptom: the verification prompt answers “not loaded” on one machine only. Cause: a CLAUDE.local.md or .claude/CLAUDE.md on the path, the stable channel (v2.1.274 on 2026-09-26), or Project instructions set to claude-md. Recovery: add the @AGENTS.md import to a committed CLAUDE.md, which works on every channel and setting except managed-only.
Rules load twice. Symptom: /context lists both CLAUDE.md and AGENTS.md under Memory files, with the same content in each. Cause: a generator wrote identical content to both files and the user chose claude-md-and-agents-md. Recovery: return to the default mode, or generate CLAUDE.md as the one-line import.
A hand edit disappears. Symptom: a fix to CLAUDE.md is gone after the next generate. Cause: generated files are output; generate overwrites them from the source. Recovery: make the edit in .rulesync/rules/. Start the root rule’s body with <!-- Generated from .rulesync/rules/. Edit there. -->; in our run rulesync copied that line to the top of CLAUDE.md and AGENTS.md, so humans and agents see where the source is.
Hand-written rules vanish after the first generate. Symptom: .claude/rules/ or .cursor/rules/ files that nobody imported are gone. Cause: "delete": true from rulesync init, or --delete. Recovery: restore them with git checkout -- .claude/rules .cursor/rules, run rulesync import for each tool that owns them, and set "delete": false.
Reviewers and cloud agents see no rules. Symptom: the rules work locally, but a Codex cloud task or review bot ignores them. Cause: Ruler’s default .gitignore block, or rulesync gitignore. Recovery: remove the entries, commit the output, and keep the drift check.
A rulesync upgrade changes the output. Symptom: the drift job fails on the first rules pull request after someone bumped the pinned version. Cause: an unpinned or bumped major. Recovery: pin the same version locally and in CI, and bump it in its own pull request with the regenerated files.