1+# agents-md
2+3+`AGENTS.md` read the way Claude Code reads `CLAUDE.md`, as a plugin, under
4+one option, `instructionFiles`:
5+6+- `claude-md`: only `CLAUDE.md` is loaded, by the engine, as today. The plugin
7+ adds nothing.
8+- `claude-md-or-agents-md` (the default): a project with no instruction files
9+ of its own gets its `AGENTS.md` files instead, loaded exactly where and how
10+`CLAUDE.md` would be. "Of its own" is read off what the engine loaded for
11+ the context: a `CLAUDE.md`, `.claude/CLAUDE.md` or `CLAUDE.local.md` in any
12+ directory from the root down to the working directory leaves the whole
13+ project to the engine, and the plugin stays out (the organization's managed
14+ file, the person's `~/.claude/CLAUDE.md`, a `.claude/rules` file and an
15+ added directory's `CLAUDE.md` do not count, as the nested walk does not see
16+ them either). With none, every `AGENTS.md` and `.claude/AGENTS.md` on that
17+ path joins the instruction files the engine renders, and a `Read` under a
18+ subdirectory attaches that directory's `AGENTS.md` unless a `CLAUDE.md`
19+ there claims it.
20+- `claude-md-and-agents-md`: every `AGENTS.md` is loaded beside `CLAUDE.md`,
21+ up and down the tree; a file `CLAUDE.md` already `@`-imports, or is a link
22+ to, is not loaded a second time (compared by path, then by content).
23+- `managed-only`: the project's checked-in and private instruction files and
24+ the person's own are dropped from the context; the organization's managed
25+`CLAUDE.md` and the engine's memory stay. The engine's nested `CLAUDE.md`
26+ attachments on `Read` are not an event yet and still arrive. (The engine's
27+`claudeMdExcludes` setting also exists, for user, project and local files,
28+ and applies to the `AGENTS.md` files this plugin reads too.)
29+30+How the files reach the model is the engine's doing, not the plugin's:
31+`prompt.context` hands a hook the instruction files behind `claudeMd`
32+(`{ path, kind, content, parent? }`, kinds `managed`, `user`, `project`,
33+`local`, `memory`, in load order) and a hook answers the list changed. The
34+engine then renders `claudeMd` from the answered files with its own preamble
35+and framing, announces them by name, and keeps only the `managed` ones for an
36+agent that omits project instructions (Explore, Plan, a custom agent with
37+`omitClaudeMd`). So an `AGENTS.md` this plugin adds as a `project` file is, to
38+everything downstream, a project instruction file: same place in the context,
39+same framing, same omission rules, same announcement. An organization's
40+prepended plugin on `prompt.context` sits above this one and has the last
41+word on the files.
42+43+`hooks/register.ts` is the module; everything under `hooks/` is its parts,
44+importing `claude-code` and one another alone. `tests/` runs under
45+`claude plugin test <this folder>`.
46+47+## Setting the option
48+49+As a built-in its option is the `/config` row "Project instructions", a
50+picker over the four values, each described there. By hand it is
51+52+```json
53+{
54+"pluginConfigs": {
55+"agents-md@builtin": {
56+"options": { "instructionFiles": "claude-md-and-agents-md" }
57+ }
58+ }
59+}
60+```
61+62+in user settings (`~/.claude/settings.json`), `--settings`, or managed
63+settings; a project's `.claude/settings.json` is not read for plugin
64+options. Changing it reloads the module, and the next context the engine
65+builds (the next turn after the reload, a new conversation, `/clear`, a
66+compaction) carries the new mode's files. A hand-typed value outside the
67+four is told once in the transcript and reads as the default. `/plugin`
68+lists the plugin among the built-ins, where a person can turn it off; with
69+it off the engine reads `CLAUDE.md` alone.
70+71+The option was first keyed `projectInstructions`, with the values `claude`,
72+`agents-fallback`, `both` and `none`. A value still stored under that key is
73+honoured for now while `instructionFiles` reads as its default: `none` as
74+`managed-only`, `claude` as `claude-md`, `agents-fallback` as
75+`claude-md-or-agents-md`, `both` as `claude-md-and-agents-md`, any other
76+value as `claude-md` (which adds nothing, never as the default, which loads
77+`AGENTS.md`); the first `session.start` of a load says in the transcript how
78+it was read. Once `instructionFiles` is set to anything but its default, the
79+old key is not read and the transcript says to remove it.
80+81+Run from this folder instead (`claude --plugin-dir mods/agents-md`), the
82+same entry is keyed `"agents-md"`.
83+84+## What it hooks
85+86+| event | what the hook does |
87+| --- | --- |
88+| `session.start` | in every mode: passes the start straight through and floats the usage row for the configured mode, never awaited; the first start of a load logs how a stored `projectInstructions` value is read. The session's start never waits on this plugin |
89+| `prompt.context` | under `claude-md-or-agents-md` and `claude-md-and-agents-md`: walks `$.fs.ancestors` for the `AGENTS.md` files above the working directory and answers them as `project` instruction files, each `@` import its own entry after its file, each placed where a project file of its directory stands (root first, before the first deeper project file, else after the last project file, before memory); files the engine already holds by path or by content are left out; under `claude-md-or-agents-md` it answers nothing when the project has a `CLAUDE.md` of its own (among the handed files, else found by a `$.fs.ancestors` walk, so a `CLAUDE.md` the engine loaded and then withheld still counts), and logs which files it loaded once, and again after a move to another project root; handed unknown files (a hook above rewrote the `claudeMd` text) it adds nothing; the first context of a load sends the load row (counts) and the feature mark; under `managed-only` (matcher: a `project`, `local` or `user` file present): answers the list without those kinds |
90+| `agent.spawn` on `fork: true` | under `claude-md-or-agents-md` and `claude-md-and-agents-md`: a fork the Agent tool starts shares its parent's prompt prefix, so the parent loop's delivered nested files are copied to the fork's loop and not attached to it again (a `/fork` or `/subtask` fork does not raise `agent.spawn` yet and starts from an empty set, as every fork did before; a fork started in the same tool batch as a `Read` inherits that Read's file although its prefix holds a placeholder for it) |
91+| `tool.call` on `Read` | under `claude-md-or-agents-md` and `claude-md-and-agents-md`, for a file under the session's project root (`$.session.root()`, read live, so `/cd`, a host's directory change and worktree moves are followed and a moved root starts the delivered sets and the fallback decision over; a file elsewhere gets nothing, as the engine attaches no nested `CLAUDE.md` there): walks only the directories strictly between the root and the read file (`$.fs.ancestors` with `below: root`, as the engine walks only those for a nested `CLAUDE.md`, never up to the filesystem root again) and attaches their `AGENTS.md` files not yet given to that agent loop, not already among the context's instruction files (by path or, for a project file, by text) and not claimed by a `CLAUDE.md` of the same directory (or imported by one), as `context` after the tool result, framed `Contents of <path>:` byte for byte as the engine frames a nested `CLAUDE.md`, whatever its size; each file once per loop and conversation (the context's recomputation after a compaction or `/clear` starts the count over), the context's files never; a Read that attached files sends the nested row. A `~` or `~/` path is read under the home directory as the Read tool reads it |
92+93+## What it calls on `$`
94+95+`fs.ancestors` (with each found file's `parts`: the file and its imports
96+apart; with `below` on a Read; it finds nothing on a thin client, whose
97+workspace files are remote, as the engine's own walk does), `session.root`,
98+`session.cwd`, `env.get` (`HOME` and `USERPROFILE`, once per load, the
99+profile first on a Windows spelling of the working directory, so a `~/` path
100+the model hands a Read resolves where the Read tool reads it), `ui.log`,
101+`telemetry.log` and `telemetry.mark`.
102+103+`$.telemetry` is the [telemetry](../telemetry) plugin's noun; where that
104+plugin is not seated the calls find no noun and are dropped without a trace,
105+and nothing else changes.
106+107+## What it logs
108+109+Counts and closed choices only; no path and no file text. Each row goes
110+through `$.telemetry.log`, so it exists only where the telemetry plugin
111+does:
112+113+| event | when | properties |
114+| --- | --- | --- |
115+| `agents_md_mode` | once per fresh load, at `session.start` | `mode` (`claude-md` \| `claude-md-or-agents-md` \| `claude-md-and-agents-md` \| `managed-only`), `is_interactive` |
116+| `agents_md_load` | the first context of a load, under `claude-md-or-agents-md` and `claude-md-and-agents-md` | `mode`, `file_count` (`AGENTS.md` files handed to the engine), `import_count` (their `@` imports), `total_content_length`, `yielded` (`claude-md-or-agents-md` stood down for a `CLAUDE.md` of the project's own), `walk_failed`; with it one `$.telemetry.mark` for feature `agents_md`: `ok`, or `sad` with reason `walk_failed` |
117+| `agents_md_nested` | a Read that attached nested files | `mode`, `file_count` |
118+119+## Where it still differs from CLAUDE.md
120+121+All of these apply only to the modes that load `AGENTS.md`,
122+`claude-md-or-agents-md` (the default) and `claude-md-and-agents-md`. Each
123+names a loader fact a plugin cannot reach through the events it has today.
124+125+1. Nested files attach on a text `Read` only. The engine also attaches a
126+ directory's `CLAUDE.md` for a file `@`-mentioned in the prompt, for the
127+ IDE's opened file or selection, and for the `Read` tool's notebook, image
128+ and PDF results.
129+2. A nested file the plugin attaches is not registered in the loop's
130+ read-file state, so after a compaction the engine does not restore it
131+ among the recently read files (the plugin attaches it again at the next
132+`Read` under that directory instead), and a change to it mid-session is
133+ not re-announced.
134+3. `/cd` carries the new tree's `CLAUDE.md` in its own notice; the plugin's
135+ files for the new tree arrive in the same next request through the
136+ engine's instructions announcement instead.
137+4. Paths compare by spelling; the engine resolves a symlinked alias of the
138+ working directory before deciding a file is inside it.
139+5. `--add-dir` directories contribute no `AGENTS.md`, where the engine can
140+ load their `CLAUDE.md`.
141+6. `/memory` and the `#` shortcut do not know `AGENTS.md` files, and the
142+ engine's own initial-load row does not count them (this plugin's
143+`agents_md_load` row does).
144+7. An `@` import outside the working directory inside an `AGENTS.md` is
145+ honoured only once the approval the engine asks for a `CLAUDE.md`'s
146+ external imports has been given (without it the import is left out, as a
147+`CLAUDE.md`'s is); the approval dialog itself is raised for `CLAUDE.md`
148+ imports alone.
149+8. A subagent that is not a fork gets a nested `AGENTS.md` at its own first
150+`Read` under that directory even when its parent's loop was already given
151+ it; the engine does not hand such a subagent the nested `CLAUDE.md` again.
152+ A fork matches the engine on both sides.
153+154+## Testing
155+156+claude plugin test mods/agents-md
157+158+`tests/register.test.ts` covers the default mode: a project with `AGENTS.md`
159+alone gets it as a project instruction file and one transcript line naming
160+it, a project with a `CLAUDE.md` of its own is left to the engine without a
161+walk, a failed walk leaves the context as handed, and the start hands
162+`$.telemetry` the mode row alone where a test seats a provider for that
163+noun, and goes on untouched where none is seated.