Recipes

June 23, 2026 · View on GitHub

Ready-to-Run Example Agents for Agentic RL

← Back to Main README · Overview · ALFWorld · HotpotQA · Build Your Own · Training Scripts

📖 Overview

Dressage includes example recipes — complete whitebox agent implementations that demonstrate how to build, train, and evaluate agents using the framework. Each recipe is a self-contained agent with its own tools, reward functions, and training scripts. They serve as both working examples and starting points for your own agents.

Recipes are designed to showcase the framework's key capabilities:

  • Whitebox agent pattern — subclass WhiteboxAgent, implement rollout(), export via make_generate
  • Custom tools — define tools as Python functions, register them with the agent
  • Reward functions — implement sample-oriented reward functions, wire via the registry
  • Training scripts — complete bash scripts that wire all slime hooks together
dressage/recipes/
├── alfworld/        # TextWorld navigation agent
│   ├── agent_whitebox.py  # ALFWorld WhiteboxAgent implementation
│   ├── tools.py           # Prompt, parsing, and TextWorld helpers
│   └── reward.py          # Task completion reward function
└── hotpotqa/        # Multi-hop retrieval agent
    ├── agent_whitebox.py  # HotpotQA WhiteboxAgent implementation
    ├── tools.py           # Prompt, parsing, and local search helpers
    └── reward.py          # Exact-match answer reward

🏠 ALFWorld

TextWorld Navigation Agent for Household Tasks

ALFWorld is a text-based interactive environment for household task completion, built on the TextWorld engine. The Dressage ALFWorld recipe implements a whitebox agent that navigates TextWorld environments using an env_step tool — sending text commands and receiving environment observations.

Task Description

The agent receives natural language instructions for household tasks and must navigate a simulated home environment using text commands:

Task: "Put a clean cup on the counter"

Agent thinks: I need to find a cup, clean it, and put it on the counter.

Agent actions:
  1. "go to kitchen"       → You see a kitchen with cabinets and a counter.
  2. "open cabinet 1"      → You see a dirty cup.
  3. "take cup from cabinet 1" → You pick up the cup.
  4. "go to sink"          → You see a sink.
  5. "clean cup with sink" → You clean the cup.
  6. "go to counter"       → You see a counter.
  7. "put cup on counter"  → ✅ Task complete!

How It Works

The ALFWorld agent follows a simple but effective loop: ask the LLM for the next action, execute it in the environment, feed the observation back, repeat.

ALFWorld Agent (WhiteboxAgent subclass)

        ├── self.chat(messages)           → LLM generates next action
        │   └── System prompt describes available commands
        │   └── User messages include task + observation history

        ├── env_step(action)              → Execute in TextWorld
        │   └── Agent sends text command to environment
        │   └── Environment returns observation + done signal
        │   └── Observation appended to conversation as tool result

        └── Loop until:
            ├── Task complete (environment signals done)
            ├── Max steps reached
            └── Error occurs

Architecture Details

ComponentImplementationDescription
AgentWhiteboxAgent subclassMulti-turn agent with TextWorld interaction loop. Manages conversation history, parses LLM actions, handles tool responses.
Toolenv_step functionSends text commands to the ALFWorld environment. Returns environment observation and done signal. Commands include navigation (go to X), interaction (take, put, open, close), and cleaning (clean X with Y).
RewardTask completion binaryReturns 1.0 on successful task completion, 0.0 otherwise. The environment provides the completion signal. Simple but effective for RL training.
EvaluationSuccess rateMeasured as success rate across ALFWorld's six task categories (pick, clean, heat, cool, examine, pick two).

Running ALFWorld

# Sync rollout — simpler, good for debugging
bash examples/scripts/run_alfworld_whitebox_agent_qwen3.5_4b.sh

# Async rollout — better GPU utilization for training
bash examples/scripts/run_alfworld_whitebox_agent_qwen3.5_4b_async.sh

Key Configuration

The ALFWorld scripts set these Dressage-specific variables:

DRESSAGE_PADDOCK_MODE=whitebox
DRESSAGE_SANDBOX_PROVIDER=local_bwrap
DRESSAGE_LOCAL_BWRAP_POOL_MODE=command_only

# Generate function points to the ALFWorld agent
--custom-generate-function-path dressage.recipes.alfworld.agent_whitebox.generate
--custom-rm-path dressage.reward.custom_rm.custom_rm
DRESSAGE_REWARD_MODULES=dressage.recipes.alfworld.reward

# Environment limits
ALFWORLD_MAX_STEPS=30
ALFWORLD_MAX_EPISODE_STEPS=30

🔍 HotpotQA

Multi-Hop Retrieval Agent for Complex Question Answering

