Skip to content

fix(textarena_env): forward reset(seed=...) so episodes are reproducible - #1127

Open
akashjainn wants to merge 2 commits into
huggingface:mainfrom
akashjainn:fix/textarena-forward-reset-seed
Open

fix(textarena_env): forward reset(seed=...) so episodes are reproducible#1127
akashjainn wants to merge 2 commits into
huggingface:mainfrom
akashjainn:fix/textarena-forward-reset-seed

Conversation

@akashjainn

@akashjainn akashjainn commented Sep 9, 2026

Copy link
Copy Markdown

Summary

TextArenaEnvironment.reset() accepted a seed and dropped it, so episodes were never reproducible even though the signature said they were. This forwards it to TextArena's own reset, which has always taken one.

Fixes #1102.

Type of Change

  • Bug fix

Alignment Checklist

  • I have read .claude/docs/PRINCIPLES.md and this PR aligns with our principles
  • I have checked .claude/docs/INVARIANTS.md and no invariants are violated
  • I have run bash .claude/hooks/lint.sh and tests and addressed all issues

Reproducibility is something the project already optimises for, and the Gymnasium-style reset/step/state API is a stated principle. A seed parameter that is accepted and ignored works against both.

RFC Status

  • Not required (bug fix, docs, minor refactoring)

Test Plan

Before, on Wordle-v0, reading the secret word off TextArena's own game state:

six resets at seed=1234: clock, sugar, month, price, flame, steam

After:

seed=1234 x6 : slope, slope, slope, slope, slope, slope -> 1 distinct
seed=999  x3 : cheap, cheap, cheap                      -> 1 distinct
no seed   x6 : trick, twist, taste, start, stage, curve -> 6 distinct
stable across instances : True

slope matches what the reporter independently got when they tried forwarding the seed.

Three tests added to tests/envs/test_textarena_environment.py:

  • a fixed seed gives one episode and a different seed gives another
  • the same seed survives constructing a fresh environment
  • unseeded resets still vary, so forwarding does not accidentally pin them

The first two fail without the change; the third passes either way and is there to catch an over-broad fix.

PYTHONPATH=src:envs pytest tests/envs/test_textarena_environment.py   5 passed
PYTHONPATH=src:envs pytest tests/                                     1415 passed, 121 failed
                                                                      (121 identical on main; unrelated missing env deps)
ruff format --check / ruff check on both changed files                clean

Notes

The issue asked whether forwarding is correct for every wrapped TextArena game, or whether raising on a non-None seed would be safer. seed: Optional[int] = None is on Env.reset and on Wrapper, ObservationWrapper, ActionWrapper and RenderWrapper, so it is safe through the whole chain, and determinism holds on Wordle, GuessTheNumber, Hangman, Crosswords, Sudoku and FifteenPuzzle. So forwarding rather than raising.

__init__ still resets unseeded. That call only exists to leave the environment valid before the first step(), and reset() replaces the state it creates, so seeding it would mean widening the constructor signature for no behaviour change. Happy to do it if you would rather the constructor took a seed.

@caiotheodoro offered to open a PR for this in the issue. I had it written before picking the issue up and thought the multi-game determinism check was worth having either way, but I am glad to close this if they would rather carry it.


Note

Low Risk
Single-line behavior fix aligning with the documented Gymnasium-style API; tests cover seeded vs unseeded reset behavior.

Overview
TextArenaEnvironment.reset() now forwards seed to TextArena’s underlying reset, so callers get reproducible episodes instead of accepting seed and ignoring it.

New Wordle-focused tests assert the same seed yields the same puzzle (via game_state["secret_word"], not the static prompt), reproducibility across fresh env instances, and that resets without a seed still vary.

Reviewed by Cursor Bugbot for commit 5586da8. Bugbot is set up for automated code reviews on this repo. Configure here.

TextArenaEnvironment.reset() accepts a seed, as every Environment does, and
called self._ta_env.reset(num_players=...) without it. The seed had no effect
and nothing reported that, so a caller reading the signature would reasonably
believe an episode was reproducible when it was not.

Six resets at seed=1234 on Wordle-v0 gave six different secret words
(clock, sugar, month, price, flame, steam). With the seed forwarded they give
"slope" every time, and a fresh environment at the same seed gives it too.

TextArena's own reset takes seed on Env and on Wrapper, ObservationWrapper,
ActionWrapper and RenderWrapper alike, so forwarding is safe through the
wrapper chain OpenEnv builds. Checked determinism on Wordle, GuessTheNumber,
Hangman, Crosswords, Sudoku and FifteenPuzzle: unseeded resets vary, a fixed
seed is stable, and different seeds differ.

__init__ still resets unseeded. It only exists to leave the env in a valid
state before the first step(), and reset() replaces that state, so threading a
seed through the constructor would widen the change without changing behaviour.

Fixes huggingface#1102

@burtenshaw burtenshaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. LGTM!

@bot-ci-comment

bot-ci-comment Bot commented Sep 9, 2026

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forwarding fixes single-session reproducibility, but the pinned TextArena 0.7.4 implementation calls process-global random.seed(seed) during reset. Because this wrapper advertises concurrent sessions, the one-line change leaks seeded state into other sessions and can race. Please either incorporate a serialized save/seed/reset/restore implementation with non-skipped TextArena coverage, or supersede this PR with the corrected preservation approach in #1078.

Open in Web View Automation 

Sent by Cursor Automation: Release

env.full_observations = {}

self._ta_env.reset(num_players=self.num_players)
self._ta_env.reset(num_players=self.num_players, seed=seed)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This call mutates process-global RNG state in TextArena 0.7.4 (SinglePlayerState.__init__State.__init__random.seed(seed)). A concurrent or subsequent unseeded session therefore observes the seeded stream. Serialize all underlying resets with a process-wide lock and save/restore RNG state for seeded calls; add coverage that proves seeded resets neither perturb nor race an unseeded session.

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.

textarena_env: reset() accepts a seed and discards it, so runs are not reproducible

2 participants