AGENTS.md read the way Claude Code reads CLAUDE.md, as a plugin, under
one option, instructionFiles:
- claude-md: only- CLAUDE.mdis loaded, by the engine, as today. The plugin adds nothing.
- claude-md-or-agents-md(the default): a project with no instruction files of its own gets its- AGENTS.mdfiles instead, loaded exactly where and how- CLAUDE.mdwould be. "Of its own" is read off what the engine loaded for the context: a- CLAUDE.md,- .claude/CLAUDE.mdor- CLAUDE.local.mdin any directory from the root down to the working directory leaves the whole project to the engine, and the plugin stays out (the organization's managed file, the person's- ~/.claude/CLAUDE.md, a- .claude/rulesfile and an added directory's- CLAUDE.mddo not count, as the nested walk does not see them either). With none, every- AGENTS.mdand- .claude/AGENTS.mdon that path joins the instruction files the engine renders, and a- Readunder a subdirectory attaches that directory's- AGENTS.mdunless a- CLAUDE.mdthere claims it.
- claude-md-and-agents-md: every- AGENTS.mdis loaded beside- CLAUDE.md, up and down the tree; a file- CLAUDE.mdalready- @-imports, or is a link to, is not loaded a second time (compared by path, then by content).
- managed-only: the project's checked-in and private instruction files and the person's own are dropped from the context; the organization's managed- CLAUDE.mdand the engine's memory stay. The engine's nested- CLAUDE.mdattachments on- Readare not an event yet and still arrive. (The engine's- claudeMdExcludessetting also exists, for user, project and local files, and applies to the- AGENTS.mdfiles this plugin reads too.)
How the files reach the model is the engine's doing, not the plugin's:
prompt.context hands a hook the instruction files behind claudeMd
({ path, kind, content, parent? }, kinds managed, user, project,
local, memory, in load order) and a hook answers the list changed. The
engine then renders claudeMd from the answered files with its own preamble
and framing, announces them by name, and keeps only the managed ones for an
agent that omits project instructions (Explore, Plan, a custom agent with
omitClaudeMd). So an AGENTS.md this plugin adds as a project file is, to
everything downstream, a project instruction file: same place in the context,
same framing, same omission rules, same announcement. An organization's
prepended plugin on prompt.context sits above this one and has the last
word on the files.
hooks/register.ts is the module; everything under hooks/ is its parts,
importing claude-code and one another alone. tests/ runs under
claude plugin test <this folder>.
As a built-in its option is the /config row "Project instructions", a
picker over the four values, each described there. By hand it is
{
"pluginConfigs": {
"agents-md@builtin": {
"options": { "instructionFiles": "claude-md-and-agents-md" }
}
}
}in user settings (~/.claude/settings.json), --settings, or managed
settings; a project's .claude/settings.json is not read for plugin
options. Changing it reloads the module, and the next context the engine
builds (the next turn after the reload, a new conversation, /clear, a
compaction) carries the new mode's files. A hand-typed value outside the
four is told once in the transcript and reads as the default. /plugin
lists the plugin among the built-ins, where a person can turn it off; with
it off the engine reads CLAUDE.md alone. No hooks setting or CLI mode turns
it off (disableAllHooks, allowManagedHooksOnly and --bare govern
settings hooks and installed plugins, not built-ins); where the engine loads
no instruction files (--bare without --add-dir, --safe-mode,
CLAUDE_CODE_DISABLE_CLAUDE_MDS) its walk finds none and it adds none,
CLAUDE.md and AGENTS.md alike.
The option was first keyed projectInstructions, with the values claude,
agents-fallback, both and none. A value still stored under that key is
honoured for now while instructionFiles reads as its default: none as
managed-only, claude as claude-md, agents-fallback as
claude-md-or-agents-md, both as claude-md-and-agents-md, any other
value as claude-md (which adds nothing, never as the default, which loads
AGENTS.md); the first session.start of a load says in the transcript how
it was read. Once instructionFiles is set to anything but its default, the
old key is not read and the transcript says to remove it.
Run from this folder instead (claude --plugin-dir mods/agents-md), the
same entry is keyed "agents-md".
fs.ancestors (with each found file's parts: the file and its imports
apart; with below on a Read; it finds nothing on a thin client, whose
workspace files are remote, as the engine's own walk does), session.root,
session.cwd, env.get (HOME and USERPROFILE, once per load, the
profile first on a Windows spelling of the working directory, so a ~/ path
the model hands a Read resolves where the Read tool reads it; CLAUDE_CODE_SIMPLE
and CLAUDE_CODE_DISABLE_ATTACHMENTS on every Read), ui.log,
telemetry.log and telemetry.mark.
$.telemetry is the telemetry plugin's noun; where that
plugin is not seated the calls find no noun and are dropped without a trace,
and nothing else changes.
Counts and closed choices only; no path and no file text. Each row goes
through $.telemetry.log, so it exists only where the telemetry plugin
does:
All of these apply only to the modes that load AGENTS.md,
claude-md-or-agents-md (the default) and claude-md-and-agents-md. Each
names a loader fact a plugin cannot reach through the events it has today.
- Nested files attach on a text Readonly. The engine also attaches a directory'sCLAUDE.mdfor a file@-mentioned in the prompt, for the IDE's opened file or selection, and for theReadtool's notebook, image and PDF results.
- A nested file the plugin attaches is not registered in the loop's
read-file state, so after a compaction the engine does not restore it
among the recently read files (the plugin attaches it again at the next
Readunder that directory instead), and a change to it mid-session is not re-announced.
- /cdcarries the new tree's- CLAUDE.mdin its own notice; the plugin's files for the new tree arrive in the same next request through the engine's instructions announcement instead.
- Paths compare by spelling; the engine resolves a symlinked alias of the working directory before deciding a file is inside it.
- --add-dirdirectories contribute no- AGENTS.md, where the engine can load their- CLAUDE.md.
- /memoryand the- #shortcut do not know- AGENTS.mdfiles, and the engine's own initial-load row does not count them (this plugin's- agents_md_loadrow does).
- An @import outside the working directory inside anAGENTS.mdis honoured only once the approval the engine asks for aCLAUDE.md's external imports has been given (without it the import is left out, as aCLAUDE.md's is); the approval dialog itself is raised forCLAUDE.mdimports alone.
- A subagent that is not a fork gets a nested AGENTS.mdat its own firstReadunder that directory even when its parent's loop was already given it; the engine does not hand such a subagent the nestedCLAUDE.mdagain. A fork matches the engine on both sides.
claude plugin test mods/agents-md
tests/register.test.ts covers the default mode: a project with AGENTS.md
alone gets it as a project instruction file and one transcript line naming
it, a project with a CLAUDE.md of its own is left to the engine without a
walk, a failed walk leaves the context as handed, and the start hands
$.telemetry the mode row alone where a test seats a provider for that
noun, and goes on untouched where none is seated.