Multi-Instance Runner¶
When estimating Q-values via Monte Carlo rollouts from saved mid-trajectory states, the standard runner assumes pure_step=True — a single environment instance can replay from any state. For container-based environments (Harbor, Docker runtimes) where pure_step=False, each rollout needs its own independent environment instance restored to the target state.
The multi-instance runner path handles this transparently: create per-rollout environment instances, restore each to the target state via trajectory replay, then run lockstep batched rollouts with shared LLM inference.
When to Use¶
Use env_factory + restore_fn when:
- The environment has
pure_step=False(mutable backend state) - You need
run_batch_from_states()orrun_batch_from_state_actions()(MC rollouts, Q-value estimation) - Each rollout must operate on an independent environment instance
Configuration¶
from llenvs.evaluation.runner import TrajectoryRunner
from llenvs.adapters.harbor import HarborAdapter, harbor_restore
adapter = HarborAdapter()
# Primary env for trajectory collection
env = adapter.get_environment("terminal-bench@2.0", max_steps=30)
# Factory creates fresh, independent instances
def env_factory():
return adapter.get_environment("terminal-bench@2.0", max_steps=30)
runner = TrajectoryRunner(
environment=env,
backend=backend,
sampling_params=sampling_params,
env_factory=env_factory,
restore_fn=harbor_restore,
)
How It Works¶
When run_batch_from_states() is called and the environment is non-pure with an env_factory:
- Create instances: One fresh environment per rollout in the batch
- Restore state: Each instance is restored to the target state via
restore_fn(in parallel viaThreadPoolExecutor) - Lockstep loop: All active rollouts advance one step together — LLM generation is batched (single GPU call), environment steps run in parallel (I/O-bound container operations)
- Cleanup: All instances are closed in a
try/finallyblock, even on error
Writing a Restore Function¶
A restore function takes (env, state) and returns the restored state. For Harbor, prefer the built-in harbor_restore helper instead of open-coding the replay loop:
from llenvs.adapters.harbor import harbor_restore
runner = TrajectoryRunner(
environment=env,
backend=backend,
sampling_params=sampling_params,
env_factory=env_factory,
restore_fn=harbor_restore,
)
The built-in helper validates task name consistency to guard against index drift across dataset versions, aborts on shell continuation prompts, replays the prefix with the live trajectory_timeout budget disabled, and re-anchors that budget before the restored continuation begins. A naive loop over env.step(...) is incomplete for Harbor because it can silently spend the continuation budget during replay.
Concurrency Tuning¶
Concurrency is controlled by batch_size on run_batch_from_states(). Each item in a batch gets its own environment instance, so batch_size directly caps concurrent containers:
# At most 4 containers running simultaneously
trajectories = runner.run_batch_from_states(
states,
batch_size=4,
)
When batch_size is set and there are more states than batch_size, states are processed in sequential chunks. Within each chunk, environment operations (restore, step) run in parallel threads while LLM generation is batched.
Forced Actions (Q-Value Estimation)¶
run_batch_from_state_actions() works identically — the first step of each rollout uses the forced action instead of generating from the backend:
# Q(s, a) estimation: force first action, then rollout
trajectories = runner.run_batch_from_state_actions(
states=[saved_state] * num_rollouts,
actions=[Action(text=candidate_action)] * num_rollouts,
batch_size=8,
)
Replay Validation¶
For container-based environments, replay consistency is not guaranteed (network-dependent commands, non-deterministic outputs). Use validate_replay_consistency() to test whether replaying a trajectory produces consistent state:
from llenvs.adapters.harbor import validate_replay_consistency
result = validate_replay_consistency(
env_factory=env_factory,
task_index=0,
trajectory=("apt-get update", "pip install pandas"),
probe_commands=("dpkg -l | md5sum",),
num_trials=3,
)
if result["consistent"]:
print("Task is replay-safe")
See the Harbor adapter docs for details on replay validation modes.
Pure-Step Bypass¶
When env_factory is set but the environment has pure_step=True, the multi-instance path is bypassed — the standard single-instance lockstep loop is used instead. This means you can safely set env_factory on a runner that may be used with both pure and non-pure environments.