From 80996648e98bbc727be97819acc9bc717eb1c015 Mon Sep 17 00:00:00 2001 From: Lasmar Khalifa Date: Tue, 7 Jul 2026 13:02:26 -0400 Subject: [PATCH] Add workflow parameter access in config blocks --- README.md | 1 + examples/targets_and_params.rb | 12 + lib/roast/config_manager.rb | 73 +++++- lib/roast/workflow.rb | 2 +- sorbet/rbi/shims/lib/roast/cog/config.rbi | 251 ++++++++++++++++++++ test/roast/config_manager_test.rb | 191 ++++++++++++++- test/roast/execution_manager_test.rb | 8 +- test/roast/system_cogs/call_test.rb | 6 +- test/roast/system_cogs/map_test.rb | 8 +- test/roast/system_cogs/repeat_test.rb | 6 +- tutorial/03_targets_and_params/README.md | 11 +- tutorial/04_configuration_options/README.md | 19 ++ 12 files changed, 559 insertions(+), 29 deletions(-) create mode 100644 sorbet/rbi/shims/lib/roast/cog/config.rbi diff --git a/README.md b/README.md index be8a3dc0..077c9258 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,7 @@ class and method comments on the relevant classes. * [Additional Example Workflows]([https://github.com/Shopify/roast/tree/main/examples](https://github.com/Shopify/roast/tree/main/examples)) (these comprise the Roast end-to-end test suite) * __Configuation__ * [General configuration block: `config-context.rbi`](https://github.com/Shopify/roast/blob/main/sorbet/rbi/shims/lib/roast/config_context.rbi) + * [Workflow params in cog config blocks: `cog/config.rbi`](https://github.com/Shopify/roast/blob/main/sorbet/rbi/shims/lib/roast/cog/config.rbi) * [Global cog configuration: `cog/config.rb`](https://github.com/Shopify/roast/blob/main/lib/roast/cog/config.rb) * [Agent cog configuration: `agent/config.rb`](https://github.com/Shopify/roast/blob/main/lib/roast/cogs/agent/config.rb) * [Chat cog configuration: `chat/config.rb`](https://github.com/Shopify/roast/blob/main/lib/roast/cogs/chat/config.rb) diff --git a/examples/targets_and_params.rb b/examples/targets_and_params.rb index 127bd377..371eaace 100644 --- a/examples/targets_and_params.rb +++ b/examples/targets_and_params.rb @@ -4,6 +4,18 @@ #: self as Roast::Workflow config do + # Workflow params are available inside cog config blocks and the `global` block, so you can + # configure cogs based on how the workflow was invoked. + chat do + # e.g., pick a cheaper model when the workflow is invoked with the `fast` flag: + # roast execute examples/targets_and_params.rb -- fast + model(arg?(:fast) ? "gpt-5.4-nano" : "gpt-5") + end + + # `global` config applies to every cog; the same param accessors work here too. + global do + abort_on_failure! if arg?(:strict) + end end execute do diff --git a/lib/roast/config_manager.rb b/lib/roast/config_manager.rb index 2749dd94..ae60c16d 100644 --- a/lib/roast/config_manager.rb +++ b/lib/roast/config_manager.rb @@ -8,10 +8,11 @@ class ConfigManagerNotPreparedError < ConfigManagerError; end class ConfigManagerAlreadyPreparedError < ConfigManagerError; end class IllegalCogNameError < ConfigManagerError; end - #: (Cog::Registry, Array[^() -> void]) -> void - def initialize(cog_registry, config_procs) + #: (Cog::Registry, Array[^() -> void], WorkflowContext) -> void + def initialize(cog_registry, config_procs, workflow_context) @cog_registry = cog_registry @config_procs = config_procs + @workflow_context = workflow_context @config_context = ConfigContext.new #: ConfigContext @global_config = Cog::Config.new #: Cog::Config @general_configs = {} #: Hash[singleton(Cog), Cog::Config] @@ -117,6 +118,7 @@ def on_config(cog_class, cog_name_or_pattern, cog_config_proc) # NOTE: Sorbet expects the proc passed to instance_exec to be declared as taking an argument # but our cog_config_proc does not get an argument cog_config_proc = cog_config_proc #: as ^(untyped) -> void + bind_workflow_params(config_object) config_object.instance_exec(&cog_config_proc) if cog_config_proc nil end @@ -134,8 +136,75 @@ def bind_global #: (^() -> void ) -> void def on_global(global_config_proc) global_config_proc = global_config_proc #: as ^(untyped) -> void + bind_workflow_params(@global_config) @global_config.instance_exec(&global_config_proc) if global_config_proc nil end + + #: (Cog::Config) -> void + def bind_workflow_params(object) + target_bang_method = method(:target!) + targets_method = method(:targets) + arg_question_method = method(:arg?) + args_method = method(:args) + kwarg_method = method(:kwarg) + kwarg_bang_method = method(:kwarg!) + kwarg_question_method = method(:kwarg?) + kwargs_method = method(:kwargs) + object.instance_eval do + define_singleton_method(:target!, proc { target_bang_method.call }) + define_singleton_method(:targets, proc { targets_method.call }) + define_singleton_method(:arg?, proc { |value| arg_question_method.call(value) }) + define_singleton_method(:args, proc { args_method.call }) + define_singleton_method(:kwarg, proc { |key| kwarg_method.call(key) }) + define_singleton_method(:kwarg!, proc { |key| kwarg_bang_method.call(key) }) + define_singleton_method(:kwarg?, proc { |key| kwarg_question_method.call(key) }) + define_singleton_method(:kwargs, proc { kwargs_method.call }) + end + end + + #: () -> String + def target! + raise ArgumentError, "expected exactly one target" unless @workflow_context.params.targets.length == 1 + + @workflow_context.params.targets.first #: as String + end + + #: () -> Array[String] + def targets + @workflow_context.params.targets.dup + end + + #: (Symbol) -> bool + def arg?(value) + @workflow_context.params.args.include?(value) + end + + #: () -> Array[Symbol] + def args + @workflow_context.params.args.dup + end + + #: (Symbol) -> String? + def kwarg(key) + @workflow_context.params.kwargs[key] + end + + #: (Symbol) -> String + def kwarg!(key) + raise ArgumentError, "expected keyword argument '#{key}' to be present" unless @workflow_context.params.kwargs.include?(key) + + @workflow_context.params.kwargs[key] #: as String + end + + #: (Symbol) -> bool + def kwarg?(key) + @workflow_context.params.kwargs.include?(key) + end + + #: () -> Hash[Symbol, String] + def kwargs + @workflow_context.params.kwargs.dup + end end end diff --git a/lib/roast/workflow.rb b/lib/roast/workflow.rb index b02eaeae..13a0f495 100644 --- a/lib/roast/workflow.rb +++ b/lib/roast/workflow.rb @@ -48,7 +48,7 @@ def prepare! @preparing = true extract_dsl_procs! - @config_manager = ConfigManager.new(@cog_registry, @config_procs) + @config_manager = ConfigManager.new(@cog_registry, @config_procs, @workflow_context) @config_manager.not_nil!.prepare! # TODO: probably we should just not pass the params as the top-level scope value anymore @execution_manager = ExecutionManager.new(@cog_registry, @config_manager.not_nil!, @execution_procs, @workflow_context, scope_value: @workflow_context.params) diff --git a/sorbet/rbi/shims/lib/roast/cog/config.rbi b/sorbet/rbi/shims/lib/roast/cog/config.rbi new file mode 100644 index 00000000..ddd07f1b --- /dev/null +++ b/sorbet/rbi/shims/lib/roast/cog/config.rbi @@ -0,0 +1,251 @@ +# typed: true +# frozen_string_literal: true + +module Roast + class Cog + class Config + ######################################## + # Workflow Methods + ######################################## + + # Get the single target value passed to the workflow + # + # Returns the target when exactly one target was provided to the workflow. Raises an + # `ArgumentError` if the workflow was invoked with zero or multiple targets. + # + # Targets are file paths, URLs, or other identifiers passed to the workflow when it is + # invoked. Use this method when your workflow expects exactly one target. + # + # ### Invocation + # Invoke a workflow with a single target like this: + # ```bash + # roast execute my_workflow.rb my_target_file.txt + # ``` + # + # ### Usage + # ```ruby + # config do + # chat do + # # Configure based on the single target + # temperature(target!.end_with?(".md") ? 0.7 : 0.2) + # end + # end + # ``` + # + # #### See Also + # - `targets` - Get all targets as an array (works with any number of targets) + # + #: () -> String + def target!; end + + # Get all targets passed to the workflow + # + # Returns an array of all targets provided to the workflow. Works with any number of targets + # (zero, one, or many). Targets are file paths, URLs, or other identifiers passed when the + # workflow is invoked. + # + # ### Invocation + # Invoke a workflow with multiple targets like this: + # ```bash + # roast execute my_workflow.rb target_one.txt target_two.txt + # ``` + # or using shell globs like this: + # ```bash + # roast execute my_workflow.rb target_*.txt + # ``` + # + # ### Usage + # ```ruby + # config do + # map do + # # Process targets in parallel when there are several + # parallel! if targets.length > 3 + # end + # end + # ``` + # + # #### See Also + # - `target!` - Get the single target (raises an error if there isn't exactly one) + # + #: () -> Array[String] + def targets; end + + # Check if a flag argument was passed to the workflow + # + # Returns `true` if the specified flag argument symbol was provided when the workflow + # was invoked, `false` otherwise. + # + # Flag arguments are symbolic flags passed to the workflow (e.g., `retry`, `force`) + # that enable or modify behavior. + # + # ### Invocation + # Invoke a workflow with flag arguments like this: + # ```bash + # roast execute my_workflow.rb [TARGETS] -- retry force + # ``` + # + # ### Usage + # ```ruby + # config do + # chat do + # # Pick a cheaper model when invoked with the `fast` flag + # model(arg?(:fast) ? "gpt-5.4-nano" : "gpt-5") + # end + # end + # ``` + # + # #### See Also + # - `args` - Get all flag arguments as an array + # - `kwarg?` - Check for keyword arguments (key-value pairs) + # + #: (Symbol) -> bool + def arg?(value); end + + # Get all flag arguments passed to the workflow + # + # Returns an array of all flag argument symbols provided when the workflow was invoked. + # Flag arguments are symbolic flags (e.g., `retry`, `force`) that enable or modify + # workflow behavior. + # + # ### Invocation + # Invoke a workflow with flag arguments like this: + # ```bash + # roast execute my_workflow.rb [TARGETS] -- retry force + # ``` + # + # ### Usage + # ```ruby + # config do + # global do + # # Show full output whenever any flags were passed + # display! unless args.empty? + # end + # end + # ``` + # + # #### See Also + # - `arg?` - Check if a specific flag argument was provided + # - `kwargs` - Get all keyword arguments + # + #: () -> Array[Symbol] + def args; end + + # Get a keyword argument value passed to the workflow + # + # Returns the string value for the specified keyword argument key, or `nil` if the key was + # not provided. Keyword arguments are key-value pairs passed to the workflow for configuration. + # + # ### Invocation + # Invoke a workflow with keyword arguments like this: + # ```bash + # roast execute my_workflow.rb [TARGETS] -- name=Samantha project=Roast + # ``` + # + # ### Usage + # ```ruby + # config do + # chat do + # # Use a provided model override, or fall back to a default + # model(kwarg(:model) || "gpt-5") + # end + # end + # ``` + # + # #### See Also + # - `kwarg!` - Get a keyword argument value (raises an error if not provided) + # - `kwarg?` - Check if a keyword argument was provided + # - `kwargs` - Get all keyword arguments as a hash + # + #: (Symbol) -> String? + def kwarg(key); end + + # Get a required keyword argument value passed to the workflow + # + # Returns the string value for the specified keyword argument key. Raises an `ArgumentError` + # if the key was not provided. + # + # Use this when your workflow requires a specific keyword argument to function correctly. + # + # ### Invocation + # Invoke a workflow with keyword arguments like this: + # ```bash + # roast execute my_workflow.rb [TARGETS] -- name=Samantha project=Roast + # ``` + # + # ### Usage + # ```ruby + # config do + # chat do + # # Require the 'model' keyword argument + # model(kwarg!(:model)) + # end + # end + # ``` + # + # #### See Also + # - `kwarg` - Get a keyword argument value (returns nil if not provided) + # - `kwarg?` - Check if a keyword argument was provided + # - `kwargs` - Get all keyword arguments as a hash + # + #: (Symbol) -> String + def kwarg!(key); end + + # Check if a keyword argument was passed to the workflow + # + # Returns `true` if the specified keyword argument key was provided when the workflow was + # invoked, `false` otherwise. + # + # ### Invocation + # Invoke a workflow with keyword arguments like this: + # ```bash + # roast execute my_workflow.rb [TARGETS] -- name=Samantha project=Roast + # ``` + # + # ### Usage + # ```ruby + # config do + # chat do + # temperature(0.9) if kwarg?(:creative) + # end + # end + # ``` + # + # #### See Also + # - `kwarg` - Get a keyword argument value (returns nil if not provided) + # - `kwarg!` - Get a keyword argument value (raises an error if not provided) + # - `kwargs` - Get all keyword arguments as a hash + # + #: (Symbol) -> bool + def kwarg?(key); end + + # Get all keyword arguments passed to the workflow + # + # Returns a hash of all keyword argument key-value pairs provided when the workflow was invoked. + # All keys are symbols and all values are strings. + # + # ### Invocation + # Invoke a workflow with keyword arguments like this: + # ```bash + # roast execute my_workflow.rb [TARGETS] -- name=Samantha project=Roast + # ``` + # + # ### Usage + # ```ruby + # config do + # chat do + # model(kwargs[:model] || "gpt-5") + # temperature(kwargs[:temperature]&.to_f || 0.3) + # end + # end + # ``` + # + # #### See Also + # - `kwarg` - Get a single keyword argument value + # - `kwarg!` - Get a required keyword argument value + # - `kwarg?` - Check if a keyword argument was provided + # + #: () -> Hash[Symbol, String] + def kwargs; end + end + end +end diff --git a/test/roast/config_manager_test.rb b/test/roast/config_manager_test.rb index 066d11ca..20249516 100644 --- a/test/roast/config_manager_test.rb +++ b/test/roast/config_manager_test.rb @@ -21,8 +21,13 @@ def setup @registry.use(TestCog) end + def build_manager(config_procs = [], params: WorkflowParams.new([], [], {})) + workflow_context = WorkflowContext.new(params:, tmpdir: Dir.tmpdir, workflow_dir: Pathname.pwd) + ConfigManager.new(@registry, config_procs, workflow_context) + end + test "prepare! transitions to prepared state" do - manager = ConfigManager.new(@registry, []) + manager = build_manager refute manager.prepared? manager.prepare! @@ -30,7 +35,7 @@ def setup end test "prepare! raises when called twice" do - manager = ConfigManager.new(@registry, []) + manager = build_manager manager.prepare! assert_raises(ConfigManager::ConfigManagerAlreadyPreparedError) do @@ -44,14 +49,14 @@ def setup test_cog { timeout 60 } timeout_set = true end - manager = ConfigManager.new(@registry, [config_proc]) + manager = build_manager([config_proc]) manager.prepare! assert timeout_set end test "config_for raises when not prepared" do - manager = ConfigManager.new(@registry, []) + manager = build_manager assert_raises(ConfigManager::ConfigManagerNotPreparedError) do manager.config_for(TestCog) @@ -59,7 +64,7 @@ def setup end test "config_for returns default config when no config procs are provided" do - manager = ConfigManager.new(@registry, []) + manager = build_manager manager.prepare! config = manager.config_for(TestCog) @@ -71,7 +76,7 @@ def setup config_proc = proc do test_cog { timeout 60 } end - manager = ConfigManager.new(@registry, [config_proc]) + manager = build_manager([config_proc]) manager.prepare! config = manager.config_for(TestCog) @@ -83,7 +88,7 @@ def setup config_proc = proc do test_cog(:my_step) { timeout 90 } end - manager = ConfigManager.new(@registry, [config_proc]) + manager = build_manager([config_proc]) manager.prepare! scoped_config = manager.config_for(TestCog, :my_step) @@ -97,7 +102,7 @@ def setup config_proc = proc do test_cog(/^api_/) { timeout 120 } end - manager = ConfigManager.new(@registry, [config_proc]) + manager = build_manager([config_proc]) manager.prepare! matching_config = manager.config_for(TestCog, :api_call) @@ -112,7 +117,7 @@ def setup test_cog { async! } test_cog(:my_step) { timeout 90 } end - manager = ConfigManager.new(@registry, [config_proc]) + manager = build_manager([config_proc]) manager.prepare! config = manager.config_for(TestCog, :my_step) @@ -125,7 +130,7 @@ def setup config_proc = proc do global { abort_on_failure! } end - manager = ConfigManager.new(@registry, [config_proc]) + manager = build_manager([config_proc]) manager.prepare! config = manager.config_for(TestCog) @@ -133,6 +138,170 @@ def setup assert config.abort_on_failure? end + test "target! returns the single target inside a cog config block" do + captured = nil + config_proc = proc do + test_cog { captured = target! } + end + manager = build_manager([config_proc], params: WorkflowParams.new(["Gemfile"], [], {})) + manager.prepare! + + assert_equal "Gemfile", captured + end + + test "target! raises inside a cog config block when not exactly one target" do + config_proc = proc do + test_cog { target! } + end + manager = build_manager([config_proc], params: WorkflowParams.new(["a", "b"], [], {})) + + assert_raises(ArgumentError) do + manager.prepare! + end + end + + test "targets returns the target array inside a cog config block" do + captured = nil + config_proc = proc do + test_cog { captured = targets } + end + manager = build_manager([config_proc], params: WorkflowParams.new(["Gemfile", "Rakefile"], [], {})) + manager.prepare! + + assert_equal ["Gemfile", "Rakefile"], captured + end + + test "arg? returns true inside a cog config block when the flag is present" do + captured = nil + config_proc = proc do + test_cog { captured = arg?(:big) } + end + manager = build_manager([config_proc], params: WorkflowParams.new([], [:big], {})) + manager.prepare! + + assert captured + end + + test "arg? returns false inside a cog config block when the flag is absent" do + captured = nil + config_proc = proc do + test_cog { captured = arg?(:big) } + end + manager = build_manager([config_proc], params: WorkflowParams.new([], [], {})) + manager.prepare! + + refute captured + end + + test "args returns the arg array inside a cog config block" do + captured = nil + config_proc = proc do + test_cog { captured = args } + end + manager = build_manager([config_proc], params: WorkflowParams.new([], [:hello, :world], {})) + manager.prepare! + + assert_equal [:hello, :world], captured + end + + test "kwarg returns the value inside a cog config block" do + captured = nil + config_proc = proc do + test_cog { captured = kwarg(:name) } + end + manager = build_manager([config_proc], params: WorkflowParams.new([], [], { name: "test" })) + manager.prepare! + + assert_equal "test", captured + end + + test "kwarg returns nil inside a cog config block when the keyword is missing" do + captured = :sentinel + config_proc = proc do + test_cog { captured = kwarg(:missing) } + end + manager = build_manager([config_proc], params: WorkflowParams.new([], [], { name: "test" })) + manager.prepare! + + assert_nil captured + end + + test "kwarg! returns the value inside a cog config block" do + captured = nil + config_proc = proc do + test_cog { captured = kwarg!(:name) } + end + manager = build_manager([config_proc], params: WorkflowParams.new([], [], { name: "test" })) + manager.prepare! + + assert_equal "test", captured + end + + test "kwarg! raises inside a cog config block when the keyword argument is missing" do + config_proc = proc do + test_cog { kwarg!(:name) } + end + manager = build_manager([config_proc], params: WorkflowParams.new([], [], {})) + + assert_raises(ArgumentError) do + manager.prepare! + end + end + + test "kwarg? returns true inside a cog config block when the keyword is present" do + captured = nil + config_proc = proc do + test_cog { captured = kwarg?(:name) } + end + manager = build_manager([config_proc], params: WorkflowParams.new([], [], { name: "test" })) + manager.prepare! + + assert captured + end + + test "kwarg? returns false inside a cog config block when the keyword is missing" do + captured = nil + config_proc = proc do + test_cog { captured = kwarg?(:missing) } + end + manager = build_manager([config_proc], params: WorkflowParams.new([], [], { name: "test" })) + manager.prepare! + + refute captured + end + + test "kwargs returns the kwargs hash inside a cog config block" do + captured = nil + config_proc = proc do + test_cog { captured = kwargs } + end + manager = build_manager([config_proc], params: WorkflowParams.new([], [], { foo: "bar" })) + manager.prepare! + + assert_equal({ foo: "bar" }, captured) + end + + test "workflow params are accessible inside a global config block" do + config_proc = proc do + global { abort_on_failure! if arg?(:strict) } + end + manager = build_manager([config_proc], params: WorkflowParams.new([], [:strict], {})) + manager.prepare! + + assert manager.config_for(TestCog).abort_on_failure? + end + + test "workflow params are not accessible in the top-level config block body" do + config_proc = proc do + args + end + manager = build_manager([config_proc], params: WorkflowParams.new([], [:big], {})) + + assert_raises(NameError) do + manager.prepare! + end + end + test "prepare! raises IllegalCogNameError when cog name conflicts with existing method" do # Register a cog whose derived name ("freeze") conflicts with Object#freeze conflicting_cog = Class.new(Cog) do @@ -144,7 +313,7 @@ def name end @registry.use(conflicting_cog) - manager = ConfigManager.new(@registry, []) + manager = build_manager assert_raises(ConfigManager::IllegalCogNameError) do manager.prepare! diff --git a/test/roast/execution_manager_test.rb b/test/roast/execution_manager_test.rb index bea536b8..cd282f97 100644 --- a/test/roast/execution_manager_test.rb +++ b/test/roast/execution_manager_test.rb @@ -8,14 +8,14 @@ def setup @registry = Cog::Registry.new @registry.use(TestCogSupport::TestCog) - @config_manager = ConfigManager.new(@registry, []) - @config_manager.prepare! - @workflow_context = WorkflowContext.new( params: WorkflowParams.new([], [], {}), tmpdir: Dir.tmpdir, workflow_dir: Pathname.new(Dir.tmpdir), ) + + @config_manager = ConfigManager.new(@registry, [], @workflow_context) + @config_manager.prepare! end def build_manager(execution_procs, scope: nil, scope_value: nil, scope_index: 0) @@ -68,7 +68,7 @@ def name conflicting_registry.use(conflicting_cog) # Use a clean registry for config_manager so it doesn't hit the same conflict - config_manager = ConfigManager.new(Cog::Registry.new, []) + config_manager = ConfigManager.new(Cog::Registry.new, [], @workflow_context) config_manager.prepare! manager = ExecutionManager.new( diff --git a/test/roast/system_cogs/call_test.rb b/test/roast/system_cogs/call_test.rb index 352f622a..820f1c61 100644 --- a/test/roast/system_cogs/call_test.rb +++ b/test/roast/system_cogs/call_test.rb @@ -9,14 +9,14 @@ def setup @registry = Cog::Registry.new @registry.use(TestCogSupport::TestCog) - @config_manager = ConfigManager.new(@registry, []) - @config_manager.prepare! - @workflow_context = WorkflowContext.new( params: WorkflowParams.new([], [], {}), tmpdir: Dir.tmpdir, workflow_dir: Pathname.new(Dir.tmpdir), ) + + @config_manager = ConfigManager.new(@registry, [], @workflow_context) + @config_manager.prepare! end def build_manager(execution_procs, scope: nil, scope_value: nil, scope_index: 0) diff --git a/test/roast/system_cogs/map_test.rb b/test/roast/system_cogs/map_test.rb index 8bd77b11..5964c602 100644 --- a/test/roast/system_cogs/map_test.rb +++ b/test/roast/system_cogs/map_test.rb @@ -9,14 +9,14 @@ def setup @registry = Cog::Registry.new @registry.use(TestCogSupport::TestCog) - @config_manager = ConfigManager.new(@registry, []) - @config_manager.prepare! - @workflow_context = WorkflowContext.new( params: WorkflowParams.new([], [], {}), tmpdir: Dir.tmpdir, workflow_dir: Pathname.new(Dir.tmpdir), ) + + @config_manager = ConfigManager.new(@registry, [], @workflow_context) + @config_manager.prepare! end def build_manager(execution_procs, scope: nil, scope_value: nil, scope_index: 0) @@ -371,7 +371,7 @@ def build_manager(execution_procs, scope: nil, scope_value: nil, scope_index: 0) test "map executes in parallel when configured" do config_proc = proc { map { parallel! } } - config_manager = ConfigManager.new(@registry, [config_proc]) + config_manager = ConfigManager.new(@registry, [config_proc], @workflow_context) config_manager.prepare! exec_procs = { diff --git a/test/roast/system_cogs/repeat_test.rb b/test/roast/system_cogs/repeat_test.rb index 5fe7fa38..73b530cf 100644 --- a/test/roast/system_cogs/repeat_test.rb +++ b/test/roast/system_cogs/repeat_test.rb @@ -9,14 +9,14 @@ def setup @registry = Cog::Registry.new @registry.use(TestCogSupport::TestCog) - @config_manager = ConfigManager.new(@registry, []) - @config_manager.prepare! - @workflow_context = WorkflowContext.new( params: WorkflowParams.new([], [], {}), tmpdir: Dir.tmpdir, workflow_dir: Pathname.new(Dir.tmpdir), ) + + @config_manager = ConfigManager.new(@registry, [], @workflow_context) + @config_manager.prepare! end def build_manager(execution_procs, scope: nil, scope_value: nil, scope_index: 0) diff --git a/tutorial/03_targets_and_params/README.md b/tutorial/03_targets_and_params/README.md index 181cdc26..afcbebef 100644 --- a/tutorial/03_targets_and_params/README.md +++ b/tutorial/03_targets_and_params/README.md @@ -187,6 +187,15 @@ execute do end ``` +Parameter accessors are also available inside cog `config` blocks and the `global` block: + +```ruby +config do + chat { model(arg?(:fast) ? "gpt-5.4-nano" : "gpt-5") } + global { abort_on_failure! if arg?(:strict) } +end +``` + ## Running the Workflows To run the examples in this chapter: @@ -220,7 +229,7 @@ bin/roast execute tutorial/03_targets_and_params/multiple_targets.rb \ - Use `arg?(:name)` to check if an argument is present - Use `kwarg(:name)` to get a kwarg value (returns nil if missing) - Use `kwarg!(:name)` to require a kwarg (errors if missing) -- All accessors work in any cog's input block +- All accessors work in any cog's input block, and inside cog `config` / `global` blocks ## What's Next? diff --git a/tutorial/04_configuration_options/README.md b/tutorial/04_configuration_options/README.md index 7e1057ed..097de3b3 100644 --- a/tutorial/04_configuration_options/README.md +++ b/tutorial/04_configuration_options/README.md @@ -111,6 +111,25 @@ end This is useful when you have multiple similar cogs that need the same configuration. +### Configuring Based on Parameters + +Workflow parameter accessors (`target!`, `targets`, `arg?`, `args`, `kwarg`, `kwarg!`, `kwarg?`, `kwargs`; see Chapter 3) +are available inside cog `config` blocks and the `global` block, so you can configure cogs based on how the workflow was +invoked: + +```ruby +config do + chat do + # Pick a cheaper model when invoked with the `fast` flag: `-- fast` + model(arg?(:fast) ? "gpt-5.4-nano" : "gpt-5") + end + + global do + abort_on_failure! if arg?(:strict) + end +end +``` + ## Display Options Control what gets printed during workflow execution using display methods in your `config` block: