The scripts are in the repository, not in the installed package. Clone it,
then
cd recipes/01-simulate/bring-your-own-agent before running the commands below. Browse this recipe on GitHub.The contract
The engine calls your function once per rollout with the situation the simulator wrote, and expects the tool calls it made and what it said:simulator=False uses the built-in template writer so no model key is
needed; the situations are less varied than a model writes, which is fine
for wiring up an agent and a judge. Any extra keys on the dict stay on the
row. Inside the callable, current_rollout.rollout_index says which repeat
this is, if the agent needs to know.
repeat_policy="fixed" asks for all four repeats up front. The mode="rl"
default is "successive": two probe rollouts per ask, and the remaining
repeats only on asks whose graded probes disagree. That is the right
economy for a graded run, but an ungraded one never splits, so it would
stop at two repeats per ask and the example’s every-third-repeat agent
would never misbehave.
When the agent is the problem
A callable that raises, or returns an OpenAI-style message instead of the contract, used to produce a zero-row run that blamed the situation writer. Now the run says so:budget=8; it moves by a few between runs.
data.stopped_because == "agent_failed" when no row survived,
data.search["agent_errors"] is the count, data.search["first_agent_error"]
carries the exception type and message, and "agent_errors" lands in
data.degraded whenever any rollout was lost this way, even on a run that
still produced rows.
An eval score is not a reward
run_judge(rows, judge) and evaluate(rows, judge) produce the same row
shape and the same numbers. The difference is provenance: evaluate stamps
lineage.source == "eval" because it is meant for a held-out set. Training
on those rows makes the scorer you report the reward you optimised against.
select_for_rl, select_for_sft and build_preference_pairs each report
eval_sourced and warn when it is non-zero. Nothing is dropped; the count
is the guard. Grade the training set with run_judge (or
data.grade(judge=...)), and keep evaluate for the rows the model never
sees.