What 200 public agent repositories declare, and what they do

A static scan of 200 permissively-licensed public agent repositories, at pinned commits, measured against ARS v1.0. No repository is named.

corpus 0aa06e40b633 static analysis only

What this is

Two hundred public agent repositories, selected by a rule written before the results were known, fetched at pinned commits, and read by the same instrument this site publishes. Nothing was executed. No repository is named.

The short version. Of 4,445 tools observed across the corpus, 103 — 2.3% — state anything at all about whether they change state. Of the 100 whose claim could be checked against their own implementation, 0 were found contradicting it. The gap is not that declarations are wrong. It is that there are almost none.

Read the headline carefully, because a zero is the easiest number on a page to misread. It does not say that agent tools are safe. It says that in a corpus of 4,445 tools, only 103 made a statement specific enough to be checked at all — and that the ones that did were, by these deliberately conservative rules, keeping to it. The finding is the size of that denominator.

That is the measurable version of the problem this standard exists for. An assurance argument compares what a system is permitted to do with what it does, and across this corpus the first half of that comparison is mostly absent.

The headline figure

Of the tools whose OWN self-description asserts that they only read — a declared read side-effect, or a description shipped to the model saying it changes nothing — how many have an implementation that changes state?

headline

0.0% 0 of 100 tools

0observed present
100observed absent
3not observable

The denominator excludes the 3 not-observable cases. The tool asserts read-only and its body could not be read: the implementation is in a language this does not parse, the function could not be bounded without risking a neighbour's code, or the source line was not resolvable

A zero here is a measurement, not a reassurance. It says that of the 100 tools that made a checkable claim, none was found breaking it under these rules — rules deliberately chosen to under-report: POST is not counted as a write, calls into helpers are not followed, and writes to caches, temp paths and logs are discounted. It is not a statement about the 3 that could not be checked, and it is not a statement about anything that made no claim at all.

provenance primary-contradiction-by-tool in aggregate.json — 0 + 100 + 3 references, one per member of each bucket, as salted identifiers

The corpus

200repositories
199assessed
0fetch failed
1scan failed
4,445tools observed

How it was built. Fifteen GitHub topic queries — the frame is committed beside the data and was fixed before anything was measured. Every entry cleared 80 stars, is not a fork, is not archived, and carries a permissive SPDX licence; anything with no licence, an unresolvable one, or a copyleft one was excluded and recorded. 1,017 repositories were discovered, 435 excluded by a published rule, 582 eligible, and 200 selected by walking the fifteen strata in turn rather than taking the top 200 by stars — which would have described whichever two topics have the largest repositories.

Every entry is pinned to a commit SHA, never a branch. A corpus that moves under its own findings cannot be re-checked by anybody who disputes them.

Nothing was executed and nothing was re-hosted. Clones were shallow, at the pinned commit, with hooks and both gitconfigs pointed at an empty directory, symlinks materialised as inert files, and .git removed after checkout. This work references public repositories and extracts facts about them; it does not redistribute anyone's source.

What the sample is biased toward, stated rather than discovered later. Popularity — every entry cleared a star floor, so this describes agent code people found. Topic labels — a repository that applies none of the fifteen topics is invisible to the frame. And a clear licence, which correlates with organisational maturity. All three bias the sample toward more careful code, which is the conservative direction for a finding of this shape.

Where "undeclared" comes from, when nobody wrote a declaration

Not one repository in this corpus has ever written an Agent Contract. So the finding cannot come from comparing code against an owner's declaration — there is none. It comes from the artifact contradicting itself.

Primary, and the only source the headline uses. A tool's own self-description against its own body. Either a machine-readable classification in the declaration — an MCP readOnlyHint, a read_only keyword, a declared read side-effect — or the description string that gets shipped to the model, saying in words that the tool changes nothing. A tool description is not documentation in the usual sense: it is the operative text the agent plans against, at the moment of the decision.

Excluded from the headline: the tool's name. fetch_ in HTTP code frequently means "make a request", and a get_ on a search client can legitimately POST. That reading is computed and published below, separately, so a reader can see what the looser interpretation would have produced — it is not folded in.