HotpotQA is a question answering dataset requiring multi-hop reasoning over multiple Wikipedia paragraphs. The Dressage HotpotQA recipe implements a whitebox agent with a local FAISS+BGE retrieval tool — the agent searches for relevant passages, reasons over them, and produces an answer.

Task Description

The agent answers complex questions that require finding and combining information from multiple documents:

Question: "What is the birthplace of the director of Inception?"

Agent reasoning:
  1. Search "director of Inception"
     → "Inception is a 2010 film directed by Christopher Nolan"
  2. Search "Christopher Nolan birthplace"
     → "Christopher Nolan was born in London, England"
  3. Answer: "London, England"  ✅

How It Works

The HotpotQA agent uses a retrieve-reason-retrieve loop: search for relevant information, reason about what's needed next, search again if necessary, then produce a final answer.

HotpotQA Agent (WhiteboxAgent subclass)

        ├── self.chat(messages)               → LLM generates query or answer
        │   └── System prompt describes retrieval tool
        │   └── User messages include question + retrieved passages

        ├── retrieval_tool(query)             → Search FAISS index
        │   ├── Encode query using BGE embeddings (sentence-transformers)
        │   ├── Search FAISS index for top-k nearest neighbors
        │   └── Return top-k passage texts with scores

        └── Loop: retrieve → reason → retrieve → answer
            ├── Agent decides whether to search or answer
            ├── Multiple retrieval rounds for multi-hop questions
            └── Final answer extracted from last assistant response

Architecture Details

ComponentImplementationDescription
AgentWhiteboxAgent subclassMulti-hop reasoning agent. Decides when to retrieve vs. answer. Manages retrieval context in conversation.
RetrievalFAISS + BGELocal FAISS index from HOTPOTQA_CORPUS_DIR/index.bin plus passages from HOTPOTQA_CORPUS_DIR/hpqa_corpus.jsonl, using the configured BGE embedding model.
RewardExact MatchChecks the last <answer>...</answer> block against gold answers after normalization. No F1, format bonus, or partial credit.
EvaluationExact-match reward1.0 only when the normalized answer exactly matches a normalized target; otherwise 0.0.

Additional Dependencies

The HotpotQA recipe requires additional packages for the retrieval component:

pip install faiss-cpu sentence-transformers numpy

Retrieval Defaults

HOTPOTQA_CORPUS_DIR=/path/to/hotpotqa/corpus  # contains index.bin and hpqa_corpus.jsonl
HOTPOTQA_EMBEDDING_DEVICE=cpu
HOTPOTQA_TOPK=5

Running HotpotQA

# Sync rollout — simpler, good for debugging
bash examples/scripts/run_hotpotqa_whitebox_agent_qwen3.5_4b.sh

# Async rollout — better GPU utilization for training
bash examples/scripts/run_hotpotqa_whitebox_agent_qwen3.5_4b_async.sh

Key Configuration

DRESSAGE_PADDOCK_MODE=whitebox

# Generate function points to the HotpotQA agent
--custom-generate-function-path dressage.recipes.hotpotqa.agent_whitebox.generate
--custom-rm-path dressage.reward.custom_rm.custom_rm
DRESSAGE_REWARD_MODULES=dressage.recipes.hotpotqa.reward

🔮 Building Your Own Recipe

Creating a custom agent is straightforward: subclass WhiteboxAgent, implement rollout(), and export a generate function. Here's a complete guide.

Step 1: Create Your Agent

from dressage.rollout.generate.whitebox_agent import WhiteboxAgent, make_generate

class MyAgent(WhiteboxAgent):
    name = "my_agent"

    async def rollout(self, sample, sampling_params) -> str:
        """Run one complete agent trajectory.

        Args:
            sample: The prompt sample with task description
            sampling_params: LLM sampling configuration

        Returns:
            str: The agent's final answer or output
        """
        messages = [
            {"role": "system", "content": "You are a helpful assistant with access to tools."},
            {"role": "user", "content": sample.prompt},
        ]

        response = await self.chat({
            "messages": messages,
            "model": "qwen3.5-4b",
            "tools": [
                {
                    "type": "function",
                    "function": {
                        "name": "search",
                        "description": "Search for information",
                        "parameters": {
                            "type": "object",
                            "properties": {"query": {"type": "string"}},
                            "required": ["query"],
                        },
                    },
                }
            ],
        })

        # Multi-turn tool use loop
        while has_tool_calls(response):
            tool_result = execute_my_tool(response)
            messages.append({"role": "tool", "content": tool_result})
            response = await self.chat({
                "messages": messages,
                "model": "qwen3.5-4b",
            })

        return extract_final_answer(response)

# Export the generate function — this is what slime calls
generate = make_generate(MyAgent)

Step 2 (Optional): Add Sandbox Access

