This package defines the environment/tool contracts used by agentic rollout. The turn LOOP
itself lives in unirl/rollout/harness/tool_agent.py
(ToolAgentHarness — worker-side task control flow); the distributed runtime hosting it is
AgenticRolloutEngine; trajectory storage is described by the
Sample/Part types.
Environment.reset(request) -> Sampleperforms per-trajectory setup and may augment or replace the request.Environment.step(sample) -> (observation, done, info)consumes the latest generated action. An observation is a decodedPrimitive;Noneappends no turn. The environment, rather than the loop, decides when the episode is done.Environment.close(sample)is an optional, idempotent teardown hook. It must not raise.- A
Toolexposes a JSON function schema andexecute(arguments) -> str. AStatefulToolinstead implements the session lifecyclesession_start,execute_session, andsession_end.
Environment instances are shared by concurrent trajectory threads on a worker. Implementations
must therefore derive state from the Sample, keep it behind a per-trajectory key, or guard it
with locks.
One turn is mechanically:
Sample -> fork -> blocking generate -> environment.step
| done
+------> return Sample
| observation
+------> observe -> next turn
ToolAgentHarness.run calls environment.reset once, forks a one-sample continuation per
turn on the "policy" engine, and stops when the environment returns done=True, after
max_turns, or — at a turn boundary — when the runtime requests a cooperative suspension
(HarnessContext.suspend_requested).
AgenticTrainer expands P prompts into P * n tasks, and RolloutManager
dispatches each through a single engine slot. Ray actor concurrency lets each
worker run up to per_worker_inflight synchronous trajectories while the inner
backend batches requests.
AgenticRolloutEngine.generate runs one trajectory; manager collect provides
group completion, while quiesce provides turn-boundary suspension for cleanup.
A trajectory is one ordered Sample.parts chain:
input(user) -> generated(assistant) -> observation(tool) -> generated(assistant) -> ...
Sample.fork appends a generated Part carrying sampling parameters and lineage-derived sample
IDs. The engine fills that frontier. Sample.observe appends a branch-one input Part, defaults
its role to tool, and carries no sampling parameters, so observations are conditioning rather
than trainable policy output. The next generation reconstructs its chat history from the ancestor
chain. Environments produce observations and termination; the trainer scores
valid terminal answers through its configured RewardService.
On manager quiesce, dispatch pauses and each in-flight turn finishes. A
nonterminal trajectory is checkpointed before its next turn, so generation is
never interrupted mid-turn. The manager returns retained queued or suspended
tasks according to its root filter. The public AgenticTrainer does not resume
these across optimizer steps: normal steps block until every requested group is
complete, and failure cleanup quiesces and discards unfinished work.
ToolEnvironment reads the latest Texts frontier and parses the last Hermes/Qwen-style
<tool_call>{...}</tool_call>. It also accepts a balanced JSON object when a stop string removed
the closing tag. A valid call is dispatched and its raw text result becomes the next tool
observation; chat-template rendering adds any tool-response markup. Tool schemas are injected into
the inner engine's chat_template_kwargs when that configuration supports them, unless the recipe
already supplied tools.
The included tools are:
CalculatorTool: whitelisted arithmetic AST evaluation.SandboxTool: a per-session persistent Python subprocess with timeout and output limits.SearchTool: batched Serper or SerpApi search; requiresSERPER_KEY_ID.VisitTool: Jina page reading with optional OpenAI-compatible summarization.
- A missing or malformed tool call is treated as a final answer. Unknown tools, invalid arguments, and tool exceptions are returned to the model as error text rather than raised.
ToolEnvironment.steprequires aTextsfrontier. Direct batched use keeps rows aligned with empty observations after one row finishes while a sibling continues; production execution uses one trajectory per task and avoids that mixed-row case.- The harness catches an exception from one trajectory, logs it, and returns the partial trace as
a
failedoutcome; the trainer excludes failed trajectories from GRPO statistics. A slot RPC failure poisons the manager and fails the training step. Teardown failures are logged and suppressed. max_turnsis the hard engine bound. When an environment also exposesmax_turns, the production engine requires it to match.ToolEnvironmentalso terminates when no row calls a tool.ToolAgentHarness.runcallsEnvironment.closefromfinallyon success, failure, AND suspension; a teardown error is logged, never raised.
The runnable agentic configuration is
examples/deep_research/deep_research_search_judge.yaml.