Also excluded: the repository's README. A README describes a project; a contradiction has to be about a tool.

⚠️ POST is not a write, and that decision is why this number is small

The tempting implementation counts any outbound HTTP call from a read-named tool as a transmission. It is wrong. Every vector search, GraphQL query, embedding lookup and model inference call is an HTTP POST that changes nothing. A detector that counted them would fire on nearly every retrieval tool in the corpus, and the first reader to open an example would find a requests.post to a search endpoint and stop reading.

So POST and PUT are excluded entirely. What counts is behaviour that changes state under any reading: local file writes and deletes, subprocess execution, runtime code evaluation, explicit HTTP DELETE, mail and message dispatch, state-changing SQL, and payment. Losing volume to keep the claim true is the trade this page makes.

Two further choices, both of which shrink the number: a write whose target names a cache, a temp path or a log is not counted; and the detector reads each tool's own body and follows no calls, so a tool that delegates to a helper that deletes is not reported. The figure understates.

Every rule is a published regular expression. Nothing was judged by a model, so a maintainer who disputes a result can run the same rules over their own code and get the same answer.

The distribution

Every figure carries three buckets: what was observed present, what was observed absent, and what could not be observed at all. The third is printed at the same size as the other two, because a percentage whose unreadable share is hidden is a statement about the instrument.

How many repositories contain at least one tool that asserts read-only and does not behave that way?

0.0% 0 of 11 repositories

0observed present
11observed absent
189not observable

The denominator excludes the 189 not-observable cases. No tool in the repository made a tier A or tier B read-only assertion that could be checked — either no tool was observed at all, none of the observed tools said anything about reading, or the bodies of those that did could not be read. Plus: nothing was read for these repositories: 1 scan_failed.

A zero here is a measurement, not a reassurance. It says that of the 11 repositories that made a checkable claim, none was found breaking it under these rules — rules deliberately chosen to under-report: POST is not counted as a write, calls into helpers are not followed, and writes to caches, temp paths and logs are discounted. It is not a statement about the 189 that could not be checked, and it is not a statement about anything that made no claim at all.

provenance primary-contradiction-by-repository in aggregate.json — 0 + 11 + 189 references, one per member of each bucket, as salted identifiers

Of every tool observed across the corpus, how many make any statement at all about whether they change state — either a machine-readable annotation in the declaration, or a sentence in the description shipped to the model?

2.3% 103 of 4445 tools

103observed present
4342observed absent
0not observable

The denominator excludes the 0 not-observable cases. None: whether a tool says anything about its own side effects is readable from the tool as observed, and does not depend on reading its body. Tools in repositories the adapters could not read at all are absent from this population entirely, which the coverage figures below account for.

⚠️ A tool that states nothing is not thereby doing anything wrong. This measures the availability of a declaration to check an implementation against — not the quality of any implementation.

provenance tools-that-state-their-own-side-effects in aggregate.json — 103 + 4342 + 0 references, one per member of each bucket, as salted identifiers

Of the tools whose NAME begins with a read verb and whose description says nothing either way, how many have an implementation that changes state?

0.4% 2 of 516 tools

2observed present
514observed absent
41not observable

The denominator excludes the 41 not-observable cases. The tool name implies a read and its body could not be read

⚠️ A name is not a declaration. `fetch_` in HTTP code frequently means "make a request", and a `get_` on a search client can legitimately POST. This is reported because excluding it entirely would hide what the looser reading would have produced, and it is EXCLUDED FROM THE HEADLINE for the same reason it is caveated here.

provenance name-implies-read-contradiction-by-tool in aggregate.json — 2 + 514 + 41 references, one per member of each bucket, as salted identifiers

How many repositories register at least one tool that writes, sends, spends or deletes?

19.3% 16 of 83 repositories

16observed present
67observed absent
117not observable

The denominator excludes the 117 not-observable cases. No adapter observed any tool in the repository, so nothing can be said about what its tools do. Plus: nothing was read for these repositories: 1 scan_failed.

provenance posture.registers_a_send_capable_tool in aggregate.json — 16 + 67 + 117 references, one per member of each bucket, as salted identifiers

