Skip to main content
When standard environment types don’t fit your use case, MultiTurnEnv provides full control over the rollout loop. This guide covers advanced patterns for building custom environments with complex interaction logic.

When to Customize

Use custom multi-turn environments when you need:
  • Complex game logic — Board games, simulations, strategy games
  • Non-linear conversations — State-dependent message assembly
  • Custom feedback loops — Environment responses based on intermediate state
  • Specialized stop conditions — Domain-specific termination logic
  • Advanced state management — Complex per-rollout initialization and cleanup

The Rollout Loop

Understanding the rollout loop is essential for customization:
Never override rollout() — It’s marked @final for a reason. Override specific methods instead:
  • setup_state() — Per-rollout initialization
  • env_response() — Environment feedback after each turn
  • get_prompt_messages() — Custom message assembly
  • render_completion() — Final conversation rendering
  • add_trajectory_step() — Trajectory metadata

Core Methods to Override

env_response(): Required

Defines how the environment responds after each model turn:
Return value: List of new messages to append (don’t mutate existing messages).

setup_state(): Optional

Initialize per-rollout resources:
Always call await super().setup_state(state) at the end to ensure parent class initialization runs.

get_prompt_messages(): Optional

Customize how messages are assembled for each turn:

render_completion(): Optional

Customize how the final conversation is assembled:

add_trajectory_step(): Optional

Add metadata to each turn:

Stop Conditions

Define when rollouts should terminate using the @vf.stop decorator:

Basic Stop Conditions

Built-in stop conditions (always available):
  • has_error — stops if state["error"] is set
  • max_turns_reached — stops after max_turns iterations
  • prompt_too_long — stops if prompt exceeds model context
  • has_final_env_response — stops if early termination signaled

Priority-Based Execution

Control evaluation order with priorities (higher runs first):

Early Termination from env_response

Signal completion directly from the environment:
Setting state["final_env_response"] triggers the has_final_env_response stop condition.

Resource Management

Cleanup: Per-Rollout

Use @vf.cleanup for per-rollout resource cleanup:

Teardown: Environment Shutdown

Use @vf.teardown for environment-level cleanup:
Idempotency is critical — Cleanup methods may be called multiple times or when resources are in unexpected states. Always:
  • Check if resources exist before cleaning up
  • Handle exceptions gracefully
  • Use try/except blocks
  • Log errors but don’t raise

Error Handling

Verifiers provides structured error handling:

Error Hierarchy

Raising Errors

When a vf.Error is raised:
  1. Automatically caught by the rollout loop
  2. Stored in state["error"]
  3. Built-in has_error stop condition triggers
  4. Rollout terminates gracefully

Complete Example: Tic-Tac-Toe

Here’s a complete custom environment:

Testing Custom Environments

Best Practices

Start simple — Build a minimal working version first, then add complexity incrementally.
Test stop conditions — Ensure rollouts don’t run forever. Add timeout conditions as a safety net.
Log liberally — Use self.logger to log state transitions, decisions, and errors during development.
Don’t mutate messages — Always return new message lists from env_response(), never modify in place.
Handle all error cases — Assume the model will send malformed responses. Validate and provide clear feedback.

Next Steps