roast-ai 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +0 -2
  3. data/Gemfile.lock +8 -8
  4. data/README.md +26 -14
  5. data/examples/demo/Gemfile.lock +2 -2
  6. data/examples/plugin-gem-example/Gemfile.lock +4 -4
  7. data/examples/simple_agent.rb +1 -1
  8. data/examples/targets_and_params.rb +12 -0
  9. data/internal/documentation/comments/doc-comments-external.md +5 -3
  10. data/internal/documentation/comments/doc-comments.md +2 -1
  11. data/lib/roast/cog_input_manager.rb +2 -44
  12. data/lib/roast/cogs/agent/config.rb +103 -4
  13. data/lib/roast/cogs/agent/providers/claude/claude_invocation.rb +5 -1
  14. data/lib/roast/cogs/agent/providers/claude/message.rb +4 -2
  15. data/lib/roast/cogs/agent/providers/pi/messages/tool_call_message.rb +218 -18
  16. data/lib/roast/cogs/agent/providers/pi/messages/tool_result_message.rb +208 -13
  17. data/lib/roast/cogs/agent/providers/pi/pi_invocation.rb +2 -0
  18. data/lib/roast/cogs/agent/stats.rb +15 -0
  19. data/lib/roast/cogs/agent/usage.rb +7 -0
  20. data/lib/roast/cogs/agent.rb +57 -4
  21. data/lib/roast/cogs/chat/config.rb +53 -2
  22. data/lib/roast/cogs/chat.rb +13 -5
  23. data/lib/roast/cogs/cmd.rb +35 -0
  24. data/lib/roast/command_runner.rb +3 -1
  25. data/lib/roast/config_manager.rb +29 -2
  26. data/lib/roast/version.rb +1 -1
  27. data/lib/roast/workflow.rb +10 -7
  28. data/lib/roast/workflow_param_accessors.rb +53 -0
  29. data/sorbet/rbi/shims/lib/roast/cog/config.rbi +251 -0
  30. data/sorbet/rbi/shims/lib/roast/config_context.rbi +13 -1
  31. data/tutorial/01_your_first_workflow/README.md +3 -2
  32. data/tutorial/01_your_first_workflow/configured_chat.rb +1 -1
  33. data/tutorial/02_chaining_cogs/README.md +23 -2
  34. data/tutorial/02_chaining_cogs/code_review.rb +2 -2
  35. data/tutorial/02_chaining_cogs/session_resumption.rb +1 -0
  36. data/tutorial/03_targets_and_params/README.md +10 -1
  37. data/tutorial/04_configuration_options/README.md +19 -0
  38. data/tutorial/README.md +1 -1
  39. metadata +4 -2
@@ -5,6 +5,14 @@ module Roast
5
5
  module Cogs
6
6
  class Chat < Cog
7
7
  class Config < Cog::Config
8
+ # Environment variable that overrides the built-in default chat provider.
9
+ #
10
+ # When a chat cog does not explicitly configure a provider, Roast uses the provider named by
11
+ # this variable, falling back to the built-in default (`PROVIDERS.keys.first`, i.e. `:openai`) when it
12
+ # is unset or blank. The value is normalized (surrounding whitespace stripped, then downcased)
13
+ # before lookup, and an explicit `provider` configured on the cog always takes precedence over it.
14
+ DEFAULT_PROVIDER_ENV_VAR = "ROAST_DEFAULT_CHAT_PROVIDER" #: String
15
+
8
16
  PROVIDERS = {
9
17
  openai: {
10
18
  api_key_env_var: "OPENAI_API_KEY",
@@ -28,6 +36,10 @@ module Roast
28
36
  default_base_url: "https://generativelanguage.googleapis.com/v1beta",
29
37
  default_model: "gemini-3.1-flash-lite",
30
38
  },
39
+ bedrock: {
40
+ api_key_env_var: "AWS_ACCESS_KEY_ID",
41
+ default_model: "us.anthropic.claude-haiku-4-5-20251001-v1:0",
42
+ },
31
43
  }.freeze #: Hash[Symbol, Hash[Symbol, String]]
32
44
 
