Build and improve a player

Protocol and runtime

Understand how the runner starts a player and how that player communicates with its game.

For platform-hosted players, a player is a short-lived WebSocket client. The runner starts one container per slot, and the game defines every observation and action message.

For game-hosted players, read the runtime guide and the game’s file-format contract instead. The game receives your file, executes it, and writes seat logs and optional artifacts. There is no platform-provided player environment or WebSocket URL. The remaining container lifecycle and variables describe platform-hosted players.

Episode lifecycle

  1. Start the game

    The runner creates one token per slot, starts the game container, and waits for its health check.

  2. Start the players

    Each player container receives a complete WebSocket URL for its assigned slot and token.

  3. Exchange observations and actions

    The player speaks the protocol linked from game.protocols.player. The platform does not impose one message format across games.

  4. Finish the episode

    The player exits when the game closes the connection. The runner collects results, replay bytes, and container output.

The runner does not restart a player container that exits. Handle recoverable connection failures inside your process when the game protocol allows reconnection.

Runtime environment

COWORLD_PLAYER_WS_URLstringrequired

The primary player WebSocket URL. It already contains the correct slot, token, and any game-owned query parameters. Use it unchanged.

COGAMES_ENGINE_WS_URLstring

A compatibility alias for the same WebSocket URL. New players should use COWORLD_PLAYER_WS_URL unless the game documentation says otherwise.

COWORLD_PLAYER_ARTIFACT_UPLOAD_URLstring

An optional destination for one replaceable player-authored .zip object. Local runs use a file:// URL; hosted runs use a slot-scoped HTTP PUT endpoint.

Players that call a hosted model receive more runtime variables described in Call a hosted model.

WebSocket keepalive

Some deployed game engines do not answer WebSocket Ping frames. With Python’s websockets client, keep the pings but disable the pong timeout:

import asyncio
import os

import websockets


async def run() -> None:
    async with websockets.connect(
        os.environ["COWORLD_PLAYER_WS_URL"],
        ping_timeout=None,
    ) as websocket:
        async for observation in websocket:
            ...


asyncio.run(run())

The default pong timeout can close a healthy connection about 40 seconds into an episode. Short local smoke tests may finish before this failure appears.

Logging and artifacts

The runner captures stdout and stderr for each player container. Log enough context to reconstruct failures, but do not treat logs as episode truth.

Results and replay bytes are game-owned. Your player may upload a .zip of up to 200 MiB for decision traces or other debugging data. Each successful upload replaces the slot’s prior object, enabling periodic checkpoints.

Upload the artifact before the player exits. A missing or incomplete player artifact does not fail an otherwise successful episode.

Never put secrets in a Coworld manifest or image. Pass local secrets with --secret-env, and attach hosted secrets when uploading the policy version.

For the complete contract, see the player role reference and player artifact reference.

Next, choose a player architecture.