# How to scope agent instructions to one package in a monorepo

> Give each package its own AGENTS.md, Cursor rule, Copilot instructions file, or Claude Code rule, and check which files each tool loads from inside one package.

Source: https://voxgig.com/howto/scope-agent-instructions-per-package

- Audience: platform-team
- Level: intermediate
- Verified: 2026-10-21
- Published: 2026-10-19

## Short answer

Put a package's commands and conventions in `packages/<name>/AGENTS.md` and keep the root file to what applies everywhere, with one line saying the package file wins where they disagree. Codex reads the chain from the root down to its working directory. Cursor and Copilot give precedence to the file nearest the one being edited, and Claude Code loads the file in a subdirectory when it opens something there. Path-scoped rules cover the same ground without nesting.

---
## You will need

Node 22 or later, a monorepo with at least two packages, and an agent that reads `AGENTS.md`.
Verified 2026-10-21 against Node 22.22.0. The loading rules the sample encodes come from the
documentation of [Codex](https://learn.chatgpt.com/docs/agent-configuration/agents-md),
[Claude Code](https://code.claude.com/docs/en/memory), [Cursor](https://cursor.com/docs/rules), and
[Copilot](https://docs.github.com/en/copilot/how-tos/copilot-on-github/customize-copilot/add-custom-instructions/add-repository-instructions).
The sample runs no agent. It reads two fixture repositories and reports what each tool would load,
as its documentation describes.

## Approaches compared

| Approach | When it fits | What it costs you | When to pick something else |
| --- | --- | --- | --- |
| [Claude Code rules with `paths`](https://code.claude.com/docs/en/memory) | Claude Code teams who want rules per file pattern rather than per directory | A `.claude/rules` directory one tool reads, loaded only when Claude opens a matching file, so a rule about commands can arrive after the first command | The repository is used from several agents, where `AGENTS.md` reaches all of them |
| [Copilot instructions with `applyTo`](https://docs.github.com/en/copilot/how-tos/copilot-on-github/customize-copilot/add-custom-instructions/add-repository-instructions) | Copilot code review and the cloud agent, where the request names the files | Files under `.github/instructions` that other agents ignore, and a glob to keep in step with the package layout | The agent is a terminal tool working from a directory rather than from a request |
| [Cursor `.mdc` rules with `globs`](https://cursor.com/docs/rules) | Cursor users who want a rule attached whenever a package's file is in context | One more format in `.cursor/rules`, read by one editor, with globs that stop matching after a directory move | Your team is not on Cursor, or the same text already sits in a nested `AGENTS.md` |
| [Nested AGENTS.md files](https://agents.md/) | Several agents on one repository, with a directory per package | A file per package to keep current, and loading rules that differ by tool, since Codex stops at its working directory | One package, where the root file is the package file |

Nesting and path scoping solve the same problem from two ends. A nested `AGENTS.md` is found by
where the agent is or what it opens, and every tool here reads one, though not in the same way. A
path-scoped rule is found by matching the file being edited against a glob, and each tool reads
only its own format. Codex is the outlier: it builds its chain from the working directory, so a
package below that directory never loads.

## Give each package its own file, and make the root defer

The package file carries what differs: the commands, the layout, and the conventions the package
keeps that its neighbors do not.

```md title="repo-fixed/packages/api/AGENTS.md"
# packages/api

The HTTP API, a Node 22 service with no framework.

- Test: `npm run test --workspace packages/api`
- Lint: `npm run lint --workspace packages/api`
- Routes live in `src/routes/`, one file per resource. Register a new route in `src/router.mjs`.
- Every handler validates its body against the schema in `src/schemas/` before touching the store.
```

The root file keeps what holds everywhere, and says in words what happens when the two disagree.

```md title="repo-fixed/AGENTS.md"
# Monorepo

Two npm workspaces, `packages/api` and `packages/web`, sharing one lockfile. Each package has its
own `AGENTS.md` with its test command and conventions. Where this file and a package file disagree,
the package file wins.

- Install: `npm ci`
- Test: the command in the package's `AGENTS.md`; `npm test` from the root runs every workspace
- Commit messages follow Conventional Commits.
- Do not edit `package-lock.json` by hand.
```

That sentence about disagreement is there because the tools give you three different answers
without it. [Codex](https://learn.chatgpt.com/docs/agent-configuration/agents-md) concatenates the
chain from the root down and says a closer file overrides earlier guidance because it appears later
in the prompt, which is an order, not a rule.[^1] Cursor says more specific instructions take
precedence. [Claude Code](https://code.claude.com/docs/en/memory) says that when two instructions
contradict each other it may pick one arbitrarily. One line in the root file turns all three into
the same answer.

## Mirror the package file where a tool wants its own format

Three of the four tools also read a path-scoped rule in a format of their own, and the body of each
is the package file's bullet list. Cursor attaches an `.mdc` rule when a file matching its `globs`
is in context.

```yaml title="repo-fixed/.cursor/rules/api.mdc"
---
globs: packages/api/**
alwaysApply: false
---
```

Copilot adds an `.instructions.md` file to a request when a file it names in `applyTo` is part of
that request.

```yaml title="repo-fixed/.github/instructions/api.instructions.md"
---
applyTo: "packages/api/**"
---
```

Claude Code loads a rule under `.claude/rules` when Claude reads, writes, or edits a file matching
its `paths`, and a rule with no `paths` at launch, with the same standing as the project file.

```yaml title="repo-fixed/.claude/rules/api.md"
---
paths:
  - "packages/api/**"
---
```

Four copies of one list is a maintenance problem, so generate the three rule files from the
`AGENTS.md` rather than editing them by hand. The glob is the part that drifts: a package renamed
or moved keeps its `AGENTS.md`, because the file moves with it, and loses every rule whose pattern
still names the old path.

## Ask each tool what it loads, from inside one package

The resolver encodes one loading rule per tool, taken from its documentation. Codex walks from the
root to the working directory; Claude Code reads the working directory and its parents at launch,
and the file in a subdirectory once it opens something there.

```js title="resolve.mjs"
export const tools = {
  // Codex builds the chain once, from the project root down to the working
  // directory, one AGENTS.md per directory, and stops there.
  codex: ({ root, cwd }) => chain(cwd).map((d) => join(d, 'AGENTS.md')).filter((rel) => exists(root, rel)),

  // Claude Code reads AGENTS.md from the working directory and above when no
  // CLAUDE.md is there, a subdirectory's AGENTS.md once it opens a file there,
  // and a .claude/rules file whose paths match the file or that has no paths.
  claude: ({ root, cwd, file }) => {
    const launch = chain(cwd).map((d) => join(d, 'AGENTS.md')).filter((rel) => exists(root, rel))
    const below = relative(cwd, dirname(file)).startsWith('..') ? [] : chain(dirname(file)).slice(chain(cwd).length)
    const onDemand = below.map((d) => join(d, 'AGENTS.md')).filter((rel) => exists(root, rel))
    return [...launch, ...onDemand, ...ruleFiles(root, '.claude/rules', '.md', 'paths', file, { unscoped: true })]
  },
```

The demo runs two repositories through it: `repo-mono`, whose root file names `npm test`, and
`repo-fixed`, whose root defers. For each it starts the agent inside `packages/api` to edit a
handler, then at the root to edit a web component.

```bash
node which.mjs
```

```text output
repo-mono, started in packages/api, editing packages/api/src/handler.mjs
  codex    loads AGENTS.md, packages/api/AGENTS.md
           test commands: npm test | npm run test --workspace packages/api
  claude   loads AGENTS.md, packages/api/AGENTS.md, .claude/rules/api.md
           test commands: npm test | npm run test --workspace packages/api
  cursor   loads AGENTS.md, packages/api/AGENTS.md, .cursor/rules/api.mdc
           test commands: npm test | npm run test --workspace packages/api
  copilot  loads .github/copilot-instructions.md, .github/instructions/api.instructions.md, packages/api/AGENTS.md
           test commands: npm test | npm run test --workspace packages/api
```

Every tool loads the package file, and every tool also sees the root's `npm test`, so each one
holds two commands and resolves the pair by its own rule. The agent that opened this page's problem
was reading the same two lines. The fixed repository removes the choice.

```bash
node which.mjs
```

```text output
repo-fixed, started in the root, editing packages/web/src/app.mjs
  codex    loads AGENTS.md
           test commands: none
  claude   loads AGENTS.md, packages/web/AGENTS.md, .claude/rules/web.md
           test command: npm run test --workspace packages/web
  cursor   loads AGENTS.md, packages/web/AGENTS.md, .cursor/rules/web.mdc
           test command: npm run test --workspace packages/web
  copilot  loads .github/copilot-instructions.md, .github/instructions/web.instructions.md, packages/web/AGENTS.md
           test command: npm run test --workspace packages/web
```

Started inside `packages/api`, all four tools see one command in the fixed repository. Started at
the root and editing the web package, three of them reach `packages/web/AGENTS.md` through the file
being edited, and Codex does not, because its chain ends at the working directory. The real check
is the one each tool's documentation offers: ask the agent, before it runs anything, which
instruction files are active and which test command it will use. Codex documents
`codex --cd packages/api --ask-for-approval never "Show which instruction files are active."`,
Claude Code lists its memory files under `/context`, and Copilot names the instructions file in a
response's references.

## Check it worked

The fourth test pins the Codex behavior, which is the one most likely to surprise a team that
starts every session at the root.

```js title="resolve.test.mjs"
test('started at the root, Codex never reaches a package file below the working directory', () => {
  assert.deepEqual(resolve('codex', { root: 'repo-fixed', ...atRoot }), ['AGENTS.md'])
  assert.deepEqual(testCommands('repo-fixed', resolve('codex', { root: 'repo-fixed', ...atRoot })), [])
})
```

```bash
node --test resolve.test.mjs
```

```text output
1..8
# tests 8
# suites 0
# pass 8
# fail 0
# cancelled 0
# skipped 0
# todo 0
# duration_ms 109.406805
```

Four others assert that every tool loads `packages/api/AGENTS.md` from inside `packages/api`, and
that the contradicting repository shows `2` commands to every tool where the fixed one shows `1`.
Two more check that a file at the root lists each file once and that a Claude Code rule with no
`paths` loads for any file. The last checks that a rule scoped to one package never loads for a
file in the other.

## When it goes wrong

The agent runs the root's command from inside a package. Both files name a test command and the
root says nothing about which wins, so the answer depends on the tool. Codex goes by position in
the prompt, Cursor by specificity, and Claude Code by whichever it picks. Take the command out of
the root file and state the precedence there, as the fixed repository does.

Codex ignores the package file. The session started at the root, and Codex builds its chain from
the root down to the working directory only, so a file below it is never read. Start Codex in the
package directory, or pass `--cd packages/api`, which its documentation offers for exactly this.

Claude Code ignores every `AGENTS.md` in the repository. Someone added a `CLAUDE.md` or a
`CLAUDE.local.md` in the working directory or a parent of it, and by default Claude reads
`AGENTS.md` only when none of those exists.[^2] Set the project instructions option to read both files, or
import `AGENTS.md` from the `CLAUDE.md`.

## When not to do this

Do not nest for a repository with one package. The root file is the package file, and a second
copy of it one directory down is a second thing to keep current.

Do not copy the root's rules into each package file so that the package file stands alone. Every
tool here loads the root as well, so the agent reads both copies on every turn. The day the copies
differ, you have the contradiction this page is about, with the precedence line pointing at the
wrong one.

Do not move a rule that must hold everywhere into the package files. Codex started at the root will
not read them, and Claude Code reads the file in a subdirectory only after it opens something
there. A rule about commits or secrets belongs in the root file, with a copy in
`.github/copilot-instructions.md`, because Copilot gives the nearest `AGENTS.md` precedence over
the root one.

Do not treat the resolver as the agent. It encodes what four documentation pages say, on the day
they were read, and the tools change faster than this page will. The listing command of the tool
in front of you is the authority.[^3]

## Related how-tos

- [Organize agent skills across many related repositories](/howto/organise-skills-across-a-multi-package-ecosystem)

- [Share one MCP server config across several editors](/howto/share-one-mcp-config-across-editors)

- [Migrate CLAUDE.md and .cursor/rules to AGENTS.md](/howto/migrate-cursor-rules-to-agents-md)

## Last verified

Verified 2026-10-21 against Node 22.22.0. Every output block is what the command preceding it
printed. No coding agent was run: the resolver encodes each tool's loading rule as its documentation
states it, and the two fixture repositories are read from disk.

[^1]: The Codex [instructions page](https://learn.chatgpt.com/docs/agent-configuration/agents-md)
includes at most one file per directory, preferring `AGENTS.override.md` to `AGENTS.md`, and joins
the chain from the root down. It stops adding files once the combined size reaches
`project_doc_max_bytes`, 32 KiB by default. The chain is read in the order it was built. So when a
root file is long enough to exhaust the budget, the file left out is the one nearest the work. That
is the file the same page says should override the rest.

[^2]: The Claude Code [memory page](https://code.claude.com/docs/en/memory) lists what counts for
that check: a `CLAUDE.md`, a `.claude/CLAUDE.md`, or a `CLAUDE.local.md` in the working directory
or any parent directory. A personal, uncommitted `CLAUDE.local.md` therefore switches off the
shared `AGENTS.md` for the one person who created it, which is a difficult bug to report to the
colleagues who cannot see it.

[^3]: Copilot's [documentation](https://docs.github.com/en/copilot/how-tos/copilot-on-github/customize-copilot/add-custom-instructions/add-repository-instructions)
says the nearest `AGENTS.md` in the directory tree takes precedence, and Cursor's
[rules page](https://cursor.com/docs/rules) says nested files are combined with their parents with
more specific instructions taking precedence. Nearest and more specific describe the same
arrangement from two directions. Neither page says what happens when the nearer file is the less
specific one.