AGENTS.md for Monorepos skill for Codex

Root rules once, package rules where they apply. Codex maps the workspace, prints the exact instruction chain each starting directory produces with a bundled script, then restructures root and package AGENTS.md files so shared rules appear once and package files carry only their deltas. It accounts for override shadowing, truncation at project_doc_max_bytes and sessions launched from the repo root.

  • Codex Mastery
  • $agents-md-monorepo
  • 167 lines
  • 3 files
  • ~1,900 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- agents-md-monorepo

Installs to ~/.agents/skills/agents-md-monorepo/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $agents-md-monorepo, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Designs a layered set of nested AGENTS.md and AGENTS.override.md files for monorepos and multi-service repositories, so Codex gets repo-wide rules from the root and package-specific commands from the directory it works in, without duplicated or contradictory guidance or blowing the project_doc_max_bytes budget. Use when a monorepo has packages with different toolchains, when Codex applies root rules inside a package that needs other commands, or when debugging which AGENTS.md files Codex actually loaded.

Use it when

  • A monorepo mixes toolchains or test commands per package
  • Root guidance is long enough to risk truncating package files
  • You need to know which instruction files Codex actually loaded
  • A temporary AGENTS.override.md needs to be introduced or cleaned up

Use something else when

How to check the result

Run scripts/agents_chain.py for the root and each package directory, then confirm with codex debug prompt-input that package-specific text appears when starting inside that package.

The mistake it prevents

Assuming Codex loads a package's AGENTS.md when it was started at the repo root; only ancestors of the starting directory are read.

~/.agents/skills/agents-md-monorepo/SKILL.md

--- name: agents-md-monorepo description: Designs a layered set of nested AGENTS.md and AGENTS.override.md files for monorepos and multi-service repositories, so Codex gets repo-wide rules… license: MIT ---

Done well, each directory Codex is started from yields a short, correct chain: universal rules once at the root, then only the deltas that matter for that package, with the exact commands that work there. Nothing is repeated, nothing contradicts, and the whole chain fits the byte budget.

Discovery rules you are designing for

RuleConsequence for layout
Codex walks from the project root (first ancestor with .git, or project_root_markers) down to the working directoryRules in sibling packages never load; rules in ancestors always load
One file per directory: first non-empty of AGENTS.override.md, AGENTS.md, then project_doc_fallback_filenamesAn override in a directory hides that directory’s AGENTS.md completely
Files are concatenated root firstDeeper files come later and win when they conflict
Combined size capped by project_doc_max_bytes (32 KiB default)Text past the cap is dropped, and deeper (more specific) files are the ones lost
Chain is built once per run, from the starting directoryStarting Codex at the repo root means package files are NOT loaded, even when it edits that package
Global ~/.codex/AGENTS.md (or AGENTS.override.md) loads firstPersonal preferences can leak into team runs; keep them minimal

The fourth row is the important trap: because truncation removes the tail, a bloated root file starves the package files that carry the most specific instructions.

The fifth row is the second trap. When a task spans packages or Codex is launched at the root, the root file must tell Codex where package rules live, for example: “Before editing under packages/<name>/, read packages/<name>/AGENTS.md.”

Workflow

- [ ] 1. Map packages, toolchains and where people start Codex
- [ ] 2. Print the current chain for each starting directory
- [ ] 3. Decide what lives at root vs package level
- [ ] 4. Write or refactor the files
- [ ] 5. Re-run the chain check and a live load check

1. Map the repository

cat pnpm-workspace.yaml turbo.json nx.json lerna.json go.work Cargo.toml 2>/dev/null | head -80
git ls-files | grep -E '(^|/)(package.json|pyproject.toml|go.mod|Cargo.toml|pom.xml|build.gradle(.kts)?)$' | head -50
git ls-files | grep -E '(^|/)AGENTS(\.override)?\.md$'
cat .codex/config.toml 2>/dev/null    # project_doc_* and project_root_markers can be set per project (trusted projects only)

Ask or infer where sessions start: repo root (common in the desktop app and CI), or inside a package (common for CLI users). Design for both.

2. Print the effective chain

Use the bundled script, which applies the documented lookup order, flags shadowed files and reports cumulative bytes:

python3 scripts/agents_chain.py packages/billing            # chain for one starting dir
python3 scripts/agents_chain.py . --audit                    # every instruction file and what it shadows
python3 scripts/agents_chain.py apps/web --fallback TEAM_GUIDE.md --max-bytes 65536

It exits 1 when the project files exceed the budget. Pass --fallback for each name in project_doc_fallback_filenames and --max-bytes when the limit is raised.

3. Decide placement

ContentPlace it in
Package manager, workspace-wide install, commit and PR rules, security boundariesRoot AGENTS.md
Map of packages and a pointer to package filesRoot AGENTS.md (a 5 to 15 line table)
How to run one package’s tests, lint, buildThat package’s AGENTS.md
Toolchain that differs from the root (Go service in a TS monorepo)Package AGENTS.md, stating the commands in full
A rule that must REPLACE the parent’s rule for a subtreeState the replacement explicitly in the package file (“In this package use make test, not pnpm test”)
Temporary instructions during a migration or incidentAGENTS.override.md in the affected directory, with an owner and removal date inside it
Code review rules for one service## Code Review Rules section in that service’s AGENTS.md

Prefer plain AGENTS.md files that add to their parents. Use AGENTS.override.md only when a directory’s normal file must be suppressed; it does not suppress ancestor files. To neutralise an ancestor rule, the deeper file has to contradict it in words, and the later position makes it win.

4. Write the files

Root file pattern:

# AGENTS.md (repo root)

## Workspace
- pnpm workspaces + Turborepo. Install: `pnpm install --frozen-lockfile`.
- Run a task for one package: `pnpm turbo run test --filter=@acme/billing`.
- Full CI check: `pnpm turbo run lint typecheck test`.

## Packages
| Path | Stack | Instructions |
|---|---|---|
| `apps/web` | Next.js | `apps/web/AGENTS.md` |
| `services/billing` | Go 1.23 | `services/billing/AGENTS.md` |
| `packages/ui` | React library | inherits root rules only |

Before changing files under a path listed above, read its AGENTS.md if you did not
start in that directory.

## Boundaries
- Never edit another package's generated code; regenerate it.
- Cross-package changes: update the consumer and the provider in the same change.

Package file pattern (keep it to the deltas):

# services/billing/AGENTS.md

This package is Go, not TypeScript. Root pnpm commands do not apply here.

## Commands
- Test: `go test ./...`  (one test: `go test ./internal/invoice -run TestRounding`)
- Lint: `golangci-lint run`
- Regenerate mocks: `go generate ./...`

## Rules
- Amounts are `int64` cents. Never use float64 for money.
- Migrations live in `migrations/`; never edit a merged migration.

5. Verify

  1. python3 scripts/agents_chain.py <dir> for the root and every package with its own file: right files listed, no unexpected ignored: lines, total under budget.
  2. Live check from a package directory, without a model call:
    cd services/billing && codex debug prompt-input > /tmp/pi.json && grep -c "This package is Go" /tmp/pi.json
  3. Behavioral check:
    codex exec --cd services/billing --ephemeral "Which test command applies here? Quote your instructions."
    If the quoted rules come from the wrong file, look for a stray AGENTS.override.md (in the repo or ~/.codex/) or a CODEX_HOME pointing elsewhere.

Gotchas

  • Codex launched at the repo root does not auto-load packages/x/AGENTS.md when it later edits packages/x. The root pointer table is what makes the agent go read it.
  • A nested git repository or submodule with its own .git becomes its own project root; ancestors above it stop loading. Set project_root_markers if that is wrong.
  • project_root_markers = [] makes the working directory the root, so only that one directory’s file (plus global) loads.
  • Raising project_doc_max_bytes is a fix of last resort: every byte is spent on every turn. Shorten the root first.
  • Untrusted projects skip project .codex/config.toml, so fallback names or byte limits set there are ignored until the project is trusted. Put them in ~/.codex/config.toml when they must always apply.
  • Overrides left behind after an incident keep hiding the real file. Grep for AGENTS.override.md in reviews and give each one an expiry line.

Output contract

**Chain check:** root (2.1 KB) -> services/billing (0.9 KB) = 3.0 KB of 32 KB
**Files changed:** AGENTS.md (cut from 410 to 120 lines), services/billing/AGENTS.md (new)
**Overrides:** none | services/x/AGENTS.override.md (owner, expiry)
**Verified:** prompt-input contains package rules from services/billing and apps/web

What the zip contains

Codex reads bundled files only when a step points to them. Every one is shown here, so you can review it before installing.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "AGENTS.md for Monorepos"
  short_description: "Root rules once, package rules where they apply"
  default_prompt: "$agents-md-monorepo Codex keeps running pnpm test inside our Go service. Fix the AGENTS.md layout"