33
45
  # Configure the cog to use a specified API provider when invoking the llm
@@ -42,7 +54,8 @@ module Roast
42
54
 
43
55
  # Configure the cog to use the default provider when invoking the llm
44
56
  #
45
- # The default LLM provider used by Roast is OpenAI (`:openai`).
57
+ # The default LLM provider is the one named by the `ROAST_DEFAULT_CHAT_PROVIDER` environment
58
+ # variable, or OpenAI (`:openai`) when that variable is unset.
46
59
  #
47
60
  # #### See Also
48
61
  # - `provider`
@@ -54,6 +67,10 @@ module Roast
54
67
 
55
68
  # Get the validated provider name that the cog is configured to use when invoking the llm
56
69
  #
70
+ # The provider is resolved in order of precedence: the provider explicitly configured on the cog,
71
+ # then the `ROAST_DEFAULT_CHAT_PROVIDER` environment variable (normalized by stripping surrounding
72
+ # whitespace and downcasing), then the built-in default (`PROVIDERS.keys.first`, i.e. `:openai`).
73
+ #
57
74
  # Note: this method will return the name of a valid provider or raise an `InvalidConfigError`.
58
75
  # It will __not__, however, validate that the you have access to the provider's API.
59
76
  # If you have not correctly configured API access, you will likely experience a failure when Roast attempts to
@@ -65,7 +82,8 @@ module Roast
65
82
  #
66
83
  #: () -> Symbol
67
84
  def valid_provider!
68
- provider = @values[:provider] || PROVIDERS.keys.first
85
+ env_default = ENV[DEFAULT_PROVIDER_ENV_VAR].presence&.strip&.downcase&.to_sym
86
+ provider = @values[:provider] || env_default || PROVIDERS.keys.first
69
87
  unless PROVIDERS.include?(provider)
70
88
  raise InvalidConfigError, "'#{provider}' is not a valid provider. Available providers include: #{PROVIDERS.keys.join(", ")}"
71
89
  end
@@ -94,6 +112,7 @@ module Roast
94
112
  # - Anthropic Provider: ANTHROPIC_API_KEY
95
113
  # - Perplexity Provider: PERPLEXITY_API_KEY
96
114
  # - Gemini Provider: GEMINI_API_KEY
115
+ # - Bedrock Provider: AWS_ACCESS_KEY_ID
97
116
  #
98
117
  # #### See Also
99
118
  # - `api_key`
@@ -113,6 +132,7 @@ module Roast
113
132
  # - Anthropic Provider: ANTHROPIC_API_KEY
114
133
  # - Perplexity Provider: PERPLEXITY_API_KEY
115
134
  # - Gemini Provider: GEMINI_API_KEY
135
+ # - Bedrock Provider: AWS_ACCESS_KEY_ID
116
136
  #
117
137
  # #### See Also
118
138
  # - `api_key`
@@ -176,6 +196,37 @@ module Roast
176
196
  PROVIDERS.dig(valid_provider!, :default_base_url)
177
197
  end
178
198
 
199
+ # Get the AWS secret access key used to sign requests when the Bedrock provider is selected
200
+ #
201
+ # Only the Bedrock provider uses this value. It is read from the `AWS_SECRET_ACCESS_KEY` environment variable.
202
+ # This method will raise InvalidConfigError if that variable is unset or blank.
203
+ #
204
+ #: () -> String
205
+ def valid_aws_secret_access_key!
206
+ ENV["AWS_SECRET_ACCESS_KEY"].presence || raise(InvalidConfigError, "AWS_SECRET_ACCESS_KEY is not set")
207
+ end
208
+
209
+ # Get the AWS session token used to sign requests with temporary credentials, such as those from AWS SSO
210
+ #
211
+ # Only the Bedrock provider uses this value. It is read from the `AWS_SESSION_TOKEN` environment variable.
212
+ # Returns nil if that variable is unset or blank, because long-term credentials need no session token.
213
+ #
214
+ #: () -> String?
215
+ def valid_aws_session_token
216
+ ENV["AWS_SESSION_TOKEN"].presence
217
+ end
218
+
219
+ # Get the AWS region that the Bedrock provider sends requests to
220
+ #
221
+ # Only the Bedrock provider uses this value. It is read from the `AWS_REGION` environment variable only.
222
+ # `AWS_DEFAULT_REGION` is ignored, so that a region left in a shell profile never selects a region silently.
223
+ # This method will raise InvalidConfigError if `AWS_REGION` is unset or blank.
224
+ #
225
+ #: () -> String
226
+ def valid_aws_region!
227
+ ENV["AWS_REGION"].presence || raise(InvalidConfigError, "AWS_REGION is not set")
228
+ end
229
+
179
230
  # Configure the cog to use a specific model when invoking the agent
