> For the complete documentation index, see [llms.txt](https://docs.fast.poker/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.fast.poker/for-operators-dealer-service/fleet-setup.md).

# Fleet and sharding setup

A fleet splits the crank across multiple instances by role, so the latency-sensitive deal loop stops competing with serial work like matchmaking and settlement.

A single box runs everything in one process (role `all`) and needs no fleet config. Reach for a fleet only when one instance can't keep up.

## Topology

| Role        | Count      | Commits to TEE? |
| ----------- | ---------- | --------------- |
| `discovery` | 1          | No              |
| `servicer`  | 1-16       | Yes             |
| `settler`   | 1          | Yes             |
| `sweeper`   | 1          | No              |
| `all`       | single box | Yes             |

`servicers` is the only scaled count (1-16). `all` is the single-box parity mode: every loop in one process, no fleet config.

## Roles and loops

| Role        | Does                                                                                                |
| ----------- | --------------------------------------------------------------------------------------------------- |
| `discovery` | Finds tables and publishes the shard map. No dealing.                                               |
| `servicer`  | The per-hand deal loop: `start_game` / `tee_deal` / `tee_reveal` / `settle`. Horizontally scalable. |
| `settler`   | L1 settlement and closeout: rake, prize sweep, table cleanup.                                       |
| `sweeper`   | Rake removal, kick, and auction sweeps.                                                             |

A `servicer` or `settler` commits to the TEE (ScheduleCommit) and therefore needs an escrow (teePayer). `discovery` and `sweeper` do not commit and need no escrow.

## When to scale

One box (role `all`) handles small-to-moderate load. Scale to a fleet when:

* A single instance can no longer keep up with the deal loop across many concurrent tables.
* You want to isolate failure domains so a stall in one loop doesn't block the others.

## Per-instance teePayer

**Hard rule: every TEE-committing instance needs its OWN funded `tee-payer-keypair.json`.** Stage it at `runtime/<instance>/`.

Sharing one teePayer across instances breaks ScheduleCommit fee accounting: the debits collide. The dashboard Fleet page flags any shared teePayer with a red `shared!` warning and blocks enabling the fleet until every committing instance is staged, isolated, and funded.

This applies to every `servicer` and the `settler`. `discovery` and `sweeper` need no teePayer.

## Shard map and stand-down

Instances coordinate table ownership through a `shard-map.json` carrying a `fleet_active` flag. `discovery` publishes each servicer's slice; a servicer scopes itself to its slice and stands down on anything outside it, and a legacy single box stands down entirely the moment a fleet's map goes live.

This is best-effort coordination, not a distributed lock. A brief overlap is possible during handoff: at startup before the first map is applied, or when the pool creates or reuses a table before the next map is published. That overlap is **harmless** because the on-chain program is the source of truth and idempotent under concurrent operators, so the loser transaction reverts with a benign code the crank swallows. Coordination exists to avoid wasted fees, not for safety.

### Use strict mode in production

`fleet_mode` decides what a servicer does when no fresh shard map is present:

| Mode             | No live map                           | Use                                                                                                    |
| ---------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `auto` (default) | Deal everything (single-box behavior) | A lone box, or a fleet you don't mind briefly overlapping during bring-up                              |
| `strict`         | Stand down (deal nothing)             | A production fleet: fail closed so a missing or stale map never lets a servicer deal outside its slice |

Set `fleet_mode: strict` for a mainnet fleet, and let `discovery` publish a map before the servicers are expected to deal.

## Funding model

One L1 payer funds all N per-instance teePayers in parallel. Size the L1 payer balance for N instances; each teePayer auto-refills from the L1 payer, exactly like the single-box one.

A fleet uses **more** RPC than one box, because each instance polls independently. If you run the per-channel RPC governor, it caps **each instance separately**, not the fleet as a whole, so divide your target RPS by the instance count to get a global ceiling.

## Running it

| Mode       | Command                          |
| ---------- | -------------------------------- |
| Single box | `npm start`                      |
| Fleet      | `npm run fleet -- --servicers N` |

Run from the dealer directory, or enable it from the dashboard Fleet Setup panel.

Config lives under the `fleet` key:

```
fleet = { enabled, servicers, discovery, settler, sweeper }
fleet_mode
fleet_stale_ms
```