If your agent needs to execute code, read/write files, or run shell commands in an isolated environment, use PaddockWhiteboxAgent:

from dressage.rollout.generate.whitebox_agent import PaddockWhiteboxAgent, make_generate

class MySandboxAgent(PaddockWhiteboxAgent):
    name = "my_sandbox_agent"

    async def rollout(self, sample, sampling_params) -> str:
        response = await self.chat({
            "messages": [{"role": "user", "content": sample.prompt}],
            "model": "qwen3.5-4b",
        })

        # Execute code in an isolated sandbox
        shell_text, shell_metadata = await self.paddock.tool_call(
            self.session_id, "shell.exec", {"cmd": "python solve.py"}
        )

        # Read output files from sandbox
        output, output_metadata = await self.paddock.tool_call(
            self.session_id, "file.read", {"path": "/workspace/result.txt"}
        )

        return output

generate = make_generate(MySandboxAgent)

Step 3: Define Your Reward Function

from dressage.reward.registry import register_reward

@register_reward("my_reward")
def my_reward(sample, *, args=None):
    """Compute reward for a completed trajectory.

    The sample contains the full trajectory data including
    the agent's final output, all tool interactions, and metadata.
    """
    metadata = sample.metadata or {}
    agent_output = sample.response or ""
    gold_answer = sample.label or metadata.get("gold_answer", "")
    proxy_extra = metadata.get("proxy_extra_info", {})

    # Your reward logic here
    if is_correct(agent_output, gold_answer):
        return 1.0
    elif is_partially_correct(agent_output, gold_answer):
        return 0.5
    else:
        return 0.0

Step 4: Wire Into Slime

python3 -m slime.train \
  --custom-generate-function-path my_package.my_agent.generate \
  --rollout-function-path \
    dressage.rollout.sync_rollout.generate_rollout_sync \
  --custom-rm-path dressage.reward.custom_rm.custom_rm \
  --custom-convert-samples-to-train-data-path \
    dressage.rollout.convert_samples.convert_samples_to_train_data \
  --custom-reward-post-process-path \
    dressage.training.reward_post_process.reward_post_process \
  --data-source-path \
    dressage.rollout.data_source.DressageDataSource \
  --advantage-estimator grpo
export DRESSAGE_REWARD_MODULES=my_package.reward

Custom datasets select the reward through sample metadata:

{"prompt": "Solve the task", "label": "expected answer", "metadata": {"instance_id": "custom-001", "reward_fn": "my_reward"}}

Tip

Check out the ALFWorld and HotpotQA implementations in dressage/recipes/ for complete working examples. They demonstrate the full pattern including edge case handling, error recovery, and training script configuration.

Recipe Checklist

When building a new recipe, make sure you have:

  • Agent class extending WhiteboxAgent or PaddockWhiteboxAgent
  • Tool implementations (Python functions or paddock tool_call)
  • Reward function registered via @register_reward
  • Training script with all slime hook paths
  • Prompt dataset in JSONL format
  • Basic tests for agent logic and reward computation

📊 Training Scripts

All example training scripts are in examples/scripts/. They demonstrate different combinations of agent modes, sandbox providers, and async scheduling:

Recipe Scripts

ScriptAgentModeDescription
run_alfworld_whitebox_agent_qwen3.5_4b.shALFWorldSyncWhitebox, synchronous rollout
run_alfworld_whitebox_agent_qwen3.5_4b_async.shALFWorldAsyncWhitebox, fully async rollout
run_hotpotqa_whitebox_agent_qwen3.5_4b.shHotpotQASyncWhitebox, synchronous rollout
run_hotpotqa_whitebox_agent_qwen3.5_4b_async.shHotpotQAAsyncWhitebox, fully async rollout

Blackbox Scripts

ScriptModelSandboxMode
run_blackbox_qwen3.5_4b_async_local.shQwen3.5-4BLocal bwrapFully async
run_blackbox_qwen3.5_4b_async_remote.shQwen3.5-4BE2B remoteFully async
run_blackbox_qwen3.5_4b_partial_rollout_async_local.shQwen3.5-4BLocal bwrapPartial async
run_blackbox_qwen3.5_4b_partial_rollout_async_remote.shQwen3.5-4BE2B remotePartial async
run_blackbox_qwen3.5_35b_a3b_sync_local.shQwen3.5-35B-A3BLocal bwrapSync
run_blackbox_qwen3.5_35b_a3b_sync_remote.shQwen3.5-35B-A3BE2B remoteSync

📂 Sample Data

Prompt datasets for training runs are in examples/data/:

FileDescriptionFormat
dressage_dapo_prompts.jsonlDAPO-style coding prompts with SWE-bench-like task descriptionsJSONL with prompt, metadata fields

← Training · Back to Main README · Next: Quick Start →