180
231
  #
181
232
  # The model name format is provider-specific.
@@ -59,16 +59,19 @@ module Roast
59
59
  Event << { block: { header: "UNKNOWN", content: message.content } } if config.show_prompt? || config.show_response?
60
60
  end
61
61
  end
62
+ # The Bedrock Converse response has no modelId field, so ruby_llm leaves response.model_id nil for it.
63
+ # The chat model holds the ID that ruby_llm sent.
64
+ model_id = response.model_id || chat.model.id
62
65
  if config.show_stats?
63
66
  temperature = chat.instance_variable_get(:@temperature)
64
- lines = ["Model: #{response.model_id}"]
67
+ lines = ["Model: #{model_id}"]
65
68
  lines << "Temperature: #{format("%0.2f", temperature)}" if temperature
66
69
  lines << "Input Tokens: #{response.input_tokens}"
67
70
  lines << "Output Tokens: #{response.output_tokens}"
68
71
  Event << { block: { header: "LLM STATS", content: lines.join("\n") } }
69
72
  end
70
73
 
71
- verify_response_not_truncated!(response)
74
+ verify_response_not_truncated!(response, model_id)
72
75
 
73
76
  Output.new(Session.from_chat(chat), response.content)
74
77
  end
@@ -95,15 +98,15 @@ module Roast
95
98
  # on a natural boundary. This is an unavoidable trade-off of the heuristic approach until
96
99
  # ruby_llm exposes finish_reason.
97
100
  #
98
- #: (RubyLLM::Message) -> void
99
- def verify_response_not_truncated!(response)
101
+ #: (RubyLLM::Message, String) -> void
102
+ def verify_response_not_truncated!(response, model_id)
100
103
  max_tokens = effective_max_tokens
101
104
  return unless max_tokens
102
105
  return unless response.output_tokens
103
106
 
104
107
  if response.output_tokens >= max_tokens
105
108
  raise MaxTokensExceededError,
106
- "LLM response from #{response.model_id} was truncated at the max token limit " \
109
+ "LLM response from #{model_id} was truncated at the max token limit " \
107
110
  "(output: #{response.output_tokens} tokens, limit: #{max_tokens} tokens). " \
108
111
  "The response content is likely incomplete and should not be used."
109
112
  end
@@ -143,6 +146,11 @@ module Roast
143
146
  when :gemini
144
147
  context.gemini_api_key = config.valid_api_key!
145
148
  context.gemini_api_base = config.valid_base_url
149
+ when :bedrock
150
+ context.bedrock_api_key = config.valid_api_key!
151
+ context.bedrock_secret_key = config.valid_aws_secret_access_key!
152
+ context.bedrock_session_token = config.valid_aws_session_token
153
+ context.bedrock_region = config.valid_aws_region!
146
154
  end
147
155
  end
148
156
  end
@@ -168,6 +168,40 @@ module Roast
168
168
  show_stdout? || show_stderr?
169
169
  end
170
170
 
