Skip to main content
The workflow below checks two things on every pull request that touches a world or the agent that calls it: whether each world still answers the way its author says it must, and whether the agent can still get through a task in one. The complete file is at /examples/api-worlds-ci.yml.

What the workflow needs

Three repository secrets, and nothing else. The keys decide which project the worlds are read from, so a CI key pointed at a staging project keeps pull requests away from production data. The gateway CLI reads all three straight from the environment, so no gateway auth login step is needed. Install it with npm install -g @withgateway/sdk; it needs Node 18 or newer.
GATEWAY_HOST is not secret, and a repository variable (vars.GATEWAY_HOST) works just as well. Keeping all three together as secrets keeps the workflow short.

The workflow

The tests job checks the worlds

gateway worlds test <slug> runs the suite the world’s head version ships — the tests/*.json cases and tests/test_*.py files described in Test a world — on the platform, and exits 1 when any case fails. One matrix entry per world means a broken world fails its own job under its own name, and fail-fast: false lets the rest finish, so one run reports every failure.
The argument is a slug, not a path. If a directory of the same name sits in the working directory, the CLI tests that directory instead. Keep matrix values distinct from your folder names, or pass ./worlds/<dir> deliberately.

The smoke job checks the agent

One session, one agent, one grade. The session is opened on the world’s api surface, the agent is pointed at api.url with the session token, and the task’s own grader scores the end state. Four details in that job matter. Open with --no-wait and poll. --no-wait prints the session id as soon as the platform accepts the open, and the step polls gateway worlds session status until ready is true. Mask the token. The session token gates the api surface, so ::add-mask:: it before it can reach a log line, and send it as an Authorization: Bearer header — never in a URL. Gate on a number. gateway worlds session grade prints {sessionId, reward, rewards, raw}; jq -e turns the reward into the step’s exit code so a weak run fails the job rather than passing quietly. Close in an always() step. A session that no job closes sits there until it closes for being idle. Closing it in always() returns the container whether the agent passed, failed, or crashed. Your agent reads two variables and nothing else:
An agent needs a base URL and a header. Nothing else about it changes.

Check the pull request’s own tree before it is pushed

Both jobs above read published versions. A pull request that edits a world’s contract, handlers or cases has not published anything yet, so add a job that tests the tree in the checkout:
With a python3 of 3.12 or newer on the runner, the CLI serves the world from its bundled runtime and runs everything locally — no push, no version, no session. Install pytest alongside it, or the Python half of the suite fails and the python tests line names the install command.

Push on merge, and let the push run the tests

Publish on merge, not on every pull request. A world whose gateway-env.toml carries [actions] on_push = ["tests"] gets its suite run against each pushed version automatically, so the merge workflow only has to push:
The on-push run never blocks or fails the push. Read the result afterwards in the world’s Tests tab or with gateway bench tests acme-ledger.
The on-push run sees the pushed tree and nothing else, so cases that count rows fail on a version whose data has not been published yet. Test a world covers how to keep JSON cases bare-tree-safe.

One command instead of three

gateway worlds ci is the whole smoke job in one line. It pushes the checked-out world, runs every task, writes a summary table into $GITHUB_STEP_SUMMARY, and exits 1 under the score you set:
The branch defaults to GITHUB_HEAD_REF on a pull request, so each pull request’s runs are tagged with its own branch. Use the longer form when you need the session, the URL, or the token yourself.

The same gate from TypeScript

When the pull request changes the world’s own source, validateWorld does it in one call from Node: it pushes the checkout on the pull request’s branch, dispatches a named run config against that exact version, waits for every run, gates on the score, and returns a comment-ready markdown summary.
When it is your agent that changed rather than the world, runSessions opens one container per scenario, runs your agent against each, and gates the mean reward. Both are covered in Gate a pull request and Run a task suite.

Where to go next