← All reference docs

Discovery: finding proven code by what it does, not what it's called

Status: first rung shipped (2026-07) — oath find; the rest is roadmap. This is the layer that turns the content-addressed store from "a place your code lives" into "a commons you can draw from" — you find an existing proven definition by the property it satisfies, with no name trusted.

Why this exists

Every other discovery path is name-keyed: ls, get, dependents, context all take a name. But names are the one mutable, non-authoritative, collision- prone layer (docs/teamstore.md, and the naming discussion generally). So "reuse the proven validate" secretly means "reuse whatever my store's label validate points at" — which is exactly the trust we removed everywhere else. Discovery needs to key on meaning, not on a label.

The key realization: properties are content-addressed too

A property is stored as (binders, body), where the function under proof is self (not a ref) and the binders are de Bruijn indices. So a pure algebraic law carries no names and no specific hashes — commutativity is literally (== (self a b) (self b a)) — and its canonical encoding has one hash wherever it appears, on any definition, of the matching operand types.

That means "which proven definitions satisfy this spec?" is a hash lookup, not a search. propHash (canon.go) is the content address of a property; oath find indexes it. Content-addressing, which already gave code a name-independent identity, gives specs one too.

The three-rung ladder

Discovery by meaning has three strengths of "the same," each looser than the last and each further from name-dependence:

  1. Syntactic — identical AST → same code hash. (Already how identity works.)
  2. Spec-equivalent — different implementations that satisfy the same property. This is the rung oath find implements, and it was already latent in the proofs we store — it just had no query surface. If two defs share a law and both prove it, they are interchangeable for that law.
  3. Rewriting-equivalent (e-graph) — different bodies that are provably equal under a rule set, collapsed to one canonical form. The deepest dedup and the biggest build; shipped, up to and including the saturating engine (e-classes, congruence closure, bidirectional distributivity over Int/Rat, cost-based extraction — docs/egraph.md). Its hard constraint is below.

oath find (rung 2, today)

Two front doors, both matching on the generalized property content hash (propHashGeneral — matched up to operand types):

Query by example — point at a def whose property you want:

$ oath find rat-add
  · commutes [proven here]  #f230af55f94f
      rat-mul   (proven as "commutes")  ← proven on both: interchangeable for this law
  · assoc [proven here]  #59b248e21d01
      (no definition in the store satisfies this)

Query by fresh spec — write the property you want; the sought function is self. A spec query needs no body — write just the signature and the property:

$ cat spec.oath
(defn wanted [] [(a Int) (b Int)] Int
  (prop commutative [(a Int) (b Int)] (== (wanted a b) (wanted b a))))
$ oath find --spec spec.oath
spec query "wanted" — which proven definitions satisfy it (by content hash, no name, no example):
  · commutative [tested here]  #f230af55f94f
      rat-add   (proven as "commutes")  ← a proven implementation of this spec
      rat-mul   (proven as "commutes")  ← a proven implementation of this spec

Both find rat-add/rat-mul with no name trusted and (here) across types — an Int spec matched the Rat implementations. Both are exposed over MCP (find and find_spec), since agents are the intended consumers: generate a spec, ask the commons who already proved it, reuse instead of rebuild.

The invariant that protects the substrate

The discovery layer never touches identity. Code hashes and prop hashes are syntactic and stay that way; find (and, later, the e-graph) only draw edges between existing hashes — "these satisfy the same law," "these bodies rewrite-equal." They never merge two objects into one identity or redefine a hash. Identity is the O1 encoding (SPEC §1) and remains so. Semantics is a view over the hash graph, never a change to it. This is what keeps the e-graph from destabilizing the foundation when it lands.

Honest limits (and the roadmap they imply)

oath find matches a property up to its operand types (propHashGeneral generalizes the primitive leaf types in the binders to positional type variables, so commutativity over Int and over Rat both become [t0, t0] and match). What remains:

The rungs compose: content-hash match (up to type) for the fast common case, proof-implication for the semantic cases it misses, and the e-graph for body-equivalence — every step loosening name-dependence further, and every one of them drawing edges over the hash graph, none touching identity.

When a query returns nothing: check the SHAPE before concluding absence

An empty result and an empty corpus are the same output. This bites on the two modes that take a LAW, and it reaches them differently:

