A monorepo with a single root-level AGENTS.md is like a company with one employee handbook that applies equally to the legal team and the warehouse staff. It’s better than nothing, but it’s going to be wrong most of the time for most people.
The alternative — one AGENTS.md per package — sounds obvious until you realize you’re now maintaining 12 files that all share 80% of the same content, and when you update the test runner command you have to touch all of them.
This guide shows the patterns that actually work: hierarchical AGENTS.md files with inheritance-like behavior, workspace-aware tooling configurations, and how to structure things so AI agents get the right context without duplication becoming a maintenance burden.
How Hierarchical AGENTS.md Resolution Works
Both Claude Code and OpenAI Codex read AGENTS.md files at multiple levels. When an agent is operating on a file inside packages/api/src/routes/, the resolution order is:
packages/api/AGENTS.md(most specific)packages/AGENTS.md(if it exists)AGENTS.mdat repo root (fallback)
All matching files are loaded and merged — not just the most specific one. This is the key insight: you can use the root AGENTS.md for shared policies and per-package files for local overrides, and the agent sees everything.
Claude Code specifically walks up from the working directory, collecting all AGENTS.md files it finds, then concatenates them in order from root to leaf. The result is that root rules always apply, package rules extend them.
Root AGENTS.md: What Belongs Here
The root AGENTS.md should contain only things that are genuinely universal across every package in the repo:
# AGENTS.md
## Repository Overview
This is a pnpm workspace monorepo. Packages live in `packages/`. Apps live in `apps/`.
Root build: `pnpm turbo build`
Root test: `pnpm turbo test`
Root lint: `pnpm turbo lint`
## Workspace Rules
- Never run `npm install` or `yarn install` — use `pnpm install` only
- Changes to `packages/shared-types` may require running `pnpm turbo build --filter=shared-types` before TypeScript in other packages resolves correctly
- lockfile is `pnpm-lock.yaml` — never commit changes to it without running `pnpm install` first
## Cross-Package Conventions
- Internal packages are referenced as `@company/package-name`
- Do not import across package boundaries using relative paths like `../../packages/...`
- Shared utilities live in `packages/utils` — check there before implementing something new
## CI
- Tests run on Node 20 LTS in CI
- Environment variables in CI are in `.env.ci` (committed) — local overrides in `.env.local` (gitignored)
- Never hardcode secrets; use `process.env.VAR_NAME`
## What NOT to include here
See per-package AGENTS.md for package-specific build commands, test patterns,
framework conventions, and file structure rules.
Notice what’s absent: framework-specific rules, package-level commands, API conventions. Those belong lower in the hierarchy.
Package-Level AGENTS.md Patterns
Frontend App (Next.js)
# AGENTS.md — apps/web
## Package Commands
- Dev: `pnpm dev` (runs on port 3000)
- Build: `pnpm build`
- Test: `pnpm test` (Vitest)
- Lint: `pnpm lint` (ESLint + Prettier)
- Type check: `pnpm type-check`
## Stack
- Next.js 14 App Router (not Pages Router)
- Tailwind CSS — no CSS modules
- shadcn/ui components in `src/components/ui/` — import from there, don't reimplement
- React Server Components by default; add `'use client'` only when necessary
## File Conventions
- Pages: `app/(routes)/[route]/page.tsx`
- Layouts: `app/(routes)/[route]/layout.tsx`
- Server actions: `app/(routes)/[route]/actions.ts`
- Components: `src/components/[ComponentName]/index.tsx`
- Never use default exports for components — always named exports
## API Layer
- All API calls go through `src/lib/api/` — never fetch directly in components
- Server actions handle mutations; GET data loading uses React Server Components
## Testing
- Unit tests: `.test.ts` alongside the file
- Integration tests: `src/__tests__/`
- Don't test implementation details — test behavior
Backend API (Node.js / Fastify)
# AGENTS.md — packages/api
## Package Commands
- Dev: `pnpm dev` (port 8080, hot reload via tsx watch)
- Build: `pnpm build` (outputs to dist/)
- Test: `pnpm test` (Vitest)
- Test single file: `pnpm test src/routes/users.test.ts`
- DB migrations: `pnpm migrate:up` / `pnpm migrate:down`
## Architecture
- Fastify-based HTTP API
- Routes defined in `src/routes/` — one file per resource
- Business logic in `src/services/` — never put it in routes
- Database access in `src/repositories/` — services call repositories, not the other way
## Database
- PostgreSQL via `@company/db` (our internal wrapper around Drizzle ORM)
- Schema in `packages/db/schema/`
- Running migrations requires a local DB — see `docker-compose.yml` at repo root
## Authentication
- JWT auth via `src/middleware/auth.ts` — apply to all protected routes
- `req.user` is typed as `AuthUser` after the middleware runs
- Never inline auth logic in route handlers
## Error Handling
- Use `createError()` from `src/lib/errors.ts` for all error responses
- Return RFC 7807 format: `{ type, title, status, detail }`
Shared Package (Types / Utils)
# AGENTS.md — packages/shared-types
## Package Commands
- Build: `pnpm build` (must run before other packages can import from here)
- Type check: `pnpm type-check`
- No test runner — types are validated by tsc
## Rules
- This package exports TypeScript types only — no runtime code
- All types must be exported from `src/index.ts`
- No third-party dependencies — this package must stay zero-dependency
- Breaking changes require a major version bump and updating all consumers
## When to Add Types Here
- Types shared by 2 or more packages belong here
- Types used by only one package stay in that package
- Never duplicate a type that already exists here
Turborepo and Nx: Per-Task Context
One pattern that works well with Turborepo is keeping task-specific context in a docs/agent-context/ directory within each package, and referencing it from AGENTS.md.
# AGENTS.md — packages/api
## Commands
- Test: `pnpm test`
- See `docs/agent-context/testing.md` for test patterns and common pitfalls
- See `docs/agent-context/db.md` for database migration workflow
This keeps the AGENTS.md itself short and scannable while making deeper documentation available on demand. Claude Code follows the @docs/agent-context/testing.md import syntax, which lets you reference these without the agent having to guess where to look.
For Nx workspaces, the project configuration in project.json is already per-package, and AGENTS.md files layer naturally on top:
# AGENTS.md — apps/dashboard
## Nx Commands
- Build: `nx build dashboard`
- Test: `nx test dashboard`
- Affected test (from CI): `nx affected --target=test --base=main`
## Nx-Specific Behavior
- Nx caches build outputs — if your change isn't reflected, run `nx reset` first
- Library boundaries are enforced via ESLint `@nx/enforce-module-boundaries`
- Check `nx graph` to visualize dependency chain before making cross-package changes
The Deduplication Problem
The obvious failure mode: your root AGENTS.md says “use Vitest for tests” and so does every package AGENTS.md. When you migrate to a different test runner, you update 9 files and miss 3.
The solution is a strict division of concerns enforced as a team rule, not a technical constraint:
| Root AGENTS.md | Package AGENTS.md |
|---|---|
| Workspace manager (pnpm) | Framework (Next.js, Fastify) |
| Root-level scripts | Package-level scripts |
| Cross-package conventions | Internal file structure |
| CI environment facts | Package-specific env vars |
| Shared tooling versions | Local dev setup |
If something appears in both root and package files, it should be because the package is overriding or extending the root policy — not because of copy-paste.
Measuring What the Agent Actually Sees
One thing worth doing before finalizing your AGENTS.md hierarchy: check how much token context the agent receives by printing the concatenated result.
# Simulate what an agent sees when working in packages/api/src/
find . -name "AGENTS.md" -path "*/packages/api/*" -o -name "AGENTS.md" -maxdepth 1 | \
sort | xargs cat | wc -w
In a real project, concatenating root + one package AGENTS.md typically lands between 600-1200 words. Above 2000 words, you’re competing with your own code for context space. If you’re approaching that limit, trim first by removing examples (keep rules, drop illustrations) and second by moving reference docs to the docs/agent-context/ pattern above.
Claude Code’s context window is large enough that this rarely becomes a hard limit, but it does affect instruction priority — content near the end of the concatenated file tends to get less weight.
Workspace-Scoped .gitignore Pattern
One practical consideration: if you’re generating AGENTS.md files from a template (common in large orgs), add a comment indicating the source:
# AGENTS.md — packages/api
# Generated from: scripts/templates/api-package-agents.md
# Last updated: 2026-04-01 by @yourusername
# Manual overrides below this line are preserved during regeneration
This signals to agents (and humans) that the base content is canonical, and anything below the line is intentional local customization.