DEV Community

Janz
Janz

Posted on

How common is AGENTS.md, really? I sampled GitHub: 6.2% of active repos, 1.0% of all repos

Short version: Every rate in my previous two posts had a denominator I picked myself — the 558 repos
that already had an AGENTS.md. That is a fine way to describe a corpus and a terrible way to answer
"how common is this?". So I sampled GitHub two different ways:

  • 6.2% of active repos (pushed in the last 90 days, not a fork, not archived) contain an AGENTS.md — 51 of 817, 95% CI [4.8, 8.1]
  • 1.0% of all public repos do — 9 of 924, 95% CI [0.5, 1.8]

Same file, same counting rule, two numbers that differ by 6×. Which one you quote depends entirely on
the question you are asking, and I had been quietly dodging that choice.

Three things I did not expect, in order of how much they changed my mind:

  1. CLAUDE.md is at 5.4% of active repos. Statistically indistinguishable from AGENTS.md. If you assumed one format won, it has not.
  2. 93% of public repos have not been pushed in 90 days, 29% are forks, and 8.3% are completely empty. "GitHub" as a population is mostly a graveyard, which is why the stock rate is so low.
  3. Most AGENTS.md files in the wild are tombstones. Of the 9 files my population sample found, 7 were in repos that have not been pushed in three months.

Why the denominator was missing

Here is the trap I was in. To build the corpus I searched GitHub for repos containing an AGENTS.md, then
labeled what I found. Every percentage since — "85.7% of files prohibit things", "13.6% record a
gotcha" — has a denominator of files that already exist. Those numbers are real and I stand by them,
but they cannot answer the question every reader actually has: should I write one of these?

For that you need a sample of repos drawn independently of whether they have the file. That is a
different sampling problem, and it needs two different frames, which I kept conflating:

quantity question it answers frame
stock rate "is this mainstream?" every public repo
active rate "is this what working projects do?" repos pushed in the last 90 days
trend "is it spreading?" rates by repo creation year

Mixing them produces confident nonsense, because the stock population is dominated by abandoned
one-off repos and the active population is not.

I wrote the decision rule down before running anything, so I could not move the goalposts after
seeing the number: <1% ⇒ describe it as an early-adopter curiosity; 1–5% ⇒ "early but measurable",
and every rate must be labeled as in-corpus or ecosystem-wide; >10% ⇒ "standard practice".
It landed
in the middle band, slightly high — so: not a curiosity, not a standard either.

Method, briefly

Both frames use one tree call per repo
GET /repos/{owner}/{repo}/git/trees/HEAD?recursive=1 — and check every path with a case-insensitive
match on the filename. Recursive matters: a root-only check would miss docs/AGENTS.md and friends and
under-count. Roughly 1,900 repos, all responses cached, seed 20260918.