Either way structure does not bridge, so a wrong shape and a missing artifact look identical from outside — and the shape is the half you control. The name-keyed modes (find <name> and --equiv) are not affected: they ask for a name rather than a shape.

And primitive leaves generalize LESS than the headline suggests — for --spec. --implies re-types the whole property (binders and body-embedded types) to each candidate, which is what lets an Int law reach its Rat counterpart however the law was written. --spec generalizes only the binders and hashes the property BODY unchanged, so on that surface a primitive written into the body — in a type application, a constructor, an annotation — does not generalize at all: laws that are otherwise identical but say (Nil [Int]) and (Nil [Rat]) miss each other. Keep concrete types in the binders where you can, or reach for --implies, which pays a proof per candidate and does not care.

Start with a SIGNATURE PROBE, not with a law

This is the cheapest move available and it is worth making first.

A query's law only matches if it hash-equals a definition's own. But when it doesn't match, --spec falls through to listing every definition whose SIGNATURE is compatible — so a law you know will never match is a way of asking "what does this corpus have at this shape?"

(defn wanted [] [(s Str)] Str s
  (prop refl [(x Str)] (== (wanted x) (wanted x))))
· refl  — no definition states this law as written
  4 definition(s) have a COMPATIBLE SIGNATURE:
    config-key         PROVEN ...
    gh-group           tested ...
    record-field       tested ...
    shout              PROVEN ...

Nothing about that query says what you want. It lists the corpus's definitions at (-> Str Str) anyway. Probe a few shapes, read the names with oath get, and only then write the real law — you will be writing it against a definition you have seen rather than one you are guessing at.

It is a map, not a census, in two ways. The list is built only from definitions that carry properties of their own, so one with no stated laws is silently absent — an empty list is not proof that the shape is empty, and --implies can still prove your query against such a body. And the list is capped at eight names; past that it tells you how many more share the signature, so widen your probe rather than reading eight as all of them.

One caveat on the probe, and it is not fatal. The reflexive law is only a probe because nothing in the corpus states it. If some definition ever did, --spec would report that content-hash match and print no neighbour list — you would get a hit instead of a map. Nothing in this corpus states one today, and if it happens the failure is legible rather than silent: you see a match you did not expect.

Two more moves in the same spirit, both cheaper than guessing a law:

The three axes, for when you have a shape and it still finds nothing

These describe real gaps that nothing bridges. They are not, on the evidence below, what finds artifacts — probing and reading the corpus is. Reach for them when a probe has told you the shape exists and your law still misses.

axisthe guess that finds nothingthe shape the corpus used
returnthe operation returns the whole collection — (List Str)it returns ONE element — Str
abstractionit takes a VALUE — (x Str)it takes a TEST — (p (-> a Bool))
polymorphisma monomorphic definition[a] on the definition — the corpus's combinators are forall a

Return. "Report a required key the host did not supply" was written to return (List Str), the missing keys. config-missing returns Str, the first one. Both laws missed, with no signature-compatible fallback — indistinguishable from an empty corpus. Changing only the return type, keeping both laws otherwise identical in meaning, proved config-missing twice.

Abstraction. "Does this list of Str contain an element" was written as (-> Str (List Str) Bool). The corpus answers it with any, which is (-> (-> a Bool) (List a) Bool) — the fixed value is generalized to a predicate. The mismatch is an extra parameter, not a leaf type, so nothing bridges it. Restated in the higher-order shape, --implies proves any satisfies the law and REFUTES all with a countermodel.

Polymorphism. "Take the longest prefix whose elements pass a test" written monomorphically finds nothing, even when the law is copied verbatim from take-while's own. Declaring the query definition [a] proves take-while AND filter, and refutes drop-while.

It is the DEFINITION's polymorphism that matters, not type application in the law. Measured as a 2x2 on one query:

query definitionthe law's recursive callresult
monomorphic(wanted p xs)nothing
[a](wanted [Int] p xs)filter, take-while proved
[a](wanted p xs)filter, take-while proved

The application is inferred, so writing it changes nothing. Only the first row is a different query.

Writing a polymorphic query

A definition's type variables are not in scope in its property binders — declare the definition polymorphic and state the law at a concrete type, which is how the corpus's own polymorphic definitions state theirs:

