Skip to main content
AgentWorkflow is the SDK contract for rollout behavior. You subclass it, implement one async run() method, and create one sample either by returning its message history or by calling the current policy through an Osmosis-supported agent integration that registers the sample source. The workflow should answer one question: given this dataset prompt, what should the agent do before the grader scores the result?

Base Class

The SDK shape is:
run() is called once for each workflow execution. Construct any per-execution agent or session objects inside the method and run the agent. Then either return the sample directly or let the integration register the resulting conversation with the active RolloutContext.

Workflow Return Value

run() accepts three shapes, all normalized to a single sample: Supported integrations register a sample source for you, so most workflows return None: OsmosisStrandsAgent registers its Strands history, while OsmosisMemorySession registers an OpenAI Agents SDK session. Return AgentWorkflowOutput when you build the message history yourself:
AgentWorkflowOutput.info is reserved and is not currently passed to graders. Use metrics for finite numeric sample measurements and ctx.artifacts_dir for files.
AgentWorkflowOutput rejects unknown top-level fields and non-finite metric values (NaN, inf, -inf). The same validation runs across Local and Harbor/container execution.

AgentWorkflowContext

The ctx object gives the workflow its input and config: If your dataset row contains system_prompt, user_prompt, and ground_truth, the prompt fields are assembled into ctx.prompt. The reference answer is not passed to the workflow; it is exposed to your grader as ctx.label. The same metadata object is available on both AgentWorkflowContext and GraderContext, so workflows and graders can read the same per-row context.
Keep task answers out of AgentWorkflow.run(). The workflow should produce behavior; the Grader should decide whether that behavior deserves reward.

Writing Artifacts

Use ctx.artifacts_dir to write files that shouldn’t be embedded in the sample payload, such as logs, traces, screenshots, or other large or binary outputs. Each rollout gets its own directory, but it’s None when the environment can’t provision one, so guard with if ctx.artifacts_dir: before writing to it. An unguarded write raises and fails the workflow.
After the rollout finishes, collected files appear alongside its sample in the run’s Artifacts panel on the Osmosis Platform, mirroring the layout you write under ctx.artifacts_dir. Artifact collection never affects rewards or rollout status. Write artifacts off the event loop with asyncio.to_thread, as shown above. In managed runs, ctx.artifacts_dir can be backed by remote storage, so closing a file may wait for its upload. A synchronous write then blocks every rollout on the server and delays result polling.

Saved Trajectories

Rollout servers created with create_rollout_server() save finished samples as ATIF (Agent Trajectory Interchange Format) documents alongside run artifacts. Persistence is best-effort and never changes reward or rollout status. Calling LocalBackend.run_workflow() or another backend method directly in a custom harness does not install this server lifecycle; that harness must save any trajectory it needs. Files land next to the artifacts directory on the platform-managed host:
RolloutSample.messages preserves the framework-native history that graders read. For ATIF persistence, built-in integrations make a separate, best-effort normalized copy in trajectory_messages; normalization failure leaves the native sample intact and skips trajectory persistence. Explicit workflow output uses its returned messages for both views when they can be copied.
If you build a custom sample source whose native history isn’t already OpenAI chat-completions-shaped, set RolloutSample.trajectory_messages on the returned sample to control what gets persisted (an explicit None skips trajectory saving for that sample).
ATIF includes usage, model, and timestamp data only when the source messages or server report provide those fields. Missing metadata stays absent; the SDK does not fabricate it.

Live Trajectory Previews

Live trajectory previews are disabled unless the hosting environment enables preview publication. When enabled, the rollout server samples supported conversations about every three seconds and publishes at most the 100 most recent ATIF steps; size limits can shorten the preview further. Previews are diagnostic only and do not replace the grading sample, affect rewards, or change the saved trajectory.json. Before publication, the SDK redacts the configured LLM API key and recognized credential patterns, including authorization headers and URL credentials. When the host enables previews, these sources provide snapshots automatically: Workflows that only return messages from run() at completion have no live source, so they produce no preview. Bundles built with an older SDK version also produce no preview and keep their final-result behavior. Mini-swe-agent versions that write their trajectory only on exit can’t provide running previews.

Opt Out for a Rollout

To keep a rollout out of live previews, set preview_enabled = False on the active rollout context. Do this at the start of run(), before you construct the agent or session and before the first await:
Setting the flag later stops further snapshots but does not retract a preview the SDK already captured.

Add Previews to a Custom Sample Source

Custom SampleSource implementations publish no preview by default. To opt in, override async get_preview(max_messages) and return a RolloutSample holding a copy of at most max_messages recent messages:
Register an instance from inside run() with set_sample_source() on the active rollout context; each rollout accepts only one source. get_preview() must return a bounded, read-only snapshot. Don’t call get_sample() from it or mutate the history that graders read. Previews from custom sources show unknown step IDs and turn counts.

Model Routing Requirement

LLM calls inside run() must route through the RolloutContext installed by the execution backend. The training cluster uses the rollout-scoped chat-completions URL from this context to serve the current policy, collect traces, and connect the reward to the sample.
Use one of the supported integrations: Do not call litellm, the OpenAI SDK, or another provider SDK directly with a hard-coded policy model from run(). Direct calls bypass the rollout context and are not compatible with training.

Strands Pattern

For Strands, pass ctx.prompt directly as messages and call invoke_async():
Constructing OsmosisStrandsAgent inside run() binds it to the active rollout context and registers the agent as a sample source. See Strands Integration for tool examples, migration steps, and details about OsmosisRolloutModel.

OpenAI Agents Pattern

For OpenAI Agents, construct an OsmosisAgent, attach OpenAI Agents ModelSettings, create one OsmosisMemorySession, and pass that session to Runner.run():
The session is what records the OpenAI Agents SDK conversation for grading. Create it inside run() so it registers with the current RolloutContext. See OpenAI Agents Integration for session behavior, tracing notes, and migration steps.

Custom Configuration

Custom configs extend AgentWorkflowConfig. Define a module-level config instance in your rollout entrypoint and pass it to the backend:
Pass the config instance explicitly to the backend constructor, for example LocalBackend(workflow=SearchWorkflow, workflow_config=search_workflow_config). Eval and training TOML files do not currently set workflow config fields directly. BaseConfig allows extra fields, so simple rollout configs usually do not need additional Pydantic boilerplate.

Tool-Using Workflows

Tool use belongs inside your agent framework, not in the backend. Define tools the way your framework expects, pass them into the Osmosis-wrapped agent, and let the integration record the resulting messages. For example, a Strands workflow can keep its tool list in config:

Entry Point Wiring

Workflow classes and config objects require explicit wiring. Select them in the backend constructor and expose that backend through the rollout server:
Multiple concrete AgentWorkflow subclasses can exist in the same module; only the class passed as workflow runs. Submit preflight imports the entrypoint once to surface import-time errors and does not inspect the module namespace; see Files in a Rollout for when that import is skipped and what it executes locally.

Next Steps

Strands Integration

Build a Strands-based rollout with tools and OsmosisStrandsAgent.

OpenAI Agents Integration

Build an OpenAI Agents SDK rollout with OsmosisAgent and OsmosisMemorySession.

Grader

Define reward logic for the sample your workflow produces.

Evaluation

Submit an evaluation run to test your workflow and grader before a training run.
Last modified on October 22, 2026