scripts/agents_chain.py140 lines, 5.2 KB
#!/usr/bin/env python3
"""Show the AGENTS.md instruction chain Codex would assemble for a directory.

Mirrors the documented discovery rules: global file from CODEX_HOME
(AGENTS.override.md, else AGENTS.md), then one file per directory from the
project root down to the target directory, choosing the first non-empty file
among AGENTS.override.md, AGENTS.md and any fallback names. Reports sizes
against the project_doc_max_bytes budget and files shadowed by an override.

Usage:
  python3 scripts/agents_chain.py [TARGET_DIR] [--fallback NAME ...]
                                  [--max-bytes N] [--markers .git,.hg]
                                  [--codex-home DIR] [--audit]

Exit codes: 0 ok, 1 project instructions exceed the budget, 2 usage error.
"""
from __future__ import annotations

import argparse
import os
import sys
from pathlib import Path

PRIMARY = ["AGENTS.override.md", "AGENTS.md"]
SKIP_DIRS = {".git", "node_modules", ".venv", "venv", "dist", "build", "target", ".next", "__pycache__"}


def non_empty(path: Path) -> bool:
    try:
        return path.is_file() and path.read_text(encoding="utf-8", errors="replace").strip() != ""
    except OSError:
        return False


def pick(directory: Path, names: list[str]) -> tuple[Path | None, list[Path]]:
    """Return the file Codex would use in this directory and the non-empty files it ignores."""
    chosen = None
    shadowed = []
    for name in names:
        candidate = directory / name
        if non_empty(candidate):
            if chosen is None:
                chosen = candidate
            else:
                shadowed.append(candidate)
    return chosen, shadowed


def find_root(target: Path, markers: list[str]) -> Path | None:
    if not markers:
        return target
    for directory in [target, *target.parents]:
        if any((directory / m).exists() for m in markers):
            return directory
    return None


def chain_dirs(root: Path | None, target: Path) -> list[Path]:
    if root is None:
        return [target]
    rel = target.relative_to(root)
    dirs = [root]
    current = root
    for part in rel.parts:
        current = current / part
        dirs.append(current)
    return dirs


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument("target", nargs="?", default=".", help="directory Codex is started from (default: cwd)")
    parser.add_argument("--fallback", action="append", default=[], help="name from project_doc_fallback_filenames (repeatable)")
    parser.add_argument("--max-bytes", type=int, default=32768, help="project_doc_max_bytes (default 32768)")
    parser.add_argument("--markers", default=".git", help="comma-separated project_root_markers; empty string = cwd is root")
    parser.add_argument("--codex-home", default=os.environ.get("CODEX_HOME", str(Path.home() / ".codex")))
    parser.add_argument("--audit", action="store_true", help="also list every instruction file under the root and what shadows it")
    args = parser.parse_args()

    target = Path(args.target).resolve()
    if not target.is_dir():
        print(f"error: {target} is not a directory", file=sys.stderr)
        return 2
    names = PRIMARY + args.fallback
    markers = [m for m in args.markers.split(",") if m]
    root = find_root(target, markers)

    print(f"target:       {target}")
    print(f"project root: {root if root else '(none found; only the target directory is checked)'}")
    print(f"lookup order: {', '.join(names)}")
    print()

    global_file, global_shadowed = pick(Path(args.codex_home).expanduser(), PRIMARY)
    if global_file:
        print(f"[global] {global_file}  {global_file.stat().st_size} B")
        for s in global_shadowed:
            print(f"         ignored: {s} (an override is present)")
    else:
        print(f"[global] none in {args.codex_home}")

    total = 0
    over = False
    order = 0
    for directory in chain_dirs(root, target):
        chosen, shadowed = pick(directory, names)
        if chosen is None:
            continue
        order += 1
        size = len(chosen.read_bytes())
        total += size
        flag = ""
        if total > args.max_bytes and not over:
            over = True
            flag = "  <-- budget reached here; later text is dropped"
        print(f"[{order}] {chosen}  {size} B (cumulative {total} B){flag}")
        for s in shadowed:
            print(f"     ignored: {s}")

    print()
    print(f"project instructions: {total} B of {args.max_bytes} B budget")
    if args.audit and root is not None:
        print("\naudit of instruction files under the root:")
        for dirpath, dirnames, _ in os.walk(root):
            dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
            chosen, shadowed = pick(Path(dirpath), names)
            if chosen:
                rel = Path(dirpath).relative_to(root) or Path(".")
                line = f"  {rel}/{chosen.name}"
                if shadowed:
                    line += "  (shadows " + ", ".join(s.name for s in shadowed) + ")"
                print(line)
    if over:
        print("FAIL: combined project instructions exceed the budget", file=sys.stderr)
        return 1
    print("OK")
    return 0


if __name__ == "__main__":
    sys.exit(main())

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem