Skip to content

Matchmaking as a Policy

Problem

You want strangers to be paired into a session, but you don't want clients deciding the matchmaking rules. Group size, mode, and filters should be owned by the server, so a client simply asks to be matched — it can't inflate the group size, change the mode, or bypass a filter.

Solution

Provision a declared Project with a named matchmaking policy, then have clients join by policy name. The policy is a pre-declared pool: it captures the mode/groupSize/filter a client used to supply on entry, moved server-side. Clients call joinProject(project, { policy }), which connects, enters the policy, waits for the server to mint a session, and joins it — all in one call.

1. Provision the Project (once, out-of-band)

Start a server with a management key, then create the Project through the control plane. This example declares one auto policy named duos (group size 2), marked as the Project's default.

bash
curl -X POST http://localhost:8080/admin/projects \
  -H "Authorization: Bearer $STARFISH_MGMT_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "arcade",
    "displayName": "Arcade",
    "policies": [
      { "name": "duos", "mode": "auto", "groupSize": 2, "default": true }
    ]
  }'

See the Project Provisioning API for the full schema and CRUD endpoints.

2. Join by policy

typescript
import { StarfishClient } from "@starfish/client";

const client = new StarfishClient({
  server: "ws://localhost:4000",
  project: "arcade",
});

// Connects, enters the "duos" policy, waits for a match, and joins the
// server-minted session — all in one call.
const { session } = await client.joinProject("arcade", { policy: "duos" });
console.log("Landed in session:", session);
python
from starfish import StarfishClient, StarfishClientOptions

client = StarfishClient(StarfishClientOptions(
    server="ws://localhost:4000",
    project="arcade",
))

# join_project returns the session/join response frame.
join = await client.join_project("arcade", policy="duos")
print("Landed in session:", join.header.session)

Explanation

  • No client-side pool config. The client names the policy (duos) and nothing else. mode, groupSize, and filter come from the Project — the client cannot override them. That is the whole point of declaring a policy.
  • joinProject is the "join a Project, not a Session" ergonomic. It connects (declaring the Project), enters the named policy, awaits the server's matched event, and joins the minted session. It is meant for auto policies, which yield server routing.
  • Omit policy to use the Project's default. joinProject("arcade") enters the policy marked default: true. If the Project declares no default, the server returns pool.policy_required. Naming a policy that doesn't exist returns pool.policy_not_found.
  • No lobby. Matchmaking precedes session membership — you don't join a session before entering a policy. The first matched member to join opens the session; the rest join it.

Variations

Attach metadata for filtering

A policy's filter matches on member metadata. Pass member metadata when you join (meta in TypeScript, attributes in Python); the server applies the policy's filter to it.

typescript
await client.joinProject("arcade", {
  policy: "ranked",
  meta: { region: "eu", skill: "intermediate" },
});
python
await client.join_project("arcade", policy="ranked", attributes={"region": "eu", "skill": "intermediate"})

The policy that owns the filter — for example { "region": "@self" }, meaning "only match members whose region equals mine" — is declared server-side:

json
{ "name": "ranked", "mode": "auto", "groupSize": 2, "filter": { "region": "@self" } }

Interactive policies

Policies can also wrap the interactive modes (claim, mutual, propose, delegated). Entering by policy name is allowed — the config is server-declared — but the interactive claim/propose/assign flow then proceeds as usual. For those, enter by policy with pool.enter({ policy }) and drive the flow yourself rather than using joinProject (which awaits an automatic match). See Pool Matchmaking.

Local development without provisioning

If you haven't provisioned a Project, you're in an implicit Project — there are no policies, so clients supply pool config directly. See Auto Pairing for that client-driven form.

See also