Skip to main content
Use EnvironmentMultiTurn for tasks that take more than one response: a conversation, a tool-using agent, anything where the environment reacts to what the model just did and the model acts again. Subclass it and implement the episode hooks. GRPO rolls out the whole episode, then scores the finished trajectory. OPD can use the same plain multi-turn loop and distills the student’s assistant turns from the managed teacher; native tool-calling environments remain GRPO-only. flash env setup --multi-turn scaffolds a runnable starter on this class — a “guess the secret number” episode with start_episode, step_episode, max_episode_turns, and score_episode wired end-to-end — that you edit into your own task.

The episode loop

During training the trainer drives your hooks in a loop, then scores the result:
  • start_episode(example, prompt_text) returns the initial messages (e.g. a system prompt plus the first user message).
  • max_episode_turns(example) returns the maximum number of assistant actions Flash may sample before forcing the episode to end. The value is per example.
  • step_episode(example, messages, assistant_response) is the core. messages already includes the assistant action being stepped as its last element. Return an EnvironmentStepResult:
    • done=False, messages=(...) keeps the episode open and injects the environment’s response (a tool result, the next user turn);
    • done=True, final_response_text=... ends it. final_response_text overrides the last assistant message as the text passed to scoring.
  • score_episode(example, episode) grades one completed transcript (episode.messages) and returns a RewardResult, exactly like score_response but over the whole trajectory.
  • score_episodes(example, episodes) is the batch wrapper Flash calls for a GRPO group. You normally do not implement it; the SDK default runs your singular score_episode for each item, using max_score_concurrency.

Full multi-turn contract

This is the worker-facing surface Flash expects:
EnvironmentStepResult.messages should contain only new environment-side messages to append after the assistant action. Do not repeat the assistant message Flash just sampled. metadata is appended to the completed episode’s metadata under steps, available to score_episode and logs. Keep durable episode state in the transcript, not in hidden mutable attributes.

An action protocol the model can emit as text

The trained policy is an open model, not a tool-use API, so define a plain-text protocol it can produce and your step_episode can parse — for example, a tool-call block versus a final reply:
environment.py

SFT targets for multi-turn

SFT does not execute the multi-turn loop. It builds one supervised row as:
The default sft_completion(example) turns example.output into the target messages — a scalar becomes one assistant message, and a message list (or {"messages": [...]}) becomes the full transcript (see Datasets). Flash uses completion-only loss for SFT. It masks the token prefix rendered from start_episode(...) and trains on every token in sft_completion(...). In a multi-turn target transcript, any interleaved assistant, user, and tool/environment messages in output are part of the supervised completion. If a multi-turn environment has only scalar outputs, SFT collapses to one assistant turn per row and does not run step_episode; provide full target transcripts or override sft_completion when you need multi-turn SFT.

Keep step_episode stateless

The SDK doesn’t hand you a per-episode scratch object, and GRPO runs many rollouts of the same example concurrently — so don’t stash mutable world-state on the env keyed by example. If your task has state (an API or service that changes as tools run), make the transcript the source of truth and rebuild state by replaying messages inside step_episode / score_episode. Replay is reproducible as long as your service is deterministic given the same calls (seed any ids; avoid wall-clock-dependent logic in the reward).

Score behaviours, not a script

Open-ended tasks rarely have one correct transcript, so don’t grade by imitation. Score the behaviours you care about — did it take the resolving action, follow required ordering (e.g. authenticate before a privileged call), stay within limits — and return that blend from score_episode. Keeping the reward in a plain function (importable without the trainer) makes it unit-testable.

Configure for multi-turn

For multi-turn runs, the episode budget comes from max_episode_turns. What matters in [train]:
  • max_completion_tokens is per assistant action (one tool call or one reply), not per episode — keep it modest.
  • max_context_tokens must hold the system prompt plus the whole running transcript (every turn, every tool result), so set it well above a single-turn task.
  • group_size is the number of full episodes rolled out per prompt for the GRPO advantage estimate. OPD usually uses group_size = 1.

Managed generation request deadlines

Pure multi-turn GRPO model generations have managed request deadlines between 10 and 60 minutes, based on context size. A timed-out assistant generation is aborted and retried once; timed-out output is never sent to the environment. This covers only model generation in the pure multi-turn GRPO path. It does not cover environment or external-service calls, OPD, or tool-loop paths.

Next steps

Training

Submit a run once your environment is ready.

Deploy & chat

Serve the trained adapter.