How many repositories configure any timeout, deadline or abort signal at all?

89.8% 176 of 196 repositories

176observed present
20observed absent
4not observable

The denominator excludes the 4 not-observable cases. The reading of the repository was incomplete, or no model identifier was recognised at all. Plus: nothing was read for these repositories: 1 scan_failed.

⚠️ Inferred from source and configuration text rather than from a declaration. A timeout set by a framework default, or a model pinned in a deployment manifest this scan never saw, would read as absent here.

provenance posture.sets_any_timeout in aggregate.json — 176 + 20 + 4 references, one per member of each bucket, as salted identifiers

How many repositories pin a dated or fully-qualified model version, rather than a floating alias?

48.6% 69 of 142 repositories

69observed present
73observed absent
58not observable

The denominator excludes the 58 not-observable cases. The reading of the repository was incomplete, or no model identifier was recognised at all. Plus: nothing was read for these repositories: 1 scan_failed.

⚠️ Inferred from source and configuration text rather than from a declaration. A timeout set by a framework default, or a model pinned in a deployment manifest this scan never saw, would read as absent here.

provenance posture.pins_a_model_version in aggregate.json — 69 + 73 + 58 references, one per member of each bucket, as salted identifiers

How many repositories interpolate a variable into a string used as a prompt or system message?

86.2% 169 of 196 repositories

169observed present
27observed absent
4not observable

The denominator excludes the 4 not-observable cases. The reading of the repository was incomplete, or no model identifier was recognised at all. Plus: nothing was read for these repositories: 1 scan_failed.

⚠️ Inferred from source and configuration text rather than from a declaration. A timeout set by a framework default, or a model pinned in a deployment manifest this scan never saw, would read as absent here.

provenance posture.concatenates_untrusted_content_into_instructions in aggregate.json — 169 + 27 + 4 references, one per member of each bucket, as salted identifiers

In how many repositories did any of the three adapters observe at least one tool?

41.7% 83 of 199 repositories

83observed present
116observed absent
1not observable

The denominator excludes the 1 not-observable case. Nothing was read for these repositories: 1 scan_failed

⚠️ An observed-absent here is a statement about the adapters, not about the repository. The three adapters read a documented pattern set; a repository defining its tools some other way is indistinguishable, to them, from a repository with no tools. This figure is published precisely so the headline can be read against it.

provenance coverage.any-tool-observed in aggregate.json — 83 + 116 + 1 references, one per member of each bucket, as salted identifiers

How many repositories are in a language one of the three adapters reads at all?

79.5% 159 of 200 repositories

159observed present
41observed absent
0not observable

The denominator excludes the 0 not-observable cases. None: the primary language is metadata from the corpus frame and is known for every entry, whether or not the repository was ever fetched or scanned

provenance coverage.stack-is-readable in aggregate.json — 159 + 41 + 0 references, one per member of each bucket, as salted identifiers

What the contradiction looks like

No instance of the primary contradiction survived the detector's rules on this corpus, so there is no pattern to show. That is the measurement, not an omission.

Disclosure posture

No repository is named anywhere on this page, and none will be before its maintainer has been contacted. Aggregate and anonymised statistics are published freely; an individual repository is not named as flawed until the person who maintains it has had the finding and a reasonable period to respond.

On this corpus the private disclosure ledger is empty. The primary contradiction figure is zero, so there is nobody to notify. The machinery is here anyway, tested and in the way, because the corpus with a non-zero figure is the one it has to be right about — and building it after finding something would be building it under pressure.

The provenance behind every figure above is a list of salted identifiers. They are complete — you can count them, and check that the buckets partition the population — and they do not resolve to a repository without a salt that is not published. Once any maintainer named in the private ledger has been notified, that salt can be released and every number here becomes attributable.

Notification is a human action. Nothing in the toolchain that produced this page can send a message, open an issue, or file an advisory, and none of it has network access.

What this does not establish

What the instrument could not see

This is a measurement of public code by one party, using one instrument, on one day. It is not a certification of anything, it confers no status on any repository named or unnamed, and it is not an audit. The standard it was measured against is itself a draft that no second party has applied.