Skip to content

One Setup to Rule Them All: A Model-Agnostic AI Coding Config

A practical repo layout that keeps Claude Code, Codex, Copilot, Cursor, and OpenCode reading the same rules, with honest notes on where portability breaks.

Abstract

Teams rarely converge on a single AI coding tool; a repo that only works with one of Claude Code, Codex, Copilot, Cursor, or OpenCode loses every contributor who picked a different tool. This post shows a concrete layout that uses AGENTS.md as the single source of truth, symlinks or @-imports for every other tool's expected filename, and MCP for cross-tool tooling. It also flags where the dream breaks: skills, slash commands, and Windows symlink traps.

The Problem

A team adopts Claude Code. A new hire prefers Cursor. The CLAUDE.md at the repo root sits quiet while Cursor reads .cursor/rules/. Someone hand-copies the rules. A month later, a rule lands in AGENTS.md with slightly different wording. Three files drift. Nobody owns the sync.

Add a few more pains that show up in real repos:

  • A monorepo with per-package conventions. Claude Code walks up the tree and concatenates nested CLAUDE.md files. OpenCode reads the nearest AGENTS.md. Copilot only reads .github/copilot-instructions.md at the root. Same code, different behaviour.
  • MCP servers (GitHub, database, filesystem) must live in .mcp.json (project root, Claude Code), .codex/config.toml, .vscode/mcp.json, and .cursor/mcp.json. Same URL, same auth, four formats.
  • Skills and slash commands live in .claude/skills/ and .claude/commands/. They do not automatically port to Codex, Copilot, or Cursor.
  • Windows contributors clone the repo. The symlinks arrive as plain text files reading AGENTS.md. Their AI tool silently loads nothing.

The goal of this post: pick a canonical file, make every tool read it, and be honest about where the lowest-common-denominator wall actually is.

What Each Tool Reads (April 2026)

Before choosing a layout, know what each tool actually looks for on startup. I cross-checked each entry with the tool's April 2026 docs (links at the end).

