> ## Documentation Index
> Fetch the complete documentation index at: https://retrievalcontextprotocol.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Federation & Selector

> Target one backend or fuse many — both layer on the core retrieve handshake with zero new server obligations, driven from one rcp.json registry.

RCP is point-to-point: one server answers for its own index. Querying **all** the
engines you can reach is a **client** concern, layered on the core methods with
**zero new server obligations**. There are two routing models, both drivable from
a single `rcp.json` registry.

| Model          | Use it when                                                                                                                                                       | API                                             |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
| **Selector**   | you have several backends and each query should hit **exactly one** — pick it by id, by required capability, or by priority with liveness fallback (lazy connect) | `Selector.load("rcp.json").select_capable(cap)` |
| **Federation** | you want to query **all** reachable engines concurrently and fuse the ranked lists with Reciprocal Rank Fusion (origin-tagged hits)                               | `Federation::from_registry_file("rcp.json")`    |

## The registry — `rcp.json`

```json theme={null}
{
  "engines": [
    { "id": "docs", "transport": "stdio", "command": ["python3", "docs_server.py"], "priority": 10 },
    { "id": "web",  "transport": "http",  "url": "http://127.0.0.1:8000/rcp", "weight": 1.5 },
    { "id": "code", "transport": "stdio", "command": ["rcp-code-engine"], "required": false }
  ]
}
```

<ParamField path="id" type="string" required>
  A client-local label used to tag fused hits by origin (`meta.engine`).
</ParamField>

<ParamField path="transport" type="&#x22;stdio&#x22; | &#x22;http&#x22;" required>
  With `command` (stdio) or `url` (http).
</ParamField>

<ParamField path="priority" type="integer" default="0">
  Higher wins for Selector's primary → secondary fallback.
</ParamField>

<ParamField path="weight" type="float" default="1.0">
  Scales the engine's contribution during fusion.
</ParamField>

<ParamField path="required" type="boolean" default="false">
  If `true`, a connect/query failure fails the whole federated query instead of
  being skipped.
</ParamField>

Discovery composes with the [`catalog`](/methods/catalog) capability: a registry
entry can point at an aggregator that enumerates further engines via
`catalog/list`.

## Selector — pick one of many

When you have several backends but each query should hit **exactly one**, a
`Selector` chooses it for you from the `rcp.json` registry. It connects
**lazily** — listing ten candidates opens **zero** connections until you call a
`select_*` method, and then only to the winner. There are three selection
policies:

| Method                | Picks                                                      | On failure                                                                            |
| --------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `select(id)`          | the one engine with that **id**                            | returns an error naming the unknown id                                                |
| `select_primary()`    | the **highest-priority** engine that answers               | walks down the priority order to the next live engine                                 |
| `select_capable(cap)` | the highest-priority live engine that **advertises `cap`** | skips engines that answer but lack the capability, and dead ones, until one qualifies |

Both `select_primary` and `select_capable` give you **priority + liveness
fallback for free**: a multi-backend deployment stays up when a backend dies,
without the client writing any failover glue. `priority` in the registry (higher
wins) sets the order they're tried.

<CodeGroup>
  ```python Python theme={null}
  sel = rcp.Selector.load("rcp.json")

  c = sel.select("docs")                            # a specific engine by id
  c = sel.select_primary()                          # highest-priority live engine
  c = sel.select_capable(rcp.Capability.Retrieve)   # highest-priority live engine that can retrieve

  for h in c.retrieve("query", k=5):
      print(h["id"], h["score"])
  ```

  ```js Node.js theme={null}
  const sel = rcp.Selector.load("rcp.json");

  const c1 = await sel.select("docs");                            // by id
  const c2 = await sel.selectPrimary();                          // highest-priority live
  const c3 = await sel.selectCapable(rcp.Capability.Retrieve);   // highest-priority live + can retrieve

  for (const h of await c3.retrieve("query", 5)) console.log(h.id, h.score);
  ```

  ```rust Rust theme={null}
  let sel = rcp::Selector::load("rcp.json")?;

  let c = sel.select("docs")?;                          // by id
  let c = sel.select_primary()?;                        // highest-priority live
  let c = sel.select_capable(rcp::Capability::Retrieve)?; // highest-priority live + can retrieve
  ```

  ```cpp C++ theme={null}
  auto sel = rcp::Selector::from_registry_file("rcp.json").value();

  auto c = sel.select("docs");                             // by id
  auto c = sel.select_primary();                           // highest-priority live
  auto c = sel.select_capable(rcp::Capability::Retrieve);  // highest-priority live + can retrieve
  // each returns Result<Client>: .value() is the connected client, .error() the failure
  ```
</CodeGroup>

<Tip>
  Use `select_capable` as the default in a mixed fleet: it is the "any live backend
  that can do X, best one first" query, so a client never has to hard-code which
  engine owns which capability.
</Tip>

## Federation — fuse many

A federated `retrieve(query, k)` fans out `retrieve` to every engine that
advertises it, concurrently. Engines lacking the capability are skipped (not an
error); a slow non-`required` engine is dropped after a client-side deadline so
one bad backend can't stall the query.

### Reciprocal Rank Fusion

The **default and recommended** fusion is RRF (Cormack et al. 2009), which needs
only **ranks** — not comparable scores — so it is robust across heterogeneous
engines:

```
RRF(d) = Σ_engine  weight_engine / (rrfK + rank_engine(d))          rrfK default 60
```

where `rank_engine(d)` is the 1-based position of document `d` in that engine's
list (engines that didn't return `d` contribute nothing). The fused list is
sorted by descending `RRF(d)` and truncated to `k`.

<Warning>
  **Never fuse raw scores directly** — scores from different scorers are not
  comparable. If you must use weighted score fusion, min-max normalise per engine
  to `[0,1]` first. RRF is preferred by default precisely because it sidesteps this
  footgun.
</Warning>

### Determinism & dedup

* **Tie-breaking** is deterministic: order by (1) higher fused score, then (2)
  higher summed weight, then (3) lexicographically smaller stringified `id` — a
  total order independent of engine response arrival.
* **Deduplication** keys on the stringified `Hit.id`. When two hits collapse, the
  fused hit **SHOULD** keep the richest body and merge `meta`. Each fused hit
  carries its origin in `meta.engine`.

### The fusion primitive — `rrf_fuse`

You don't need the full `Federation` facade to fuse. Every reference SDK exports
the fusion algorithm as a **standalone, tested function** over ranked lists you
already hold — a cache, a replay, two hand-issued `retrieve` calls — so no
adopter re-derives §16.3 (and re-derives it subtly wrong). All four are
byte-for-byte identical: same tie-break, same richest-body dedup, same origin
tagging in `meta.engine` and `meta.fusedScore`.

<CodeGroup>
  ```python Python theme={null}
  fused = rcp.rrf_fuse(
      {"dense": dense_hits, "sparse": sparse_hits},   # engine id -> ranked list
      k=10,
      weights={"dense": 1.0, "sparse": 0.7},          # optional per-engine weights
  )
  # rcp.weighted_fuse(...) instead when engine scores ARE comparable
  # (it min-max normalises per engine first).
  for h in fused:
      print(h["id"], h["meta"]["engine"], h["meta"]["fusedScore"])
  ```

  ```js Node.js theme={null}
  const fused = rcp.rrfFuse(
    { dense: denseHits, sparse: sparseHits },
    { k: 10, weights: { dense: 1.0, sparse: 0.7 } },
  );
  // rcp.weightedFuse(lists, { k, weights }) for comparable scores.
  ```

  ```rust Rust theme={null}
  use rcp::fusion;
  let fused = fusion::rrf_fuse(
      &[("dense", &dense_hits), ("sparse", &sparse_hits)],
      Some(10),                    // k
      fusion::RRF_K_DEFAULT,       // 60
      Some(&weights),              // Option<&HashMap<String, f64>>
  );
  // fusion::weighted_fuse(&engines, k, weights) for comparable scores.
  ```

  ```cpp C++ theme={null}
  using rcp::fusion::EngineList;
  std::vector<EngineList> engines = {
      {"dense", dense_hits, 1.0}, {"sparse", sparse_hits, 0.7}};
  auto fused = rcp::fusion::rrf_fuse(engines, /*k=*/10);
  // rcp::fusion::weighted_fuse(engines, k) for comparable scores.
  // The live Federation delegates to this same function.
  ```
</CodeGroup>

The default `rrfK` is **60** (`rcp.RRF_K_DEFAULT` / `fusion::RRF_K_DEFAULT`). The
live C++ `Federation` calls the very same `rcp::fusion::rrf_fuse`, so fan-out
fusion and held-list fusion agree exactly.

### One command: fan out and fuse

[`examples/example_federation.py`](https://github.com/1ay1/rcp/blob/main/examples/example_federation.py)
is spec §16 made runnable in a single process — it spawns two genuinely
independent engine subprocesses (each its own corpus), fans one query out to
both, and fuses their rankings with `rcp.rrf_fuse`:

```sh theme={null}
python3 examples/example_federation.py
# [papers] returned 2: ['p-rrf', 'p-colbert']
# [web   ] returned 1: ['w-rrf']
#
# fused top-3 (RRF, weights={'papers': 1.0, 'web': 0.7}):
#   1. p-rrf      engine=papers  fusedScore=0.01639
#   2. p-colbert  engine=papers  fusedScore=0.01613
#   3. w-rrf      engine=web     fusedScore=0.01148
```

No servers to start, no ports, no config — the whole federation lives and dies
with the process.

## Federated capabilities

A client's *effective* capability set over a federation is the **union** of its
engines' capabilities (it can `embed` if any engine can); its *guaranteed* set is
the **intersection**. A federated `retrieve` runs against the subset advertising
`retrieve`; a federated `graph` op against those advertising it. Reference SDKs
expose both a per-engine handle and a `Federation` facade.
