Skip to content

conformance: probe environment (SPEC 8.1) and driven scenarios - the checker sees what a client does with each action - #8

Merged
tactino merged 3 commits into
mainfrom
feat/conformance-probe
Oct 2, 2026
Merged

tactino merged 3 commits into
mainfrom
feat/conformance-probe

Conversation

@tactino

@tactino tactino commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

SPEC.md section 8 lists what a client must do. The checker watched one well-behaved connection, so it could not tell several of those clauses from their violations:

  • chunk-summed reward;
  • the terminal observation on a done step;
  • time-major action order;
  • reading metadata before sending anything;
  • the connection options;
  • the two close reasons;
  • dropping held feedback across a reconnect;
  • text frames.

A client breaking all of these still printed "no violations", and SPEC.md said so.

The probe environment (new SPEC 8.1). A client under test runs n probe envs instead of its real ones. Env i counts the steps of its episode in states["t"], echoes the action it applied last in states["a"], pays 1 per step, and terminates at t = 3 + 2(i mod 3). The checker sends actions whose values encode their position (10000 k + 10 j + d). From each feedback it then works out:

  • how many steps the client ran: they must be between 1 and H, and the reward must equal them;
  • which action it applied last: this checks time-major, in-order application, and the dtype;
  • whether a done env reported its own terminal observation, and restarted at t = 0.

Driven scenarios (--scenario basic|resync|text|all). The checker drives the connection instead of watching it:

  • basic:
    • speaks 0.3 s late, to catch a client that sends first;
    • sends a 2 MiB metadata frame with an unknown key, which catches a kept 1 MiB frame cap or a client that rejects unknown keys;
    • flags a handshake that offers permessage-deflate;
    • stops the run with plugrl-server-stop, after which the client must exit 0 and not reconnect.
  • resync: halfway, closes with plugrl-server-resync just after sending an action. The client must reconnect, and start the new connection with an infer, not the feedback it was holding.
  • text: answers an infer with a text frame. The client must stop.
  • all runs each in turn, starting the client once per scenario.

Passive mode is unchanged and remains the default, so the C++ client's CI steps are untouched.

Proving each check works. examples/raw_client.py --probe implements the probe env, executes chunks as SPEC 5.3 says, and handles the close reasons. It passes all three scenarios with the existing text note. Its --bug option breaks one clause at a time:

  • early-infer, compress, frame-cap, float32, env-major;
  • last-step-reward, reset-in-step, resend-feedback, stale-chunk, ignore-stop, ignore-text.

tests/test_conformance_probe.py runs the checker against each bug and expects it to name that clause. All eleven are caught, and the conforming client passes.

What probe mode still cannot check is listed in SPEC 8:

  • reading env_ids, which must equal env_indices anyway;
  • a feedback env set that differs from the infer set, which the client chooses;
  • requiring no metadata key;
  • dropping feedback after closes other than a resync.

CI.

  • The test job installs the conformance extra, so the new tests run rather than skip.
  • The cross-language job adds a probe run of the Python reference client over all scenarios.

Local run: 42 passed (30 existing, 12 new).

It has already found two real bugs.

Next: the C++ reference client gets the probe env, and plugrl-env-client gets a probe-v1 env so the main client can be graded the same way.

…the checker sees what a client does with each action and how it handles closes

The passive checker watched one connection and could not tell chunk-summed
reward, terminal observations, time-major order, metadata-first, the close
reasons or text frames from their violations. With --probe the client runs a
probe env whose observation counts steps and echoes the applied action, and
the checker drives the connection: it speaks late, sends a 2 MiB metadata
frame, closes for a resync, stops the run, and answers with a text frame.
raw_client.py implements the probe env, and its --bug option breaks each
clause in turn; tests/test_conformance_probe.py checks that each is named.
…fter

Found by the probe checker itself, on Linux, in about one run in two of the
resync scenario: when the server's resync close arrived while the feedback
was being sent, the send raised, the reset after it never ran, and env 0
carried its finished episode onto the next connection (t = 7, 11, 15 for an
episode of 3). The packed feedback already holds the terminal observation,
so resetting first changes nothing the server sees.
@tactino

tactino commented Oct 1, 2026

Copy link
Copy Markdown
Member Author

Added one commit. On Linux the resync scenario failed the conforming client about one run in two, and the checker was right to fail it. When the resync close arrived while the client was sending its feedback, the send raised, and the reset after it never ran. Env 0 then carried a finished episode onto the next connection, reaching t = 7, 11 and 15 in an episode of 3. The client now resets ended episodes before sending; the packed feedback already holds the terminal observation. After the fix, the probe tests passed five runs in five on Linux.

…s a 7.6 violation; raw_client --bug stale-chunk

plugrl-env-client failed the resync scenario this way: after reconnecting it
fed back chunks answered on the old connection, which the checker reported
under 4.3. On a reconnected connection, feedback for an env that was never
given an action there is now named for the clause it breaks. The new
stale-chunk bug does exactly that, and the test expects the 7.6 clause.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant