Build and improve a player

Package and smoke-test

Package a container or game-hosted file and use local episodes to check the matching runtime contract.

Read game.player_runtime in the target manifest; see the runtime comparison. Follow platform-hosted images or game-hosted files, then inspect a complete episode before hosted evaluation.

A local episode does not measure performance against the live policy field. Use hosted Experience Requests for that comparison.

Platform-hosted images

Coworld uploads and hosted execution require a linux/amd64 image. Build explicitly for that platform, including on Apple Silicon:

docker buildx build \
  --platform linux/amd64 \
  --load \
  --tag my-player:local \
  .

Confirm the local tag resolves to the expected platform:

docker image inspect my-player:local \
  --format '{{.Os}}/{{.Architecture}}'

The output should be linux/amd64.

Optionally run a local episode

Override the Coworld’s bundled player with your image:

uv run coworld run-episode ./coworld/cow_.../coworld_manifest.json \
  my-player:local \
  --run python \
  --run -m \
  --run my_player.main \
  --episodes 1 \
  --output-dir ./runs/smoke

Use one --run flag per argument. This is important when the image contains several entrypoints or its default command does not start the player.

For multi-slot games, one supplied image is reused for every slot. Pass one image per slot when you need a mixed roster.

The command waits for the episode and artifact collection to finish. It then prints the results, replay, and log paths.

Inspect smoke-test evidence

If you run locally, confirm:

  • the player connected and stayed connected until the episode ended;
  • the game produced valid results instead of an error or timeout;
  • the player logs contain no unhandled exceptions;
  • the replay shows the behavior you expected;
  • any optional player artifact finished uploading before the container exited.

Use several episodes when one result cannot represent the game’s seats, variants, or randomness:

uv run coworld run-episode ./coworld/cow_.../coworld_manifest.json \
  my-player:local \
  --run python \
  --run -m \
  --run my_player.main \
  --episodes 5 \
  --output-dir ./runs/local-eval

With multiple episodes, the runner writes each run to its own subdirectory. When the selected configuration already has an integer game seed, the runner increments it for each episode.

Browser play

Use browser play when logs cannot show the problem:

uv run coworld play ./coworld/cow_.../coworld_manifest.json \
  my-player:local \
  --run python \
  --run -m \
  --run my_player.main

Test secrets locally

Pass provider keys only at run time:

uv run coworld run-episode ./coworld/cow_.../coworld_manifest.json \
  my-player:local \
  --secret-env API_KEY=...

These values go only to the player containers for that local run. They are not written into the Coworld manifest.

For local model calls, pass your own provider key with --secret-env as described in Call a hosted model.

Keep the first successful command in your project notes or script runner. A repeatable smoke command prevents local and hosted entrypoints from drifting apart.

Game-hosted files

Follow the game’s file format, entrypoint, dependencies, and execution limits. Upload accepts a file or a directory packed as a deterministic zip, capped at 100 MiB of packed bytes. Symlinks are rejected. Directory entries are relative to the directory root; empty directories are omitted. A local episode uses the same packing function as upload, so it checks the bytes that will be submitted.

The outer artifact is staged as file; its original filename and extension are not preserved. Archive entry names are preserved. Follow the game’s entrypoint convention inside the submitted artifact.

Use a downloaded manifest and exactly one file/directory path per seat. For a two-seat game:

uv run coworld download cow_...
uv run coworld run-episode ./coworld/cow_.../coworld_manifest.json \
  ./my-player ./my-player \
  --episodes 5 \
  --output-dir ./runs/local-eval

Omit paths to run bundled players. A single file override is not repeated across seats. Positional JSON files are player bytes, not episode requests. With multiple episodes, each run has its own output subdirectory.

If you run locally, confirm:

  • the game produced valid results instead of an error or timeout;
  • every seat has a non-empty log and expected optional artifact, finalized before results;
  • private player output did not leak into game stdout;
  • behavior stayed within the game’s declared execution limits;
  • the replay shows the behavior you expected.

There is no player container or policy environment; --run, --secret-env, and coworld play are unsupported.

Replay either runtime

For Coworlds that use the game container as their replay viewer, pass the manifest and replay path printed by the runner:

uv run coworld replay ./coworld/cow_.../coworld_manifest.json path/to/replay

Coworlds with a static replay viewer open replays through that viewer instead. Follow the game’s documentation or use the hosted viewer after upload.

Next, upload and evaluate the player.