(defn wanted [a] [(p (-> a Bool)) (xs (List a))] (List a) (Nil [a])
  (prop every-kept-element-passes [(p (-> Int Bool)) (xs (List Int))]
    (all [Int] p (wanted p xs))))

The procedure

  1. State the law in your own words. Do not try to guess how the target states it: --implies proves rather than matches, so a differently-worded law is fine, and --spec reports a content-hash match that a paraphrase will miss.

  2. Run --spec first. It is fast, and its fallback list of signature-compatible definitions is the cheapest signal you have: if it names candidates, your signature is close and the law is worded differently. get <name> then shows you how they state it. (The fallback list is signature-derived; a --spec MATCH is not, so read the two differently.)

  3. If it names nothing, try --implies before changing anything. An empty fallback list does not mean no signature matched: the neighbour list is built only from definitions that carry properties of their own, so a compatible definition with no stated laws is silently absent from it — and --implies can still prove your query against that body.

  4. If that also finds nothing, vary the SHAPE. The three axes above are where to start; they are the ones measured, not a complete list, and exhausting them does not establish that the corpus has nothing.

  5. On any shape that surfaced candidates, run --implies. It proves, so it finds definitions whose own laws look nothing like yours.

  6. NO VERDICT is not absence. It reports a limit of the prover, not a fact about the definition — a body outside the provable fragment (lam terms, trusted crypto primitives) yields no goal to solve. Roughly 6% of this corpus's candidates are unreachable this way, measured in docs/experiments/issue-177-fragment.md, and they are not peripheral.

  7. When you see one, re-run with --details. It NAMES each unsettled candidate and says why:

    1 NO VERDICT — the prover did not settle it (a limit of this prover, ...)
        record-field       "lam" terms are outside the provable fragment
    

    That is a name you can get and read. In the falsifier's terms the artifact is SURFACED rather than SATISFIED — nothing was proved — but a name and a reason is most of what you came for, and it costs one flag.

  8. Only if that leaves you with nothing, --equiv is the remaining route — with two things worth naming. It takes an IMPLEMENTATION where --spec and --implies take a LAW, so if you can already write the implementation you have solved much of what you were searching for; it is a fallback, not an equal fourth option. And it is not known to reach every fragment-blind candidate: it matches implementations sharing an eHash under a limited rewrite system — wider since distributivity landed, but still a rule set rather than a decision procedure — and issue-177-fragment.md tested 2 of the 12 and says so. Reaching those two is evidence that the blindness is not structural, not a guarantee of coverage.

What this section is measured against, including where it was wrong

The three axes came from re-phrasing seven intents drawn from an application's friction log: one author's first pass found 2 of 7, and re-shaped queries found 5 of 7.

That was then tested against readers, and the axes did not survive as the explanation. Four subjects who had never seen the corpus were given the seven intents; two got this section's guidance, two got only the mode list and the query syntax. Both groups averaged 6 of 7, and the group WITHOUT the guidance used fewer tool calls and fewer tokens to get there.

So the honest claims are narrower than the first version of this page made:

The axes below remain true — they describe gaps nothing bridges — but they are a diagnostic for a query that will not land, not a search strategy.

What this does not fix

Two of the seven stayed unfound at every shape, both for the same reason: the target's body is outside the provable fragment, so --implies returns no verdict however the query is written. The obstacle is the CANDIDATE's body, not your signature, which is why varying the shape does not help.

That is a statement about --implies, not about every mode — and --spec reaches these more often than you would expect. It matches property content hashes and never invokes the prover, so it is unaffected by the fragment. The catch is that your law has to hash-equal the target's own.

That happens more than "coincidence" suggests. Two readers who had never seen this corpus were given the intents behind both fragment-blind definitions and reached both on --spec, first try, writing the law from the intent sentence alone. A property's NAME is not part of its hash, so one of them naming its law never-contains-the-separator where the target names its own never-contains-a-tab is evidence they wrote the SAME law independently rather than copying it. For a sharply worded intent the obvious law is often the law the author wrote.

So: always try --spec, especially after a NO VERDICT. It is fast, and a hit is a real find — a definition that literally states your law.


Rendered verbatim from docs/discovery.md in the repository. The markdown is the single source; this page is a copy checked for drift in CI, so what you read here is what an implementer reads.