> ## Documentation Index
> Fetch the complete documentation index at: https://docs.osmosis.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Strands Integration

> Use the Strands agent framework with Osmosis for training

[Strands Agents](https://github.com/strands-agents/sdk-python) is an AWS agent framework for building tool-using agents. Use the Osmosis Strands integration when you want Strands tools, Strands message handling, and a direct migration path from an existing `strands.Agent`.

The `osmosis-ai` package includes `strands-agents[litellm]`, so starter rollouts can use Strands without adding another dependency.

## Integration Objects

| Object                | Purpose                                                                                        |
| --------------------- | ---------------------------------------------------------------------------------------------- |
| `OsmosisStrandsAgent` | Drop-in replacement for Strands `Agent` that registers samples with the active rollout context |
| `OsmosisRolloutModel` | Placeholder model that resolves to the current Osmosis policy at runtime                       |

`OsmosisStrandsAgent` preserves normal Strands constructor arguments such as `tools`, `system_prompt`, `messages`, and callback handlers.

## Quick Example

```python theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/cli.json"]}}
from osmosis_ai.rollout import AgentWorkflow, AgentWorkflowContext
from osmosis_ai.rollout.integrations.agents.strands import (
    OsmosisRolloutModel,
    OsmosisStrandsAgent,
)


class StrandsWorkflow(AgentWorkflow):
    async def run(self, ctx: AgentWorkflowContext) -> None:
        agent = OsmosisStrandsAgent(
            name="assistant",
            model=OsmosisRolloutModel(params={"temperature": 1.0}),
            messages=ctx.prompt,
            callback_handler=None,
        )
        await agent.invoke_async()
```

<Note>
  `ctx.prompt` is already the ready-to-use input for the current sample. If your dataset row contains `system_prompt` and `user_prompt`, the SDK assembles those fields before your workflow runs.
</Note>

## Tools

Define Strands tools normally with `@tool`, then pass them to `OsmosisStrandsAgent`:

```python theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/cli.json"]}}
from strands import tool
from osmosis_ai.rollout import AgentWorkflow, AgentWorkflowContext
from osmosis_ai.rollout.integrations.agents.strands import (
    OsmosisRolloutModel,
    OsmosisStrandsAgent,
)


@tool
def search(query: str) -> str:
    """Search for information."""
    return f"Results for: {query}"


class SearchWorkflow(AgentWorkflow):
    async def run(self, ctx: AgentWorkflowContext) -> None:
        agent = OsmosisStrandsAgent(
            name="search-agent",
            model=OsmosisRolloutModel(params={"temperature": 1.0}),
            tools=[search],
            system_prompt="You are a helpful assistant.",
            messages=ctx.prompt,
            callback_handler=None,
        )
        await agent.invoke_async()
```

For most tool-using agents, one `invoke_async()` call is enough because Strands handles the model-tool loop internally. Add an outer loop only when you need extra stopping conditions or a hard cap across repeated invocations.

## OsmosisRolloutModel

`OsmosisRolloutModel` does not take a `model_id`. The SDK uses the placeholder model id `openai/osmosis-rollout` at runtime, and Osmosis routes it to the current policy.

Use the `params` dict for sampling options:

```python theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/cli.json"]}}
model = OsmosisRolloutModel(
    params={
        "temperature": 1.0,
        "max_tokens": 1024,
    }
)
```

<Warning>
  Construct `OsmosisStrandsAgent` inside `AgentWorkflow.run()` or another path where the execution backend has already installed an active `RolloutContext`. Constructing it at module import time will fail because no rollout context exists yet.
</Warning>

## How Sample Collection Works

When constructed with an `OsmosisRolloutModel`, `OsmosisStrandsAgent` performs these steps:

<Steps>
  <Step title="Read the rollout context">
    It reads the active `RolloutContext` from the current execution scope and raises `RuntimeError` if none is available.
  </Step>

  <Step title="Choose a sample ID">
    It derives a sample ID from the agent's `name`, `agent_id`, or a generated UUID.
  </Step>

  <Step title="Resolve the model">
    It calls `model.for_sample(sample_id, rollout_ctx)` to create a real LiteLLM model connected to the Osmosis chat completions endpoint.
  </Step>

  <Step title="Register the agent">
    It registers itself with the rollout context so the backend can collect the Strands message history as a `RolloutSample`.
  </Step>

  <Step title="Initialize Strands Agent">
    It delegates to the normal Strands `Agent` constructor with the resolved model. Tools, prompts, messages, and callbacks pass through unchanged.
  </Step>
</Steps>

## Complete Example

```python theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/cli.json"]}}
from strands import tool
from osmosis_ai.rollout import (
    AgentWorkflow,
    AgentWorkflowContext,
    Grader,
    GraderContext,
)
from osmosis_ai.rollout.integrations.agents.strands import (
    OsmosisRolloutModel,
    OsmosisStrandsAgent,
)


@tool
def calculator(expression: str) -> str:
    """Evaluate a math expression."""
    return str(eval(expression))


class MathWorkflow(AgentWorkflow):
    async def run(self, ctx: AgentWorkflowContext) -> None:
        agent = OsmosisStrandsAgent(
            name="math-agent",
            model=OsmosisRolloutModel(params={"temperature": 1.0}),
            tools=[calculator],
            system_prompt="You are a math assistant. Use the calculator tool.",
            messages=ctx.prompt,
            callback_handler=None,
        )
        await agent.invoke_async()


def _last_text(sample) -> str:
    if not sample.messages:
        return ""
    content = sample.messages[-1].get("content", "")
    if isinstance(content, str):
        return content
    if isinstance(content, list):
        return next((b["text"] for b in content if isinstance(b, dict) and "text" in b), "")
    return ""


class MathGrader(Grader):
    async def grade(self, ctx: GraderContext) -> None:
        for sample_id, sample in ctx.samples.items():
            answer = _last_text(sample)
            reward = 1.0 if ctx.label and ctx.label.strip() in answer else 0.0
            ctx.set_sample_reward(sample_id, reward)
```

## Migrating from Strands Agent

If you already have a Strands agent, migrate it in four steps:

<Steps>
  <Step title="Replace the agent import">
    Replace `from strands import Agent` with:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/cli.json"]}}
    from osmosis_ai.rollout.integrations.agents.strands import OsmosisStrandsAgent
    ```
  </Step>

  <Step title="Replace the model">
    Replace your fixed LiteLLM model with an Osmosis placeholder:

    ```python theme={"theme":{"light":"github-light","dark":"github-dark"},"languages":{"custom":["/languages/cli.json"]}}
    from osmosis_ai.rollout.integrations.agents.strands import OsmosisRolloutModel

    model = OsmosisRolloutModel(params={"temperature": 1.0})
    ```

    Drop the `model_id`; the training cluster decides which policy to serve.
  </Step>

  <Step title="Swap the agent class">
    Replace `Agent(...)` with `OsmosisStrandsAgent(...)`. Keep tools, system prompt, messages, and callbacks the same.
  </Step>

  <Step title="Wrap in AgentWorkflow">
    Move the agent construction and `await agent.invoke_async()` call into `AgentWorkflow.run()`.
  </Step>
</Steps>

## Strands vs OpenAI Agents

| Choose Strands when                                  | Choose OpenAI Agents when                                                 |
| ---------------------------------------------------- | ------------------------------------------------------------------------- |
| Your tools are already Strands `@tool` functions     | Your workflow already uses `Runner.run` and OpenAI Agents SDK sessions    |
| You want Strands message traces in `sample.messages` | You want persisted Responses API-style session items in `sample.messages` |
| You are migrating from `strands.Agent`               | You are migrating from OpenAI Agents SDK `Agent`                          |

See [OpenAI Agents Integration](/cli/rollout/openai-agents-integration) for the OpenAI Agents SDK path.

## Next Steps

<CardGroup cols={2}>
  <Card title="Building AgentWorkflows" icon="robot" href="/cli/rollout/agent-workflows">
    Review the shared workflow contract.
  </Card>

  <Card title="Building Graders" icon="scale-balanced" href="/cli/rollout/graders">
    Score the samples produced by your Strands agent.
  </Card>

  <Card title="Evaluation" icon="flask-vial" href="/cli/rollout/eval">
    Submit an evaluation run for your Strands rollout.
  </Card>

  <Card title="OpenAI Agents Integration" icon="route" href="/cli/rollout/openai-agents-integration">
    Compare the OpenAI Agents SDK integration.
  </Card>
</CardGroup>
