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)caps the assistant actions Flash may sample before forcing the episode to end, per example.step_episode(example, messages, assistant_response)is the core.messagesalready includes the assistant action being stepped as its last element. Return anEnvironmentStepResult: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_textoverrides the last assistant message as the text passed to scoring.
score_episode(example, episode)grades one completed transcript (episode.messages) and returns aRewardResult, exactly likescore_responsebut 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 singularscore_episodefor each item, usingmax_score_concurrency.
Full multi-turn contract
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 yourstep_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:sft_completion(example) turns example.output into the target
messages (see Datasets).
When ChatML role parsing succeeds, Flash supervises authored assistant
bodies and masks non-assistant target turns. An unparseable transcript keeps
completion-only fallback and logs a warning that observation masking is
unproven. See Multi-turn SFT
masking.
A scalar target remains one assistant turn. SFT does not run step_episode;
provide a target transcript or override sft_completion for 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.
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 fromscore_episode. Keep the reward in a plain
importable function so it stays unit-testable.
Per-turn credit (GRPO)
By default, multi-turn GRPO assigns one reward to the whole episode (credit_assignment = "per_episode"): every assistant turn shares the
trajectory’s group-relative advantage. For a pure multi-turn task where each turn
is independently gradable, set credit_assignment = "per_turn" in [train] to
give each assistant turn its own group-relative advantage instead.
Supply the per-turn signal from score_episode by returning a RewardResult
whose metadata carries a per_turn_rewards list, one finite value per recorded
sampled assistant turn, in order. episode.turns excludes the seeded
start_episode prompt but includes environment replies. When environment replies
use only user or tool roles, filter to assistant turns:
per_turn_rewards must contain one finite ordered value per recorded sampled
assistant turn. A missing or invalid vector makes the affected rollout group use
episode-level credit and logs a warning. per_turn credit is rejected for
native tool-calling multi-turn environments, and a single-turn run is identical
either way.
Configure for multi-turn
For multi-turn runs, the episode budget comes frommax_episode_turns. What
matters in [train]:
max_completion_tokensis per assistant action (one tool call or one reply), not per episode — keep it modest.max_context_tokensmust hold the system prompt plus the whole running transcript (every turn, every tool result), so set it well above a single-turn task.group_sizeis the number of full episodes rolled out per prompt for the GRPO advantage estimate. OPD usually usesgroup_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.