Federated Indexer (v1)
Status: Draft v1 — 2026-05-19
Extension URI: https://projectmaya.deoscomputing.io/discovery/indexer/v1
Maintainer: DEOS Computing (did:web:deos.computing)
Depends on: A2A v1.0, did:deos v1, Personhood Assertion v1
Companions: Maya 2030 on A2A
1. Motivation
When Maya acts at amazon.com she fetches amazon.com/.well-known/agent-card.json and goes. That works because the domain is known. The harder case is discovery: "find me a vendor that ships organic dog food to 02139 by Thursday with a refund policy ≥ 30 days." There is no DNS name for "vendor that ships organic dog food," and no canonical aggregator. The aggregator problem from the receipt-rooted reputation white paper §1 applies to vendor discovery as much as to reputation scores.
Yelp, Google Business Profile, niche industry directories, DNS extensions (SVCB / HTTPS records), Bluesky's AT-protocol app-view — each has a community. They share an architectural shape that does not fit Maya's threat model: one indexer holds the data, ranks results by its own (typically advertising-driven) policy, and is the only authority a relying party can verify against.
A federated indexer flips that: any A2A agent can stand up an indexer; agents and users choose which indexers to query; indexers are accountable for the receipts they produce. No single aggregator, no single ranking authority, per-relying-party choice over trust.
This spec defines:
- The skill shape an indexer advertises in its Agent Card.
- The query/response wire format (an A2A
message/sendwith a specific action_can). - The receipts an indexer mints for each query — auditable, and sybil-resistant when paired with personhood.
- How agents combine results from multiple indexers and detect collusion.
- How indexers themselves accrue receipt-rooted reputation, so "which indexers should I trust?" has a verifiable answer.
Out of scope:
- A ranking algorithm. Indexers compete on ranking quality; that is a deployment property.
- A specific data source. Indexers can be web-crawled, registry-backed, manually curated, or social-graph-derived. The wire format is independent.
- Indexer-to-indexer federation. Indexers federate by being independently queryable; a meta-indexer is just another agent that queries others.
2. Indexer Agent Card
An indexer advertises one or both of two skills in its Agent Card skills[]:
{
"skills": [
{
"id": "discovery.search",
"name": "Catalog search",
"description": "Free-text + filter query over the indexer's catalog. Returns a ranked list of agent DIDs + agent-card URLs.",
"schema": {
"input": { "$ref": "#/definitions/SearchQuery" },
"output": { "$ref": "#/definitions/SearchResults" }
}
},
{
"id": "discovery.lookup",
"name": "Agent lookup by DID",
"description": "Resolve a known agent DID to its Agent Card URL + capability summary.",
"schema": { "input": { "did": "string" }, "output": { "$ref": "#/definitions/AgentSummary" } }
}
]
}
Indexers SHOULD also declare their coverage and scoring policy as Agent Card metadata so a relying party can decide whether to consult them:
{
"indexer": {
"scope": "global" | "regional" | "niche",
"domains": ["commerce", "calendar", "comms.email"],
"ranking_policy_url": "https://example.com/indexer/policy",
"advertising_disclosed": true | false,
"personhood_required_tier": "moderate"
}
}
A discovery.search skill MUST be invokable via the discovery.search/v1 action_can.
3. Query / response
3.1 Query
A standard A2A message/send with parts[].data.action_can = "discovery.search/v1":
{
"jsonrpc": "2.0",
"id": "...",
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [{
"kind": "data",
"data": {
"action_can": "discovery.search/v1",
"query": "organic dog food shipping to 02139 by Thursday",
"filters": {
"must_ship_to": "02139",
"category": "commerce.pet-food",
"delivery_by_iso": "2026-05-22T17:00:00-04:00",
"min_refund_window_days": 30
},
"ranking_hints": ["minimize_price", "prefer_us_supplier"],
"limit": 25
}
}]
}
}
}
3.2 Response
The agent returns the standard A2A Task with status COMPLETED and a single artifact whose parts[0].kind === "data" carries the result set:
{
"id": "task-...",
"status": { "state": "COMPLETED", "timestamp": "..." },
"artifacts": [{
"artifactId": "art-...",
"name": "search-results",
"parts": [{
"kind": "data",
"data": {
"results": [
{
"did": "did:web:vendor-a.example",
"agent_card_url": "https://vendor-a.example/.well-known/agent-card.json",
"score": 0.94,
"score_breakdown": {
"lexical": 0.91,
"filter_match": 1.0,
"reputation": 0.88,
"ranking_hint_alignment": 0.92
},
"snippet": "Vendor A: organic kibble, ships free to MA, 30-day returns.",
"personhood_tier_observed": "strong",
"advertised": false
},
{
"did": "did:web:vendor-b.example",
"score": 0.87,
"snippet": "...",
"advertised": true
}
],
"indexer_total_matches": 314,
"indexer_returned": 25,
"indexer_policy_version": "ranker-v3.2"
}
}],
"extensions": {
"https://projectmaya.deoscomputing.io/a2a/ext/substrate-receipts/v1#receipt": { /* see §4 */ }
}
}],
"history": []
}
3.3 Required result fields
| Field | Meaning |
|---|---|
did |
Result agent's DID. The verifying party uses this to fetch the agent card. |
agent_card_url (optional) |
Cache hint. If absent, the verifier resolves the DID per did:deos v1 §4. |
score |
The indexer's overall score for this result against this query. Range 0.0–1.0. |
score_breakdown (optional) |
Decomposition into named subscores. SHOULD include reputation if the indexer considers receipt-rooted reputation; SHOULD include advertised boost if any. |
snippet |
Human-readable result preview. Required so the user-side agent can render results without re-fetching every card. |
personhood_tier_observed (optional) |
If the indexer has resolved the result's DID, the personhood tier it observed. Lets the relying party filter without re-resolving. |
advertised |
Boolean. Required when the indexer accepts paid placement. The receipt embeds this verbatim — an indexer that hides advertising in advertised: false results is signing a falsifiable lie. |
3.4 Streaming
Indexers MAY also support message/stream for the same skill, emitting partial result sets as delta events. Default-paginated message/send is sufficient for most use cases.
4. Indexer receipts
Every discovery.search/v1 invocation produces a substrate receipt at action_can = discovery.search/v1 whose metadata commits to the query and the results returned. This is the audit primitive that makes federated indexing accountable: a relying party can later prove what the indexer claimed, and the indexer cannot rewrite past results.
{
"principal": "did:web:indexer.example",
"subject": "did:web:indexer.example",
"action": {
"type": "search",
"action_can": "discovery.search/v1",
"inputs_hash": "<blake3 of canonical query+filters>",
"metadata": {
"query_hash": "<blake3 of query string>",
"filter_hash": "<blake3 of canonical filter object>",
"result_dids": ["did:web:vendor-a.example", "did:web:vendor-b.example", "..."],
"result_count": 25,
"result_count_total": 314,
"ranker_policy_version": "ranker-v3.2",
"advertised_count": 3,
"issued_at": "2026-05-19T15:00:00Z"
}
}
}
The receipt does NOT contain the full result set — that's too large to anchor. It contains inputs_hash (BLAKE3 over the canonical query) and result_dids + a Merkle root over the full ranked list. A relying party who keeps the full response can re-verify by re-computing the same hashes.
4.1 Result-set Merkle root
For large result sets, the indexer SHOULD include a result_merkle_root: "<hex>" field whose tree leaves are each blake3(canonical(result_i)). A relying party preserving only partial results can verify a single result against the root via the standard Merkle inclusion proof (returned out-of-band on request).
5. Multi-indexer composition
A querying agent typically consults multiple indexers and merges results. The simplest merge is reciprocal-rank fusion (RRF):
RRF_score(d) = Σ_{i in indexers} 1 / (k + rank_i(d))
with k=60 per Cormack/Clarke 2009. Indexers that didn't return d contribute zero. RRF avoids trusting any single indexer's score magnitudes and gives outsized weight to results multiple indexers agree on — collusion-resistant unless colluders coordinate on specific orderings.
The querying agent's substrate logs a discovery.consult/v1 action receipt naming the indexers it consulted, the queries it issued, and the merged top-N. The agent is accountable for its own discovery process: "why did Maya choose vendor A?" has a substrate-rooted answer.
5.1 Indexer collusion detection
If N indexers return identical-ranked results, their signed receipts expose it: result_dids arrays and ranker_policy_versions let a relying party compute pairwise rank correlation across indexers over time. High correlation among supposedly-independent indexers is a falsifiable collusion claim.
6. Indexer reputation
Indexers are agents. They accumulate receipts: every answered query is a discovery.search/v1 action receipt, and every downstream success or dispute by a relying party who used one of their results is indirect signal.
A relying party MAY run the receipt-rooted reputation library against indexer DIDs the same way it runs it against vendor DIDs. Useful derivations:
outcomeRatioagainst the indexer — of the queries this indexer answered for me, what fraction produced clean downstream outcomes? Indexers that surface high-dispute vendors carry a measurable, falsifiable bad reputation.capabilityBoundedover query domain — an indexer's reputation oncommerce.*can be tracked separately from its reputation oncomms.*.
The derivations come from the white paper unchanged; only the subject shifts from vendor DID to indexer DID.
7. Personhood at the indexer
An indexer that ranks results SHOULD inspect each result's personhood assertion (per personhood v1) and include personhood_tier_observed in the result row. This lets a relying party filter out sybil-graph contributors without re-resolving every candidate's DID.
The indexer's own personhood requirement on queries is declared in its Agent Card (indexer.personhood_required_tier). Open public indexers MAY set this to null; production indexers serving high-stakes flows SHOULD require moderate or higher to resist sybil-driven query-pattern manipulation.
8. Threat model
8.1 Pay-for-placement camouflage
An indexer accepts payment for a result but hides it in advertised: false. Defended by the substrate receipt's advertised field being signed verbatim — a partner buying placement now has a signed receipt that the indexer claimed the result wasn't advertised. Discovered later (audit, lawsuit, regulator), the indexer is exposed.
8.2 Result-set fabrication
An indexer claims a result did:web:vendor-X matched, but vendor-X doesn't actually serve the queried capability. Defended by: the relying party can fetch vendor-X's Agent Card and verify the claimed capability exists. Indexers with frequent fabrication accumulate failed-action-receipts (outcomeRatio).
8.3 Single-indexer dominance
One indexer becomes the de facto monopoly. Defenses are non-cryptographic:
- The wire format and receipt schema are open; a competing indexer is cheap to stand up.
- SDK defaults consult ≥3 indexers.
- Receipt-rooted-reputation applies to indexers; a dominant indexer in decline is observably failing.
8.4 Coordinated indexer cartel
N indexers collude to rank a specific vendor first. §5.1 makes this detectable via pairwise correlation analysis over signed receipts. Mitigation is sociopolitical (drop the cartel members from the consulted set) rather than cryptographic.
8.5 Query-history leakage
Indexers see what users ask for. Defended by:
- Querying agents MAY consult indexers via Tor / VPN if query privacy matters.
- Indexers MAY support a
discovery.search-anonymous/v1action_can with stricter privacy guarantees (no IP logging, query hash in receipt only). - High-privacy use cases require ZK queries against indexer-held data — out of scope for v1.
9. Reference implementation surface
The TS SDK ships @deos/receipt/discovery exposing:
buildSearchQuery(opts)— canonicalize a search query forinputs_hashcomputation.verifyIndexerReceipt(receipt, response)— given an indexer's receipt and its full response, verifyresult_merkle_rootincludes every claimed result.mergeByRRF(perIndexerResults, k=60)— reciprocal-rank fusion across multiple indexers.
The substrate ships no indexer code. An indexer is an A2A agent that implements discovery.search/v1; the substrate's role is to anchor the receipts.
10. Versioning
v1.0. The URI https://projectmaya.deoscomputing.io/discovery/indexer/v1 is immutable; breaking changes go to /v2.
Planned for v1.1:
- Inline ZK proof of "this result was ranked according to this declared policy" for indexers that publish reproducible policies.
- A pricing primitive for paid queries via conditional settlement.
- Privacy-preserving query: the agent proves "this query matches my UCAN scope" without revealing the query.
11. Maintainer
DEOS Computing — pull requests, indexer reference implementations, and threat-model critiques to github.com/DEOS-Computing.
License: CC BY 4.0 (text), Apache-2.0 (reference code).