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.