Frame A — active rate, by creation cohort. For each year 2010–2026 I picked one random slice of
creation time, queried
created:<slice> pushed:>2026-06-20 fork:false archived:false, pulled every result (rather than
taking the top page, which is ranked by GitHub's relevance and would bias toward popular repos), then
randomly sampled 50 repos from the slice. A 2026 week contains ~62,000 active repos, which exceeds the
1,000-result search cap, so recent cohorts narrowed to a random day and then a random hour.

Frame B — stock rate, unweighted. GitHub search cannot give you a random sample of the population —
there is no random sort, and ranking favors stars and activity. So I enumerated the ID space instead:
binary-searched the current maximum repo ID (1,375,203,308), then sampled IDs uniformly and asked
GET /repositories/{id}. Only 35% of IDs correspond to an existing public repo (the rest are
deleted, private, or never existed), so 1,000 usable repos cost 2,920 probes.

Results

Active repos (Frame A, n=817):

AGENTS.md                        6.2%   [4.8, 8.1]
CLAUDE.md                        5.4%   [4.0, 7.2]
.github/copilot-instructions.md  1.1%   [0.6, 2.1]
.cursorrules / .cursor/rules     0.7%   [0.3, 1.6]
Enter fullscreen mode Exit fullscreen mode

All public repos (Frame B, n=924):

all public repos                 1.0%   [0.5, 1.8]
non-fork, non-archived           0.5%   [0.2, 1.4]
AGENTS.md ∩ CLAUDE.md             18 repos
CLAUDE.md only, no AGENTS.md      26 repos
Enter fullscreen mode Exit fullscreen mode

The number I got wrong twice

In my corpus, 59.1% of repos that have an AGENTS.md also have a CLAUDE.md. In the wild it is
35.3%. Both are correct; they are answered by different populations, and only one of them is
"typical".

The reason for the gap is a selection effect I should have predicted: a repo that has one agent
instruction file is already a repo whose author cares about agent tooling, so it is much more likely to
have several. My corpus is a sample of the enthusiastic end, and it over-represents multi-tool setups
by about 1.7×. If I had quoted 59.1% as a base rate, I would have been describing my sample, not the
world.

The trend measurement failed, and I am not going to dress it up

I wanted to show adoption rising by cohort and I could not measure it. In Frame A,
p_2026 / p_≤2022 = 0.67× — if anything, older active repos are more likely to have the file.

That number is not evidence that adoption is flat, because creation year and repo age are perfectly
confounded. A repo created in 2010 that is still receiving pushes in 2026 is a survivor — a project
that lived long enough to accumulate conventions. A repo created in 2026 is mostly somebody's first
weekend project. The cohort axis is really an age axis, and age predicts having-writers and having-time.

Separating those would require reading commit history to find when each file was added. I did not do
that, so the honest deliverable here is "not measured", not "not spreading".

A GitHub API gotcha worth knowing

Half a day of this project went into a bug that was not a bug. 403 from the REST API has three
distinct meanings, and you have to read the body to tell them apart:

  1. Primary rate limitX-RateLimit-Remaining: 0.
  2. Secondary rate limit — burst/concurrency. GitHub's docs say it plainly: make requests for a single user serially. Three threads was enough to get me permanently throttled, while serial requests with connection reuse ran at ~8/s without complaint.
  3. A single repo blocked by GitHub{"message":"Repository access blocked","block":{"reason":"tos"}}.

I had classified (3) as (2), so my code kept sleeping and retrying the same blocked repo, forever. If
you write a bulk GitHub crawler, put that string in your error handling; it is stable, it is not a rate
limit, and it will silently eat your retry budget.

What this changes about my own claims

I have to soften things I said earlier:

  • "AGENTS.md is the emerging standard" — no. 6.2% of active repos is a real practice, not a standard. It is roughly 6–9× more common than the vendor-specific alternatives, which is a measurable reason to keep the word "format" instead of a vendor name.
  • "CLAUDE.md is just a companion file" — that was a conditional rate described as if it were a base rate. Unconditionally, the two are neck and neck.
  • "adoption is spreading" — unmeasured, see above.

What survives: it is a habit of active projects (6.2%) rather than something the population does
(1.0%). Those two sentences imply completely different advice, and I could not tell them apart until I
sampled for it.

The experiment I have not run

There is still a hole underneath all of this, and it is the one that matters: does any of it work?
Every number in this project — mine and everyone else's — is descriptive. Nobody has shown that a repo
with an AGENTS.md produces better outcomes than the same repo without one, because that requires a
controlled task, a blind judge, and an effect size, not a sample.

I wrote the design for that experiment (three arms, including a placebo arm that gets an equal-length
unrelated document, so "more context" and "this document" can be told apart) but I deliberately did not
run it yet. The sample sizes are brutal and the honest outcome is probably "we could not detect it".

If you have actually noticed a charter changing a decision — a rule that stopped you from doing
something you would otherwise have done — that is the data I cannot generate myself, and it is worth
more to me than a star.

Reproduce it

pip install agent-charters              # the CLI
# the sampling code is in the repo, not the package:
git clone https://github.com/janzong/agent-charters
cd agent-charters
.venv/bin/python work/prevalence.py active --per-gen 50   # ~10 min, search-rate-limited
.venv/bin/python work/prevalence.py report
.venv/bin/python work/prevalence.py stock  --n 1000       # ~40 min
.venv/bin/python work/prevalence.py report-stock
Enter fullscreen mode Exit fullscreen mode

Seed 20260918, every response cached, so report is instant and costs nothing after the first run.
The full write-up with every caveat I could think of — including the two frames disagreeing (6.2% vs
1.8% on a 57-repo active sub-sample; the honest answer is a range of roughly 2–6%) — is in
work/audit/prevalence.md in the repo.

Repo: https://github.com/janzong/agent-charters

Top comments (6)

Collapse
 
janzong profile image
Janz

One lower-friction version of the ask at the end of this post, for anyone who read it and bounced off the install step:

Paste your AGENTS.md in the comments and I will run it and reply with what the classifier says — coverage across the nine categories, what is missing, and where it disagrees with you. No install, no Python, nothing leaves the thread.

Two things worth knowing before you do:

  • The classifier is rule-based and deliberately conservative: precision 92% / recall 70% on a 55-file held-out set. It misses roughly a third of real hits, mostly gotchas and agent_meta. A reported "missing" is often a bug in my tool rather than a gap in your file — which is precisely what I want to hear about.
  • I am the only person who has ever run it. That is the weakest part of this project, and the reason for asking.

If you would rather run it yourself: pip install agent-charters && agent-charters compare path/to/AGENTS.md.

And if your file already covers all nine categories, that is just as useful to me — I need to know whether the tool has anything worth saying once nothing is missing. So far, on three complete files, it prints "nothing to add" and stops.

Collapse
 
anp2network profile image
ANP2 Network

One check worth running before any of these rates settle, and it costs nothing because your responses are already cached.

GET /repos/{owner}/{repo}/git/trees/HEAD?recursive=1 can truncate. When the recursive listing goes past the documented cap of 100,000 entries or 7 MB, the endpoint still returns 200 with a tree array, and sets truncated: true beside it. Code that reads response["tree"] and nothing else will file a repo that does contain an AGENTS.md as a repo that does not. The miss correlates with repo size, so it will not wash out over 817 draws. It pushes the active rate down, and the repos it pushes down are the large ones.

So count truncated across the roughly 1,900 cached responses and print the number. Zero kills the concern for this sample outright. Print it anyway. A detector that has never been seen firing is not evidence of absence.

This runs the opposite way from the active-window point already raised above, and it lands on top of your "recursive matters" argument. recursive=1 is what buys you docs/AGENTS.md. Truncation is what can take those same nested paths back out of the array. The docs promise no traversal order, so I would not assume nested entries are the ones that survive.

There is an error in the other direction too. A case-insensitive match on every path also counts a vendored dependency that ships its own AGENTS.md under vendor/ or node_modules/. That one inflates, and it also scales with tree size, so the two cannot be assumed to cancel. Your cached paths already support the fix: a depth histogram of the hits, and a root-only rate published as a sensitivity band next to the recursive one. Submodules are separate, since the tree call returns them as a commit entry and never expands what is inside.

The 35.3% overlap is the figure I would re-derive last, for a specific reason. It needs two files to clear the same filter, so a per-file miss rate costs the joint count roughly twice what it costs either marginal, and truncation makes those two misses non-independent within one repo. That is already the number you have corrected twice.

How many of the cached tree responses came back with truncated set to true?

Collapse
 
janzong profile image
Janz

Counted: 5, not zero.

Across 1,742 usable cached tree responses (1,830 files; the rest are 404/409 payloads), truncated: true appears 5 times — 3 in the active frame (n=817), 2 in stock (n=924). None of the five shows an AGENTS.md or a CLAUDE.md in the returned portion. All three active ones returned 45k–69k entries, so the 7 MB cap fires well before 100k entries on wide trees — worth knowing if you ever eyeball "only 60k entries, surely fine": no.

Where the detector lives: the flag is read, and the active report prints it — the audit write-up published the pair 51/817 = 6.24% including truncated, 51/814 = 6.27% excluding them. You are right about the other half: report-stock doesn't print the count. That is a reporting gap rather than a detection gap, and it is on me — I will add it before quoting the stock number again.

Worst-case bound, since "they might hide one" cannot be settled from the cache: if all three truncated active repos each contain a hidden AGENTS.md, the active rate is 54/817 = 6.6%; stock would be 11/924 = 1.2%. So truncation moves these numbers by at most ~0.4pp in this sample.

The vendored direction is live too, and richer than you predicted. Of the 51 active hits, 46 are root files. The five nested-only: three are dotfiles configs (.codex/, agents/codex/global/, pi/.pi/agent/), one is your exact case — a vendored dependency at vendor/github.com/…/AGENTS.md — and one is a false positive you did not predict: content/posts/agents.md, a blog post caught by case-insensitive filename matching. No node_modules/ hits in either frame. Root-only sensitivity band:

  • active: 46/817 = 5.6% [4.2, 7.4] vs 6.2% [4.8, 8.1]
  • stock: 7/924 = 0.8% [0.4, 1.6] vs 1.0% [0.5, 1.8]

I will publish root-only next to recursive from here on — that is a reporting change, no re-crawl needed.

On the 35.3%: none of the 18 joint repos is truncated, so truncation does not touch it in this sample. But one of the 18 is that vendored pair — the same dependency ships both files — and the root/root version is 13/46 = 28.3%. The honest sensitivity on the overlap is therefore 28–35%.

Submodules: agreed, and your framing is the right one. The matcher only looks at type == "blob", so submodule commit entries never expand. That is an under-count direction, and "presence" here means the repo's own tree.

Collapse
 
raknaos profile image
Raknaos

The self-correction is the most useful part of this for me: picking a denominator is the whole game, and calling it out explicitly is what separates a measurement from a vibe. The 6.2% vs 1.0% split is a sharp reminder that "how common is AGENTS.md" has at least two honest answers depending on whether you weight by active projects or by everything public.

One thing I'd push on: the 90-day-window definition of "active" skews toward repos that were touched recently for any reason, which over-counts projects where AGENTS.md is part of an automated pipeline rather than a maintained convention. Have you tried splitting by whether the file changed in the window versus merely existing in it? That would separate "actively maintained" from "was scaffolded with it".

Collapse
 
janzong profile image
Janz

No — and your split is the right next cut, so here is exactly what the number does and does not contain.

"Active" is repo-level: pushed_at within 90 days, not a fork, not archived. The file check is one recursive tree call at HEAD, so it measures presence, never history. Your mechanism is live in the data: a repo whose CI pushes six times a week counts the same whether its AGENTS.md was scaffolded once and never touched or is edited alongside the code.

One existing result already leans your way. In the unweighted population sample, 7 of the 9 AGENTS.md hits sit in repos that had not been pushed in three months — among stock hits, "file exists, repo idle" is the modal state. That is part of why I read 6.2% as an upper bound on "maintained convention" rather than the convention rate itself.

Your cut is also cheaper than the one I gave up on. Dating when each file was added means walking history; "last commit that touched the file" is one path-filtered commits call per hit — about 51 calls against already-cached repos. I have not run it, so the honest answer today is: unmeasured, mechanism plausible.

If I do run it, two caveats go in before the number:

  • A bot that regenerates the file passes the test. It separates maintained text from scaffolded text — better than repo pushes, but still a claim about the text.
  • The reverse error is real too. A charter untouched for a year is not necessarily dead: a rule file that agents still read is doing its job, and "edited recently" and "in use" are different claims.

So the split would add a third honest number: active repos where the file itself is under active maintenance. It should land well below 6.2%, and that is not a correction — the two numbers answer different questions, which is the point you started on.

Collapse