Roast Documentation

repository·main·Indexed 22 days ago

https://github.com/shopify/roast

Roast is a Ruby-based domain-specific language (DSL) for creating structured AI workflows by chaining modular 'cogs'. It provides specialized building blocks such as `chat` for cloud-based LLMs (OpenAI, Anthropic, Perplexity, Gemini), `agent` for local coding agents (Pi CLI, Claude Code CLI), `cmd` for shell commands, and `ruby` for custom logic. The library allows for declarative workflow definitions, session management for multi-turn conversations, and flexible input handling via targets and arguments.

Tokens
26.5K
Snippets
89
Records
120
Agent score
78%

What's inside Roast

  1. Core Roast concepts and workflow capabilities

    main

    Roast is a Ruby-based DSL designed to orchestrate complex AI-powered automation. Key capabilities include:

    • Chaining Cogs: Building multi-step workflows where the output of one cog (step) is passed as input to the next using the ! suffix.
    • Cogs: The building blocks of workflows. Common types include:
      • chat: For conversational LLM interactions.
      • agent: For running coding agents.
      • ruby: For executing custom Ruby logic.
      • map: For processing collections.
      • repeat: For iterative workflows.
      • call: For invoking reusable scopes.
    • Targets and Parameters: Workflows can accept single targets (target!), multiple targets (targets), and custom arguments (args, kwargs) from the command line.
    • Control Flow: Workflows can adapt dynamically using skip!, fail!, break!, and next!, or check if a cog ran using the ? accessor.
    • Scopes: Modular, reusable blocks of logic defined via execute scopes that can be called multiple times with different inputs.
    • Concurrency: Support for parallel execution via the map cog and asynchronous execution via the async! configuration.
  2. Configure the `outputs` block in iterative scopes

    main

    The outputs block in an execute scope used by repeat defines the value passed to the next iteration.

    Important Behavior: The outputs block is guaranteed to run, even if the iteration was terminated via break! or next!. This ensures that the final state of the loop is captured and available via repeat!(:name).value.

    execute(:accumulate) do
      ruby(:add) do |_, sum, index|
        sum + index
      end
    
      ruby { |_, _, index| break! if index >= 5 }
    
      outputs { ruby!(:add).value }
    end
    
    execute do
      repeat(run: :accumulate) { 0 }
    end
  3. Core Cogs in Roast

    main

    Roast workflows are composed of several specialized cogs:

    • chat: Sends prompts to cloud-based LLMs (OpenAI, Anthropic, Perplexity, or Gemini).
    • agent: Runs local coding agents with filesystem access (e.g., Pi CLI, Claude Code CLI).
    • ruby: Executes custom Ruby code within the workflow.
    • cmd: Runs shell commands and captures their output.
    • map: Processes collections in serial or parallel.
    • repeat: Iterates until specific conditions are met.
    • call: Invokes reusable workflow scopes.
  4. How call, map, and repeat cogs invoke execution scopes

    main

    The call, map, and repeat cogs follow a common pattern of invoking named execution scopes defined with execute(:name). The difference between them lies in how many times they invoke that scope:

    • call: Invokes a named scope once with a single input value. Use this for reusable workflow segments that only need to run once.
    • map: Invokes a named scope multiple times, once for each item in a collection. Each invocation receives one item from the iterable. Use this for processing collections where every item requires the same treatment.
    • repeat: Invokes a named scope multiple times in a loop. Each iteration's output becomes the input for the next iteration. The loop continues until break! is called. Use this for iterative refinement or loops with dynamic exit conditions.
  5. What are Async Cogs and when to use them

    main

    By default, cogs in Roast run synchronously, meaning each cog must complete before the next one starts. Async cogs allow you to kick off long-running tasks in the background and continue with other work immediately within the same execution scope.

    When to use Async Cogs

    • Running multiple independent agent tasks that do not depend on each other.
    • Starting a long-running background task while performing other work.
    • Parallelizing unrelated API calls or file operations.

    When NOT to use Async Cogs

    • If cogs are fast enough to run sequentially.
    • If cogs depend on each other's outputs (accessing the output will cause the execution to block until the dependency is met anyway).
    • If you are processing a collection of items (use map with parallel! instead).

    Async Cogs vs. Parallel Map

    • Async cogs: Run different, independent tasks concurrently in the same scope (e.g., analyze_code + draft_email).
    • Parallel map: Applies the same operation to multiple items concurrently, each in its own scope (e.g., processing 10 files in parallel).
  6. Check if a cog ran using ? and !

    main

    Because steps in a workflow might be skipped, you need ways to safely or strictly access their outputs.

    Safe Access with ? suffix

    Use cog?(:name) to check if a cog actually executed. This prevents errors if a step was conditionally skipped.

    if chat?(:optional_step)
      result = chat(:optional_step).response
    else
      result = "No analysis available"
    end

    Strict Access with ! suffix

    Use cog!(:name) as a shorthand when you expect the cog to have run. If the cog did not run (e.g., it was skipped or failed), this will raise an error immediately instead of returning nil.

    # Raises error if :analyze didn't run
    analysis = chat!(:analyze).response
    execute do
      chat(:analyze) { "Analyze this: #{data}" }
    
      chat(:summarize) do
        analysis = chat!(:analyze).response
        "Summarize this analysis: #{analysis}"
      end
    end
  7. Use the `repeat` cog for iterative workflows

    main

    The repeat cog executes a named scope repeatedly. Unlike map, which processes a fixed collection, repeat continues until a condition is met or a maximum iteration limit is reached. Each iteration's output becomes the input for the next iteration.

    Each iteration receives two parameters:

    1. The value from the previous iteration (or the initial value for the first iteration).
    2. The index (starting at 0, or a custom initial_index).

    To use repeat, define an execute scope and then call repeat(:name, run: :scope_name) with an initial value block.

    execute(:process) do
      ruby(:step_increment) do |_, value, index|
        new_value = value + index
        new_value
      end
    
      ruby { break! if ruby!(:step_increment).value >= 12 }
    
      outputs { ruby!(:step_increment).value }
    end
    
    execute do
      repeat(:loop, run: :process) { 0 }
    
      ruby do
        puts "Final value: #{repeat!(:loop).value}"
      end
    end
  8. Understand Roast configuration method naming conventions

    main

    Roast uses specific suffixes to distinguish between different types of configuration methods. Understanding these helps you identify whether a method sets a value, toggles a state, or validates a setting.

    Bang Methods (!)

    Used in two specific contexts:

    1. No-argument state setters: Methods that set a configuration to a specific state without requiring an argument (e.g., show_stdout!, no_display!).
    2. Validation getters: Internal methods (prefixed with valid_*!) that retrieve and validate values, potentially raising errors (e.g., valid_provider!).

    Value Setters

    Methods that accept a parameter to set a specific value do not use a bang suffix (e.g., provider(value), model(value)).

    Predicate Methods (?)

    Methods ending in ? are used to check the current configuration state (e.g., show_stdout?, apply_permissions?).

  9. Use the `map` cog to process collections

    main

    The map cog applies a named execute scope to every item in a collection. To use it, you must first define a scope using execute(:name) and then invoke map referencing that name via the run option.

    The scope receives the current item as its value parameter. If you need the iteration index, it is provided as the third parameter to the scope's internal cogs.

    execute(:process_item) do
      chat(:analyze) do |_, item|
        "Analyze this item: #{item}"
      end
    end
    
    execute do
      map(run: :process_item) { ["item1", "item2", "item3"] }
    end
  10. Understand the difference between User-Facing and Developer-Facing documentation

    main

    Roast distinguishes between two types of documentation based on the intended audience and where the information appears in the developer workflow.

    User-Facing Documentation (External)

    Target: Users interacting with workflows directly. Usage: This documentation appears in interfaces users write in their workflow files. It must be thorough and not assume internal knowledge. Key locations:

    • All Config classes (e.g., Agent::Config, Chat::Config, Cmd::Config)
    • All Input classes (e.g., Agent::Input, Chat::Input)
    • All Output classes (e.g., Agent::Output, Chat::Output)
    • .rbi shims in sorbot/rbi/shims/lib/roast/ (These are critical as they provide IDE autocomplete/documentation for methods like agent!, chat!, config, and from).

    Developer-Facing Documentation (Internal)

    Target: Roast core contributors. Usage: This documentation is for internal implementation code. It can be more concise as the code is immediately visible. Key locations:

    • Cog classes (e.g., Agent, Chat, Cmd)
    • Params classes for system cogs (e.g., SystemCog::Params)
    • Standard cog methods like execute and initialize
    • Internal helper methods and utilities
  11. Use the agent cog for local environment access

    main

    The agent cog is designed for tasks requiring access to your local filesystem, shell, or development environment. It is backed by a local coding agent (Pi is the default; Claude Code is also supported).

    Use agent when you need to:

    • Read or write local files
    • Search through code
    • Run shell commands
    • Interact with your development environment

    Use chat when you need to:

    • Process data already in memory
    • Perform reasoning without file access
    • Generate text or analysis from provided context
    • Use less expensive/faster models for simple tasks
    execute do
      agent(:code_review) do
        <<~PROMPT
          Read the Ruby files in the src/ directory and identify
          any potential security issues. Focus on input validation
          and data sanitization.
        PROMPT
      end
    end
  12. Difference between Agent Cog and Chat Cog

    main

    Both agent and chat cogs are LLM-powered and capable of complex reasoning. The distinction is based on their execution environment and access to local resources:

    Agent Cog

    Designed for coding tasks and workflows requiring local system interaction.

    • Local filesystem access: Can read and write files on the local machine.
    • Local tool execution: Can run tools and commands locally.
    • Local MCP servers: Access to user's locally configured MCP servers.
    • Session management: Supports automatic session resumption across invocations.

    Chat Cog

    Designed for pure LLM reasoning tasks that do not require local system interaction.

    • No local filesystem access: Cannot read or write local files.
    • No local tool execution: Cannot run tools or commands on the local machine.
    • Cloud-based capabilities: Can access cloud-based tools and MCP servers provided by the LLM provider.
    • Session management: Does not currently provide automatic conversation resume or memory capability.