171
+ # Configure the cog to run the command with these environment variables set
172
+ #
173
+ # Variables not named here are inherited from Roast's own environment. A `nil` value unsets
174
+ # the variable for the command.
175
+ #
176
+ # #### See Also
177
+ # - `no_env!`
178
+ #
179
+ #: (Hash[String | Symbol, String?]) -> void
180
+ def env(vars)
181
+ @values[:env] = vars
182
+ end
183
+
184
+ # Configure the cog to run the command with Roast's own environment, unchanged (the default)
185
+ #
186
+ # #### See Also
187
+ # - `env`
188
+ #
189
+ #: () -> void
190
+ def no_env!
191
+ @values[:env] = nil
192
+ end
193
+
194
+ # Get the validated environment variables the command will be run with, keyed by name
195
+ #
196
+ # #### See Also
197
+ # - `env`
198
+ # - `no_env!`
199
+ #
200
+ #: () -> Hash[String, String?]
201
+ def valid_env
202
+ (@values[:env] || {}).to_h { |name, value| [name.to_s, value&.to_s] }
203
+ end
204
+
171
205
  alias_method(:quiet!, :no_display!)
172
206
  end
173
207
 
@@ -281,6 +315,7 @@ module Roast
281
315
  .execute(
282
316
  [input.command] + input.args,
283
317
  working_directory: config.valid_working_directory,
318
+ env: config.valid_env,
284
319
  stdin_content: input.stdin,
285
320
  stdout_handler: stdout_handler,
286
321
  stderr_handler: stderr_handler,
@@ -45,6 +45,7 @@ module Roast
45
45
  #: (
46
46
  #| Array[String],
47
47
  #| ?working_directory: (Pathname | String)?,
48
+ #| ?env: Hash[String, String?],
48
49
  #| ?timeout: (Integer | Float)?,
49
50
  #| ?stdin_content: String?,
50
51
  #| ?stdout_handler: (^(String) -> void)?,
@@ -53,6 +54,7 @@ module Roast
53
54
  def execute(
54
55
  args,
55
56
  working_directory: nil,
57
+ env: {},
56
58
  timeout: nil,
57
59
  stdin_content: nil,
58
60
  stdout_handler: nil,
@@ -69,7 +71,7 @@ module Roast
69
71
  stdin, stdout, stderr, wait_thread = Bundler.with_unbundled_env do
70
72
  Open3 #: as untyped
71
73
  .popen3(
72
- env_overrides.merge({ "PWD" => working_directory&.to_s }).compact,
74
+ env.merge(env_overrides, { "PWD" => working_directory&.to_s }.compact),
73
75
  *args.map(&:to_s),
74
76
  { chdir: working_directory }.compact,
75
77
  )
@@ -3,15 +3,18 @@
3
3
 
4
4
  module Roast
5
5
  class ConfigManager
6
+ include WorkflowParamAccessors
7
+
6
8
  class ConfigManagerError < Roast::Error; end
7
9
  class ConfigManagerNotPreparedError < ConfigManagerError; end
8
10
  class ConfigManagerAlreadyPreparedError < ConfigManagerError; end
9
11
  class IllegalCogNameError < ConfigManagerError; end
10
12
 
11
- #: (Cog::Registry, Array[^() -> void]) -> void
12
- def initialize(cog_registry, config_procs)
13
+ #: (Cog::Registry, Array[^() -> void], WorkflowContext) -> void
14
+ def initialize(cog_registry, config_procs, workflow_context)
13
15
  @cog_registry = cog_registry
14
16
  @config_procs = config_procs
17
+ @workflow_context = workflow_context
15
18
  @config_context = ConfigContext.new #: ConfigContext
16
19
  @global_config = Cog::Config.new #: Cog::Config
17
20
  @general_configs = {} #: Hash[singleton(Cog), Cog::Config]
@@ -117,6 +120,7 @@ module Roast
117
120
  # NOTE: Sorbet expects the proc passed to instance_exec to be declared as taking an argument
118
121
  # but our cog_config_proc does not get an argument
119
122
  cog_config_proc = cog_config_proc #: as ^(untyped) -> void
123
+ bind_workflow_params(config_object)
120
124
  config_object.instance_exec(&cog_config_proc) if cog_config_proc
121
125
  nil
122
126
  end
@@ -134,8 +138,31 @@ module Roast
134
138
  #: (^() -> void ) -> void
135
139
  def on_global(global_config_proc)
136
140
  global_config_proc = global_config_proc #: as ^(untyped) -> void
141
+ bind_workflow_params(@global_config)
137
142
  @global_config.instance_exec(&global_config_proc) if global_config_proc
138
143
  nil
139
144
  end
145
+
146
+ #: (Cog::Config) -> void
147
+ def bind_workflow_params(object)
148
+ target_bang_method = method(:target!)
149
+ targets_method = method(:targets)
150
+ arg_question_method = method(:arg?)
151
+ args_method = method(:args)
152
+ kwarg_method = method(:kwarg)
153
+ kwarg_bang_method = method(:kwarg!)
154
+ kwarg_question_method = method(:kwarg?)
155
+ kwargs_method = method(:kwargs)
156
+ object.instance_eval do
157
+ define_singleton_method(:target!, proc { target_bang_method.call })
158
+ define_singleton_method(:targets, proc { targets_method.call })
159
+ define_singleton_method(:arg?, proc { |value| arg_question_method.call(value) })
160
+ define_singleton_method(:args, proc { args_method.call })
161
+ define_singleton_method(:kwarg, proc { |key| kwarg_method.call(key) })
162
+ define_singleton_method(:kwarg!, proc { |key| kwarg_bang_method.call(key) })
163
+ define_singleton_method(:kwarg?, proc { |key| kwarg_question_method.call(key) })
164
+ define_singleton_method(:kwargs, proc { kwargs_method.call })
165
+ end
166
+ end
140
167
  end
141
168
  end
data/lib/roast/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module Roast
5
- VERSION = "1.2.0"
5
+ VERSION = "1.4.0"
6
6
  end
@@ -19,12 +19,15 @@ module Roast
19
19
  Sync do
20
20
  Dir.mktmpdir("roast-") do |tmpdir|
21
21
  EventMonitor.start!
22
- workflow_dir = Pathname.new(workflow_path).dirname
23
- workflow_context = WorkflowContext.new(params: params, tmpdir: tmpdir, workflow_dir: workflow_dir)
24
- workflow = new(workflow_path, workflow_context)
25
- workflow.prepare!
26
- workflow.start!
27
- EventMonitor.stop!
22
+ begin
23
+ workflow_dir = Pathname.new(workflow_path).dirname
24
+ workflow_context = WorkflowContext.new(params: params, tmpdir: tmpdir, workflow_dir: workflow_dir)
25
+ workflow = new(workflow_path, workflow_context)
26
+ workflow.prepare!
27
+ workflow.start!
28
+ ensure
29
+ EventMonitor.stop! if EventMonitor.running?
30
+ end
28
31
  end
29
32
  end
30
33
  end
@@ -48,7 +51,7 @@ module Roast
48
51
 
49
52
  @preparing = true
50
53
  extract_dsl_procs!
51
- @config_manager = ConfigManager.new(@cog_registry, @config_procs)
54
+ @config_manager = ConfigManager.new(@cog_registry, @config_procs, @workflow_context)
52
55
  @config_manager.not_nil!.prepare!
53
56
  # TODO: probably we should just not pass the params as the top-level scope value anymore
54
57
  @execution_manager = ExecutionManager.new(@cog_registry, @config_manager.not_nil!, @execution_procs, @workflow_context, scope_value: @workflow_context.params)
@@ -0,0 +1,53 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ # @requires_ancestor: Kernel
6
+ module WorkflowParamAccessors
7
+ private
8
+
9
+ #: () -> String
10
+ def target!
11
+ raise ArgumentError, "expected exactly one target" unless @workflow_context.params.targets.length == 1
12
+
13
+ @workflow_context.params.targets.first #: as String
14
+ end
15
+
16
+ #: () -> Array[String]
17
+ def targets
18
+ @workflow_context.params.targets.dup
19
+ end
20
+
21
+ #: (Symbol) -> bool
22
+ def arg?(value)
23
+ @workflow_context.params.args.include?(value)
24
+ end
25
+
26
+ #: () -> Array[Symbol]
27
+ def args
28
+ @workflow_context.params.args.dup
29
+ end
30
+
31
+ #: (Symbol) -> String?
32
+ def kwarg(key)
33
+ @workflow_context.params.kwargs[key]
34
+ end
35
+
36
+ #: (Symbol) -> String
37
+ def kwarg!(key)
38
+ raise ArgumentError, "expected keyword argument '#{key}' to be present" unless @workflow_context.params.kwargs.include?(key)
39
+
40
+ @workflow_context.params.kwargs[key] #: as String
41
+ end
42
+
43
+ #: (Symbol) -> bool
44
+ def kwarg?(key)
45
+ @workflow_context.params.kwargs.include?(key)
46
+ end
47
+
48
+ #: () -> Hash[Symbol, String]
49
+ def kwargs
50
+ @workflow_context.params.kwargs.dup
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,251 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Roast
5
+ class Cog
6
+ class Config
7
+ ########################################
8
+ # Workflow Methods
9
+ ########################################
10
+
11
+ # Get the single target value passed to the workflow
12
+ #
13
+ # Returns the target when exactly one target was provided to the workflow. Raises an
14
+ # `ArgumentError` if the workflow was invoked with zero or multiple targets.
15
+ #
16
+ # Targets are file paths, URLs, or other identifiers passed to the workflow when it is
17
+ # invoked. Use this method when your workflow expects exactly one target.
18
+ #
19
+ # ### Invocation
20
+ # Invoke a workflow with a single target like this:
21
+ # ```bash
22
+ # roast execute my_workflow.rb my_target_file.txt
23
+ # ```
24
+ #
25
+ # ### Usage
26
+ # ```ruby
27
+ # config do
28
+ # chat do
29
+ # # Configure based on the single target
30
+ # temperature(target!.end_with?(".md") ? 0.7 : 0.2)
31
+ # end
32
+ # end
33
+ # ```
34
+ #
35
+ # #### See Also
36
+ # - `targets` - Get all targets as an array (works with any number of targets)
37
+ #
38
+ #: () -> String
39
+ def target!; end
40
+
41
+ # Get all targets passed to the workflow
42
+ #
43
+ # Returns an array of all targets provided to the workflow. Works with any number of targets
44
+ # (zero, one, or many). Targets are file paths, URLs, or other identifiers passed when the
45
+ # workflow is invoked.
46
+ #
47
+ # ### Invocation
48
+ # Invoke a workflow with multiple targets like this:
49
+ # ```bash
50
+ # roast execute my_workflow.rb target_one.txt target_two.txt
51
+ # ```
52
+ # or using shell globs like this:
53
+ # ```bash
54
+ # roast execute my_workflow.rb target_*.txt
55
+ # ```
56
+ #
57
+ # ### Usage
58
+ # ```ruby
59
+ # config do
60
+ # map do
61
+ # # Process targets in parallel when there are several
62
+ # parallel! if targets.length > 3
63
+ # end
64
+ # end
65
+ # ```
66
+ #
67
+ # #### See Also
68
+ # - `target!` - Get the single target (raises an error if there isn't exactly one)
69
+ #
70
+ #: () -> Array[String]
71
+ def targets; end
72
+
73
+ # Check if a flag argument was passed to the workflow
74
+ #
75
+ # Returns `true` if the specified flag argument symbol was provided when the workflow
76
+ # was invoked, `false` otherwise.
77
+ #
78
+ # Flag arguments are symbolic flags passed to the workflow (e.g., `retry`, `force`)
79
+ # that enable or modify behavior.
80
+ #
81
+ # ### Invocation
82
+ # Invoke a workflow with flag arguments like this:
83
+ # ```bash
84
+ # roast execute my_workflow.rb [TARGETS] -- retry force
85
+ # ```
86
+ #
87
+ # ### Usage
88
+ # ```ruby
89
+ # config do
90
+ # chat do
91
+ # # Pick a cheaper model when invoked with the `fast` flag
92
+ # model(arg?(:fast) ? "gpt-5.4-nano" : "gpt-5")
93
+ # end
94
+ # end
95
+ # ```
96
+ #
97
+ # #### See Also
98
+ # - `args` - Get all flag arguments as an array
99
+ # - `kwarg?` - Check for keyword arguments (key-value pairs)
100
+ #
101
+ #: (Symbol) -> bool
102
+ def arg?(value); end
103
+
104
+ # Get all flag arguments passed to the workflow
105
+ #
106
+ # Returns an array of all flag argument symbols provided when the workflow was invoked.
107
+ # Flag arguments are symbolic flags (e.g., `retry`, `force`) that enable or modify
108
+ # workflow behavior.
109
+ #
110
+ # ### Invocation
111
+ # Invoke a workflow with flag arguments like this:
112
+ # ```bash
113
+ # roast execute my_workflow.rb [TARGETS] -- retry force
114
+ # ```
115
+ #
116
+ # ### Usage
117
+ # ```ruby
118
+ # config do
119
+ # global do
120
+ # # Show full output whenever any flags were passed
121
+ # display! unless args.empty?
122
+ # end
123
+ # end
124
+ # ```
125
+ #
126
+ # #### See Also
127
+ # - `arg?` - Check if a specific flag argument was provided
128
+ # - `kwargs` - Get all keyword arguments
129
+ #
130
+ #: () -> Array[Symbol]
131
+ def args; end
132
+
133
+ # Get a keyword argument value passed to the workflow
134
+ #
135
+ # Returns the string value for the specified keyword argument key, or `nil` if the key was
136
+ # not provided. Keyword arguments are key-value pairs passed to the workflow for configuration.
137
+ #
138
+ # ### Invocation
139
+ # Invoke a workflow with keyword arguments like this:
140
+ # ```bash
141
+ # roast execute my_workflow.rb [TARGETS] -- name=Samantha project=Roast
142
+ # ```
143
+ #
144
+ # ### Usage
145
+ # ```ruby
146
+ # config do
147
+ # chat do
148
+ # # Use a provided model override, or fall back to a default
149
+ # model(kwarg(:model) || "gpt-5")
150
+ # end
151
+ # end
152
+ # ```
153
+ #
154
+ # #### See Also
155
+ # - `kwarg!` - Get a keyword argument value (raises an error if not provided)
156
+ # - `kwarg?` - Check if a keyword argument was provided
157
+ # - `kwargs` - Get all keyword arguments as a hash
158
+ #
159
+ #: (Symbol) -> String?
160
+ def kwarg(key); end
161
+
162
+ # Get a required keyword argument value passed to the workflow
163
+ #
164
+ # Returns the string value for the specified keyword argument key. Raises an `ArgumentError`
165
+ # if the key was not provided.
166
+ #
167
+ # Use this when your workflow requires a specific keyword argument to function correctly.
168
+ #
169
+ # ### Invocation
170
+ # Invoke a workflow with keyword arguments like this:
171
+ # ```bash
172
+ # roast execute my_workflow.rb [TARGETS] -- name=Samantha project=Roast
173
+ # ```
174
+ #
175
+ # ### Usage
176
+ # ```ruby
177
+ # config do
178
+ # chat do
179
+ # # Require the 'model' keyword argument
180
+ # model(kwarg!(:model))
181
+ # end
182
+ # end
183
+ # ```
184
+ #
185
+ # #### See Also
186
+ # - `kwarg` - Get a keyword argument value (returns nil if not provided)
187
+ # - `kwarg?` - Check if a keyword argument was provided
188
+ # - `kwargs` - Get all keyword arguments as a hash
189
+ #
190
+ #: (Symbol) -> String
191
+ def kwarg!(key); end
192
+
193
+ # Check if a keyword argument was passed to the workflow
194
+ #
195
+ # Returns `true` if the specified keyword argument key was provided when the workflow was
196
+ # invoked, `false` otherwise.
197
+ #
198
+ # ### Invocation
199
+ # Invoke a workflow with keyword arguments like this:
200
+ # ```bash
201
+ # roast execute my_workflow.rb [TARGETS] -- name=Samantha project=Roast
202
+ # ```
203
+ #
204
+ # ### Usage
205
+ # ```ruby
206
+ # config do
207
+ # chat do
208
+ # temperature(0.9) if kwarg?(:creative)
209
+ # end
210
+ # end
211
+ # ```
212
+ #
213
+ # #### See Also
214
+ # - `kwarg` - Get a keyword argument value (returns nil if not provided)
215
+ # - `kwarg!` - Get a keyword argument value (raises an error if not provided)
216
+ # - `kwargs` - Get all keyword arguments as a hash
217
+ #
218
+ #: (Symbol) -> bool
219
+ def kwarg?(key); end
220
+
221
+ # Get all keyword arguments passed to the workflow
222
+ #
223
+ # Returns a hash of all keyword argument key-value pairs provided when the workflow was invoked.
224
+ # All keys are symbols and all values are strings.
225
+ #
226
+ # ### Invocation
227
+ # Invoke a workflow with keyword arguments like this:
228
+ # ```bash
229
+ # roast execute my_workflow.rb [TARGETS] -- name=Samantha project=Roast
230
+ # ```
231
+ #
232
+ # ### Usage
233
+ # ```ruby
234
+ # config do
235
+ # chat do
236
+ # model(kwargs[:model] || "gpt-5")
237
+ # temperature(kwargs[:temperature]&.to_f || 0.3)
238
+ # end
239
+ # end
240
+ # ```
241
+ #
242
+ # #### See Also
243
+ # - `kwarg` - Get a single keyword argument value
244
+ # - `kwarg!` - Get a required keyword argument value
245
+ # - `kwarg?` - Check if a keyword argument was provided
246
+ #
247
+ #: () -> Hash[Symbol, String]
248
+ def kwargs; end
249
+ end
250
+ end
251
+ end
@@ -141,7 +141,7 @@ module Roast
141
141
  #
142
142
  # #### Configure the agent provider
143
143
  # - `provider(symbol)` - Set the agent provider (e.g., `:claude`)
144
- # - `use_default_provider!` - Use the default provider (`:claude`)
144
+ # - `use_default_provider!` - Use the default provider (`:pi`, or `$ROAST_DEFAULT_AGENT_PROVIDER` when set)
145
145
  #
146
146
  # #### Configure the base command used to run the coding agent
147
147
  # - `command(string_or_array)` - Set the base command for invoking the agent
@@ -151,6 +151,10 @@ module Roast
151
151
  # - `model(string)` - Set the model to use
152
152
  # - `use_default_model!` - Use the provider's default model
153
153
  #
154
+ # #### Configure a time limit
155
+ # - `timeout(seconds)` - Kill the agent and fail the cog if it runs longer than this
156
+ # - `no_timeout!` - Let the agent run for as long as it takes (default)
157
+ #
154
158
  # #### Configure the system prompt
155
159
  # - `replace_system_prompt(string)` - Completely replace the agent's default system prompt
156
160
  # - `no_replace_system_prompt!` - Don't replace the default system prompt (default)
@@ -180,6 +184,10 @@ module Roast
180
184
  # #### Configure debugging
181
185
  # - `dump_raw_agent_messages_to(filename)` - Dump raw agent messages to a file for debugging
182
186
  #
187
+ # #### Configure run records
188
+ # - `record_agent_runs_to(filename)` - Append a JSON record of each agent run (prompts, response, session, stats, status) to a file
189
+ # - `no_record_agent_runs!` - Don't record agent runs (default)
190
+ #
183
191
  # #### Configure asynchronous execution
184
192
  # - `async!` - Run the cog asynchronously in the background
185
193
  # - `no_async!` (alias `sync!`) - Run the cog synchronously (default)
@@ -269,6 +277,10 @@ module Roast
269
277
  # - `fail_on_error!` - Consider the cog failed if the command returns a non-zero exit status (default)
270
278
  # - `no_fail_on_error!` - Don't fail the cog on non-zero exit status (exit status still available in output)
271
279
  #
280
+ # #### Configure the environment
281
+ # - `env(hash)` - Set environment variables for the command (a `nil` value unsets one)
282
+ # - `no_env!` - Run the command with Roast's own environment, unchanged (default)
283
+ #
272
284
  # #### Configure display output
273
285
  # - `show_stdout!` - Write STDOUT to the console
274
286
  # - `no_show_stdout!` - Don't write STDOUT to the console (default)