Skip to content

Pool Matchmaking API

Pools are named matchmaking queues. Clients enter a pool, the server pairs them into groups atomically, and each matched client receives a server-generated session name via a matched event. Matched clients are not auto-joined — they call join() with the returned session name themselves. Matchmaking precedes session membership: you do not join a session before entering a pool.

Pools are scoped to the connection's Project — matchmaking pairs members only within the same Project. There are two ways to enter (see Entering a Pool): the ad-hoc form, where the client supplies the pool config, and the policy form, where the client names a declared matchmaking policy and the server owns the config.

For conceptual background and end-to-end flows, see the Pool Matchmaking workflow, the Pools section of Core Concepts, and Matchmaking as a Policy.

Pools use WebSocket only — pool frames are never sent over RTC.

Pool Modes

The mode is fixed when the pool is created and is immutable for the pool's lifetime.

ModeWho matchesMembers visibleRelevant methods
autoServer (FIFO, respects filter)Noenter, matched$
claimAny member (first claim wins)Yesenter, members$, claim, matched$
mutualBoth members (each must claim the other)Yesenter, members$, claim, claimRejected$, matched$
proposeOne proposes, the other accepts/rejectsYesenter, proposal$, accept, reject, matched$
delegatedA matchmaker-role client via assign()Matchmaker onlyenter (role: matchmaker), members$, assign, matched$

Entering a Pool

enter has two forms:

Policy resolution (policy form):

  • Omitting policy uses the Project's default policy. If the Project declares no default, the server returns pool.policy_required.
  • Naming a policy that doesn't exist returns pool.policy_not_found.
  • Policies are only meaningful in declared Projects; an implicit Project has none, so only the ad-hoc form works there.

For auto policies the SDK also offers client.joinProject(project, { policy }), which connects, enters the policy, awaits the match, and joins the session in one call — see Matchmaking as a Policy.

TypeScript API — Pool class

Access the pool interface through client.pool (a Pool instance). Defined in sdks/typescript/src/pool.ts; types in sdks/typescript/src/pool-types.ts.

MemberSignatureDescription
enter (ad-hoc)enter(poolName: string, options: PoolEnterOptions): Promise<PoolEnterResult>Enter a client-named pool with client-supplied config. In claim-based modes, seeds members$ with the current member list.
enter (policy)enter(options: PoolPolicyEnterOptions): Promise<PoolEnterResult>Enter by declared policy name (or the Project's default). The server owns mode/groupSize/filter.
leaveleave(poolName: string): voidLeave the pool and clear local state (fire-and-forget).
claimclaim(poolName: string, targetId: string): voidClaim a specific member (claim and mutual modes).
acceptaccept(poolName: string, fromId: string): voidAccept a proposal (propose mode).
rejectreject(poolName: string, fromId: string): voidReject a proposal (propose mode).
assignassign(poolName: string, groups: string[][]): Promise<StarfishFrame>Matchmaker only — assign groups (delegated mode). Resolves with the assign response frame.
members$Observable<PoolMember[]>Live member list, updated by member-joined / member-left events.
matched$EventStream<PoolMatchedEvent>Fires when the server matches you; carries pool, session, and peers.
proposal$EventStream<{ pool: string; from: string; attributes?: Record<string, unknown> }>Fires when a peer proposes a match (propose mode).
claimRejected$EventStream<{ pool: string; target: string }>Fires when a claim you made is rejected (mutual mode).

Python API — StarfishClient pool methods

The Python SDK exposes pool methods directly on StarfishClient. Defined in sdks/python/starfish/client.py, backed by sdks/python/starfish/pool.py.

MethodSignatureDescription
pool_enterasync pool_enter(options: PoolEnterOptions | PoolPolicyEnterOptions) -> PoolEnteredResultEnter the pool. Pass PoolEnterOptions for the ad-hoc form or PoolPolicyEnterOptions for the policy form. Raises RuntimeError on a pool.not_found error.
pool_leaveasync pool_leave(pool: str) -> NoneLeave the pool (fire-and-forget).
pool_claimasync pool_claim(pool: str, target: str) -> NoneClaim a specific member (claim and mutual modes).
pool_acceptasync pool_accept(pool: str, from_: str) -> NoneAccept a proposal (propose mode).
pool_rejectasync pool_reject(pool: str, from_: str) -> NoneReject a proposal (propose mode).
pool_assignasync pool_assign(pool: str, groups: list[list[str]]) -> StarfishFrameMatchmaker only — assign groups (delegated mode).
pool_memberspool_members(pool: str) -> Observable[list[PoolMember]]Per-pool observable, updated by member-joined / member-left events.
pool_matchedpool_matched -> EventStream[PoolMatchResult] (property)Fires when the server matches you.

The Python SDK surfaces the matched and member-list events. Proposal and claim-rejected events (propose and mutual modes) are received by the server-side handlers but are not currently exposed as Python observables.

Types

TypeScript

ts
type PoolMode = "auto" | "claim" | "mutual" | "propose" | "delegated";
type PoolRole = "member" | "matchmaker";

// Ad-hoc form: the client supplies the pool config.
interface PoolEnterOptions {
  groupSize: number;
  mode?: PoolMode;                        // default: "auto"
  role?: PoolRole;                        // default: "member"
  meta?: Record<string, unknown>;         // member metadata (wire field: `attributes`)
  filter?: Record<string, string>;
  create?: boolean;                       // omitted → not created; send true to create
}

// Policy form: the client references a declared policy; the Project owns the config.
interface PoolPolicyEnterOptions {
  policy?: string;                        // omit to use the Project's default policy
  role?: PoolRole;                        // default: "member"
  meta?: Record<string, unknown>;
}

interface PoolMember {
  id: string;
  meta?: Record<string, unknown>;         // wire field: `attributes`
}

interface PoolEnterResult {
  pool: string;
  members: PoolMember[];
}

interface PoolMatchedEvent {
  pool: string;
  session: string;
  peers: PoolMember[];
}

enter() resolves with a PoolEnterResult (the resolved pool name and its current roster). assign() resolves with the raw StarfishFrame response; read response.payload for the matched groups and their session names.

Python

python
# Ad-hoc form: the client supplies the pool config.
@dataclass
class PoolEnterOptions:
    pool: str
    create: bool | None = None           # omitted → not created; send True to create
    mode: str = "auto"                   # "auto" | "claim" | "mutual" | "propose" | "delegated"
    group_size: int = 2
    role: str | None = None              # "member" | "matchmaker"
    attributes: dict | None = None
    filter: dict | None = None

# Policy form: the client references a declared policy; the Project owns the config.
@dataclass
class PoolPolicyEnterOptions:
    policy: str | None = None            # omit to use the Project's default policy
    role: str | None = None
    attributes: dict | None = None

@dataclass
class PoolMember:
    id: str
    attributes: dict = field(default_factory=dict)

@dataclass
class PoolMatchResult:
    pool: str
    session: str
    peers: list[PoolMember]

@dataclass
class PoolEnteredResult:
    pool: str
    mode: str
    group_size: int
    members: list[PoolMember] = field(default_factory=list)

create is opt-in in both SDKs. The pool is created only when you pass create: true (TypeScript) / create=True (Python). An omitted create is join-only: entering a pool that doesn't exist returns pool.not_found. The policy form has no create field — declared policies are provisioned out-of-band.

meta vs attributes. The TypeScript SDK exposes member metadata as meta (serialized as attributes on the wire); the Python SDK names it attributes throughout.

Protocol Message Types

Wire-level frames, all with resource: "pool". Request/response pairs share a method and are distinguished by kind (request vs response); events use kind: "event".

MethodKindDirectionDescription
enterrequestclient → serverEnter a pool
enterresponseserver → clientAcknowledgement; includes the current member list in claim-based modes
leaverequestclient → serverLeave a pool (no response)
claimrequestclient → serverClaim a specific member (claim/mutual modes)
claimresponseserver → clientPending acknowledgement (mutual mode, before the match completes)
acceptrequestclient → serverAccept a proposal (propose mode)
rejectrequestclient → serverReject a proposal (propose mode)
assignrequestclient → serverMatchmaker assigns groups (delegated mode)
assignresponseserver → clientConfirmation with the matched groups and their session names
matchedeventserver → clientMatch fired; carries the session name and peers list
member-joinedeventserver → clientA member entered the pool (visible members / matchmaker only)
member-lefteventserver → clientA member left (carries memberId and reason)
proposaleventserver → clientA peer proposed a match (propose mode)
claim-rejectedeventserver → clientA claim was rejected (mutual mode)

member-left reasons: "left", "matched", "timeout", "disconnected".

enter payload fields

FieldTypeRequiredDescription
policystringnoName of a declared matchmaking policy (policy form). When present, pool/create/mode/groupSize/filter are supplied by the policy and MUST NOT be set. Omit to use the Project's default policy.
poolstringad-hoc onlyPool name. Ad-hoc form only; omit when using policy.
groupSizenumberad-hoc onlyClients per match group (used only on creation)
createbooleannoCreate the pool if it does not exist (ad-hoc form)
modestringnoMatchmaking mode (default "auto"; used only on creation)
rolestringno"member" (default) or "matchmaker" (delegated mode)
attributesobjectnoOpaque member metadata; available to filters and visible members. (The TypeScript SDK exposes this as meta.)
filterobjectnoAttribute constraints for auto mode; literal values or "@self"

Error Codes

CodeRaised byDescription
pool.not_foundenter (ad-hoc)The pool does not exist and create was false or omitted. Python raises RuntimeError.
pool.mode_mismatchenter, claim, assignOperation not allowed in this pool's mode (e.g. role: "matchmaker" outside delegated mode, or claim in auto mode)
pool.policy_not_foundenter (policy)The referenced matchmaking policy is not declared on the connection's Project. See Troubleshooting.
pool.policy_requiredenter (policy)No policy was named and the Project declares no default policy. See Troubleshooting.
project.lockdownenter (ad-hoc)Ad-hoc creation attempted in a locked-down Project. See Troubleshooting.

Entering a pool no longer requires joining a session first — matchmaking precedes session membership.