How-to › Expose your API to agents

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.

Audience
Platform team
Level
intermediate
Topic
Write agent skills and instruction files
Verified

An agent fixing a route in packages/api runs npm test from the root, because that is the command the root AGENTS.md names. It waits four minutes for the web package’s browser tests to fail on an unrelated snapshot. The api package has its own test command, in a file the agent never opened. Which file an agent reads, and which wins when two disagree, depends on the tool you opened.

What you get

You will end up with a per-package layout that four tools honor and a resolver that prints which files each tool loads from one directory. The root file says which rule wins. This is for you if one repository holds packages with different commands.

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, Claude Code, Cursor, and Copilot. The sample runs no agent. It reads two fixture repositories and reports what each tool would load, as its documentation describes.

Approaches compared

ApproachWhen it fitsWhat it costs youWhen to pick something else
Claude Code rules with pathsClaude Code teams who want rules per file pattern rather than per directoryA .claude/rules directory one tool reads, loaded only when Claude opens a matching file, so a rule about commands can arrive after the first commandThe repository is used from several agents, where AGENTS.md reaches all of them
Copilot instructions with applyToCopilot code review and the cloud agent, where the request names the filesFiles under .github/instructions that other agents ignore, and a glob to keep in step with the package layoutThe agent is a terminal tool working from a directory rather than from a request
Cursor .mdc rules with globsCursor users who want a rule attached whenever a package’s file is in contextOne more format in .cursor/rules, read by one editor, with globs that stop matching after a directory moveYour team is not on Cursor, or the same text already sits in a nested AGENTS.md
Nested AGENTS.md filesSeveral agents on one repository, with a directory per packageA file per package to keep current, and loading rules that differ by tool, since Codex stops at its working directoryOne 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.

# 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.

# 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 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 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.

---
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.

---
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.

---
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.

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.

node which.mjs
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.

node which.mjs
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.

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 })), [])
})
node --test resolve.test.mjs
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

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.

Footnotes

  1. The Codex instructions page 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. ↩︎ Back to text

  2. The Claude Code memory page 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. ↩︎ Back to text

  3. Copilot’s documentation says the nearest AGENTS.md in the directory tree takes precedence, and Cursor’s rules page 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. ↩︎ Back to text

Read this page as markdown · All how-to guides

Generate the client instead of writing it#

Retries, timeouts, pagination and auth are the same problems in every client. Voxgig generates them from your OpenAPI description, in 23 languages, from one model.

Listen to Fireside, the Voxgig podcast

Since 2018, Richard Rodger has talked with developer advocates, event organizers, and the people who build developer-facing companies about the specifics of the work.