ToolCanonical fileFallbackTool directory
OpenAI Codex CLIAGENTS.mdnone.codex/config.toml
GitHub Copilot.github/copilot-instructions.md + AGENTS.md (merged)none.github/instructions/*.instructions.md
Claude CodeCLAUDE.mdAGENTS.md (if CLAUDE.md is absent).claude/{commands,skills} + .mcp.json at project root
OpenCode (sst)AGENTS.mdCLAUDE.md (legacy compat)~/.config/opencode/*
Cursor.cursor/rules/*.mdclegacy .cursorrules (deprecated).cursor/
Gemini CLIGEMINI.md (hierarchical, walks up to .git root)none~/.gemini/
AiderCONVENTIONS.md or AGENTS.md (via config)none.aider.conf.yml

Note: Anthropic's April 2026 memory docs describe AGENTS.md as a fallback when CLAUDE.md is absent, not as an equal peer. If both files exist, Claude Code reads CLAUDE.md and ignores AGENTS.md. This is the one place where different community guides phrase the answer differently, so verify with the official docs before you ship.

The table drives a simple choice: pick one file that the most tools read natively, and make sure every other tool can find it.

The Canonical Layout

Pick AGENTS.md. It is the widest-adopted name in 2026 and the one OpenAI, Copilot, OpenCode, Cursor, and others treat as canonical.

repo-root/  AGENTS.md  # single source of truth, committed  CLAUDE.md  # symlink -> AGENTS.md  .mcp.json  # MCP servers (Claude Code, project root)  .github/    copilot-instructions.md  # symlink -> ../AGENTS.md    instructions/  # path-specific rules (Copilot only)  .cursor/    rules/      main.mdc  # symlink -> ../../AGENTS.md or thin shim  .claude/    commands/  # Claude-specific slash commands    skills/  # Claude-specific skills  .codex/    config.toml  # MCP servers (Codex format)  .vscode/    mcp.json  # MCP servers (Copilot format)  GEMINI.md  # symlink -> AGENTS.md (optional)

One file carries the rules. Symlinks bridge every other filename a tool expects. Tool directories stay tool-specific.

Three Ways to Keep One Source of Truth

The unix-native approach. Git tracks symlinks. Every editor and CLI tool follows them.

bash
ln -sfn AGENTS.md CLAUDE.mdln -sfn ../AGENTS.md .github/copilot-instructions.mdmkdir -p .cursor/rules && ln -sfn ../../AGENTS.md .cursor/rules/main.mdc

For Windows teammates, add git config --global core.symlinks true before cloning. Without that flag, git checks out the symlink as a text file whose only content is the string AGENTS.md. The tool then reads that single line and thinks it is the whole project rulebook.

Option B: @-imports

Claude Code and OpenCode both inline referenced files when they see an @ prefix. This is the cleanest option if you want a shared base plus a few tool-specific lines.

markdown
# CLAUDE.md@AGENTS.md
## Claude-only additions- Use the `.claude/hooks` scripts before running tests.

Copilot and Cursor do not understand @-imports. They read the literal text. If you go this route, keep Copilot and Cursor on symlinks or pointer files.

Option C: Pointer files

The crudest option, but the most portable. A one-line CLAUDE.md:

markdown
READ AGENTS.md FIRST. All project rules live there.

LLM adherence is probabilistic. The pointer gets ignored maybe five percent of the time. Fine for a solo repo; not fine for a compliance-sensitive one.

Picking a Strategy

When symlinks win: pure unix team, no Windows contributors, you want zero duplication.

When @-imports win: you want tool-specific additions on top of a shared base, and your team is on Claude Code or OpenCode.

When neither is worth it: single tool today, no plans to switch. CLAUDE.md or AGENTS.md alone is fine. Premature unification is just another source of drift.

The MCP Layer

MCP (Model Context Protocol) is the only real cross-tool contract for tools, not rules. Every serious assistant reads MCP server definitions. But each reads from a different path and format:

  • Claude Code: .mcp.json at project root (not .claude/mcp.json, a common trap)
  • Codex CLI: .codex/config.toml per project, or ~/.codex/config.toml globally — most teams use the global file
  • Copilot (VS Code): .vscode/mcp.json (uses the servers key, not mcpServers)
  • Cursor: .cursor/mcp.json (uses mcpServers)

Four files. Same URL, same auth token, four formats. Community tooling (chezmoi recipes, YAML-to-many generators posted to Hacker News in early 2026) can generate all four from one source. Worth knowing about. Not worth depending on until one approach clearly wins. For now, budget for four config files if your repo ships real MCP servers.

The trade-off here is ugly but honest: rules port via one file, tools port via MCP, and MCP configuration itself does not port. You live with it or you pick one tool.

Automating the Setup (projen and friends)

Three symlinks and a .mcp.json take an hour to set up. A year later, someone renames a file, Windows symlinks break, and the four MCP configs drift. Automation makes CI the enforcement layer instead of goodwill.

The options split by what your team already uses.

projen — TypeScript synth

projen treats project files as code. You subclass a project, declare the files you want generated, and npx projen regenerates them. Any drift fails the synth check.

typescript
// .projenrc.tsimport { Project, TextFile, JsonFile, Component } from 'projen';import { readFileSync, existsSync, unlinkSync, symlinkSync } from 'fs';import { dirname, relative } from 'path';
class AgentSymlinks extends Component {  constructor(project: Project, private aliases: string[], private target: string) {    super(project);  }  synthesize() {    for (const alias of this.aliases) {      if (existsSync(alias)) unlinkSync(alias);      symlinkSync(relative(dirname(alias), this.target), alias);    }  }}
const project = new Project({ name: 'my-repo' });
new TextFile(project, 'AGENTS.md', {  lines: readFileSync('.rules/agents-source.md', 'utf8').split('\n'),});
new AgentSymlinks(project, [  'CLAUDE.md',  '.github/copilot-instructions.md',  '.cursor/rules/main.mdc',], 'AGENTS.md');
// One YAML source, four MCP config formatsnew JsonFile(project, '.mcp.json',  { obj: mcpFrom('mcp-servers.yml', 'claude') });new JsonFile(project, '.vscode/mcp.json', { obj: mcpFrom('mcp-servers.yml', 'vscode') });new JsonFile(project, '.cursor/mcp.json', { obj: mcpFrom('mcp-servers.yml', 'cursor') });
project.synth();

Trade-off: projen is AWS CDK territory. If your team is not already synthesizing package.json, tsconfig, and GitHub Actions with projen, adopting it just for AI config is over-budget. The win comes from one-tool-for-all-config, not one-tool-for-AI-config.

chezmoi — templates, developer-scoped

chezmoi is a dotfile manager with Go text/template support. When most of your AI config lives in $HOME (user-scoped Claude, Cursor, Codex globals), chezmoi fits better than per-repo synth. One .chezmoitemplates/AGENTS.md, then chezmoi apply renders it to every tool's expected path. Per-developer overrides use the same template system.

Purpose-built and plain scripts

A small dedicated CLI or a thirty-line shell script plus a pre-commit hook is enough for a single repo with no other automation. Do not adopt a framework you do not already use elsewhere.

Picking the tool:

  • Already on projen (AWS CDK shop)? Add an AgentConfig component. Zero new tooling.
  • Already on chezmoi for dotfiles? Add .chezmoitemplates/AGENTS.md. Zero new tooling.
  • Neither? A shell script and a CI check clears the bar. Upgrade later if the pain shows up.

The bar is "rule drift fails CI." Each of these three clears it.

The Lowest-Common-Denominator Wall

Not everything unifies. A few things stay tool-specific, and pretending otherwise misleads your team.

Slash commands. .claude/commands/ holds Claude-specific slash commands. Codex, Copilot, and Cursor do not read them. If your team depends on /deploy as a Claude slash command, that workflow does not port.

Skills. Anthropic published the SKILL.md specification as an open standard in December 2025. By April 2026, adoption is broad: VS Code ships native Skills support, GitHub Copilot's version is still labelled experimental, and Cursor, Codex CLI, OpenCode, and Goose read the same SKILL.md format. A skill written for Claude Code typically loads in the others without changes. What still differs is where each tool looks — .claude/skills/ is Claude-specific, and Copilot, Cursor, and the rest each define their own discovery paths. The format ports. The convention does not yet.

Path-specific instructions. Copilot supports .github/instructions/*.instructions.md with glob patterns. Cursor has globs: frontmatter in .mdc files. Claude has its own path scoping. These do not port. Keep the root AGENTS.md generic. Let each tool use its own path-scoping mechanism.

This is the wall. Accept it, document it in your AGENTS.md, and move on. A team that pretends total portability exists will waste a week trying to force it.

When to Not Do This

The honest version of this post admits its own audience. If any of these describe your situation, stop reading and pick one tool.

You are a single-vendor enterprise shop. Procurement chose Copilot. Security signed off on one vendor. Onboarding docs point at one install script. In this world, symlinks and @-imports buy nothing except maintenance overhead. A single copilot-instructions.md file is the whole job.

You are in a regulated environment that audits AI access. SOC 2, ISO 27001, and HIPAA auditors want a single-vendor trail for AI-assisted code generation — who saw what, when, with what system prompt. Multi-vendor AI-at-code-time complicates that trail. Pick one, document it, move on.

You are one developer. A solo repo has no drift problem. CLAUDE.md or AGENTS.md alone is enough. Revisit when a second contributor shows up.

You just adopted your first AI coding tool. Do not build infrastructure for a fleet of tools you do not have yet. Start with one. Symlinks become interesting the day someone on your team opens Cursor while the rest are on Claude Code.

Where this post earns its keep: open source repos (maintainers cannot dictate contributor tooling), agencies and consultancies (each client repo meets a different tool stack), mid-stage teams where adoption grew organically and standardization would now be retroactive political work, and any team that consciously values tool-choice autonomy.

Single-vendor standardization is a valid answer. This post is for teams where it is not the answer.

Common Pitfalls

Checking in personal overrides. CLAUDE.local.md and .claude/settings.local.json are for personal overrides. Add them to .gitignore on day one. A committed CLAUDE.local.md with someone's private experiments will confuse every teammate.

Assuming Copilot reads AGENTS.md alone. Copilot merges AGENTS.md and .github/copilot-instructions.md. If you put the full ruleset only in AGENTS.md and leave copilot-instructions.md empty, Copilot's web PR review may miss context. The symlink fix solves this.

Nested AGENTS.md in monorepos. OpenCode and Copilot read the nearest one. Claude Code concatenates up the tree. Same repo, different rule composition. Document the nesting behaviour you rely on.

Windows symlinks without core.symlinks=true. The failure mode is silent. The file appears. It reads AGENTS.md as plain text. The AI tool thinks that single line is the whole rulebook. Fix: add a CI check (test -L CLAUDE.md) that fails if any symlink has been replaced with a text file.

Drift after month three. Without a check, someone will "fix" a broken symlink on Windows by replacing it with a real copy of AGENTS.md. Two weeks later, the copy has drifted. Add a pre-commit hook or a CI job that enforces the symlink.

Metrics Worth Tracking

Not every team needs dashboards here, but a few simple checks catch most drift:

  • Number of instruction files that must change per rule update. Target: one.
  • Percent of developers whose tool of choice reads the canonical file on first clone. Target: 100 percent.
  • Drift incidents per quarter. Cases where tool A and tool B behaved differently on the same repo because they read different files.
  • Time to onboard a new AI tool to the repo. Target: under thirty minutes (add symlink, add MCP config, done).

If those numbers stay healthy, your "one setup to rule them all" is actually ruling.

Principles from the Field

The mechanics (symlinks, @-imports, per-tool directories) solve the storage problem. Practitioners who work with coding agents every day have stronger views on the content problem — what belongs in AGENTS.md and how to keep it from bloating.

Addy Osmani argues for spec-before-code discipline: AGENTS.md holds standing conventions, while a per-feature spec.md carries intent. He cites the "Curse of Instructions" finding — as you pile requirements into one prompt, per-rule adherence drops, and even frontier models struggle with dozens of simultaneous rules. Practical cap: keep AGENTS.md under ~200 lines and push path-specific rules into .github/instructions/*.instructions.md or .cursor/rules/*.mdc.

Simon Willison frames the job as "hoarding things you know how to do" — a pattern library your agent recombines, not a rule book it obeys. AGENTS.md earns its keep when it points at known patterns, not when it re-specifies the obvious.

Geoffrey Huntley (Sourcegraph Amp) hits two notes: too many active MCP tools pollute the context window, and tool descriptions are not standardized. His fix: deploy tools to the stages that need them — Jira MCP during planning, GitHub MCP during review, off otherwise. The same discipline applies to rules: the always-loaded root file stays small; path-scoped rules carry the weight.

ruler (intellectronica/ruler) is a small CLI that applies one rules source to every agent's native file. Fits alongside projen and chezmoi when you want something purpose-built and lighter than either.

One security note. Rules files are readable by humans and the agent, but the agent reads every character. The "Rules File Backdoor" technique (SC Media, 2026) injects invisible Unicode into rules files to steer the agent. Treat AGENTS.md like executable code — add a CI check for non-ASCII or non-printable characters in rules-file diffs.

Closing

The 2026 AI coding ecosystem quietly converged on a shared pattern: a single root markdown file, a tool-specific directory, and MCP for cross-tool tooling. AGENTS.md is the widest-accepted canonical name. Symlinks and @-imports bridge to every other expected filename.

The honest version of the pattern admits the wall. Rules port. MCP servers port (with format conversion). Slash commands, skills, and path-specific instructions do not. Plan your AI-tooling strategy around what ports, and keep the non-portable pieces documented and scoped.

If your team is still hand-copying rules between CLAUDE.md and .cursor/rules/main.mdc, the fix is a one-hour afternoon. Pick AGENTS.md, run three ln commands, add a CI check, and move on to harder problems.

References

Related Posts