roast-ai 0.4.9 → 0.5.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 (194) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/commands/docs/write-comments.md +36 -0
  3. data/.github/CODEOWNERS +1 -1
  4. data/.github/workflows/ci.yaml +10 -6
  5. data/.gitignore +0 -1
  6. data/.rubocop.yml +7 -1
  7. data/CLAUDE.md +2 -2
  8. data/CONTRIBUTING.md +2 -0
  9. data/Gemfile +18 -18
  10. data/Gemfile.lock +46 -57
  11. data/README.md +118 -1432
  12. data/README_LEGACY.md +1464 -0
  13. data/Rakefile +39 -4
  14. data/dev.yml +29 -0
  15. data/dsl/agent_sessions.rb +20 -0
  16. data/dsl/async_cogs.rb +49 -0
  17. data/dsl/async_cogs_complex.rb +67 -0
  18. data/dsl/call.rb +44 -0
  19. data/dsl/collect_from.rb +72 -0
  20. data/dsl/demo/Gemfile +4 -0
  21. data/dsl/demo/Gemfile.lock +120 -0
  22. data/dsl/demo/cogs/local.rb +15 -0
  23. data/dsl/demo/simple_external_cog.rb +17 -0
  24. data/dsl/json_output.rb +28 -0
  25. data/dsl/map.rb +55 -0
  26. data/dsl/map_reduce.rb +37 -0
  27. data/dsl/map_with_index.rb +49 -0
  28. data/dsl/next_break.rb +40 -0
  29. data/dsl/next_break_parallel.rb +44 -0
  30. data/dsl/outputs.rb +39 -0
  31. data/dsl/outputs_bang.rb +36 -0
  32. data/dsl/parallel_map.rb +37 -0
  33. data/dsl/plugin-gem-example/.gitignore +8 -0
  34. data/dsl/plugin-gem-example/Gemfile +13 -0
  35. data/dsl/plugin-gem-example/Gemfile.lock +178 -0
  36. data/dsl/plugin-gem-example/lib/other.rb +17 -0
  37. data/dsl/plugin-gem-example/lib/plugin_gem_example.rb +5 -0
  38. data/dsl/plugin-gem-example/lib/simple.rb +15 -0
  39. data/dsl/plugin-gem-example/lib/version.rb +10 -0
  40. data/dsl/plugin-gem-example/plugin-gem-example.gemspec +28 -0
  41. data/dsl/prompts/simple_prompt.md.erb +3 -0
  42. data/dsl/prototype.rb +10 -4
  43. data/dsl/repeat_loop_results.rb +53 -0
  44. data/dsl/ruby_cog.rb +72 -0
  45. data/dsl/simple_agent.rb +18 -0
  46. data/dsl/simple_chat.rb +26 -0
  47. data/dsl/simple_repeat.rb +29 -0
  48. data/dsl/skip.rb +36 -0
  49. data/dsl/step_communication.rb +10 -5
  50. data/dsl/targets_and_params.rb +57 -0
  51. data/dsl/temperature.rb +17 -0
  52. data/dsl/temporary_directory.rb +22 -0
  53. data/dsl/tutorial/01_your_first_workflow/README.md +179 -0
  54. data/dsl/tutorial/01_your_first_workflow/configured_chat.rb +33 -0
  55. data/dsl/tutorial/01_your_first_workflow/hello.rb +23 -0
  56. data/dsl/tutorial/02_chaining_cogs/README.md +310 -0
  57. data/dsl/tutorial/02_chaining_cogs/code_review.rb +104 -0
  58. data/dsl/tutorial/02_chaining_cogs/session_resumption.rb +92 -0
  59. data/dsl/tutorial/02_chaining_cogs/simple_chain.rb +84 -0
  60. data/dsl/tutorial/03_targets_and_params/README.md +230 -0
  61. data/dsl/tutorial/03_targets_and_params/multiple_targets.rb +65 -0
  62. data/dsl/tutorial/03_targets_and_params/single_target.rb +65 -0
  63. data/dsl/tutorial/04_configuration_options/README.md +209 -0
  64. data/dsl/tutorial/04_configuration_options/control_display_and_temperature.rb +104 -0
  65. data/dsl/tutorial/04_configuration_options/simple_config.rb +68 -0
  66. data/dsl/tutorial/05_control_flow/README.md +156 -0
  67. data/dsl/tutorial/05_control_flow/conditional_execution.rb +62 -0
  68. data/dsl/tutorial/05_control_flow/handling_failures.rb +77 -0
  69. data/dsl/tutorial/06_reusable_scopes/README.md +172 -0
  70. data/dsl/tutorial/06_reusable_scopes/accessing_scope_outputs.rb +126 -0
  71. data/dsl/tutorial/06_reusable_scopes/basic_scope.rb +63 -0
  72. data/dsl/tutorial/06_reusable_scopes/parameterized_scope.rb +78 -0
  73. data/dsl/tutorial/07_processing_collections/README.md +152 -0
  74. data/dsl/tutorial/07_processing_collections/basic_map.rb +70 -0
  75. data/dsl/tutorial/07_processing_collections/parallel_map.rb +74 -0
  76. data/dsl/tutorial/08_iterative_workflows/README.md +231 -0
  77. data/dsl/tutorial/08_iterative_workflows/basic_repeat.rb +57 -0
  78. data/dsl/tutorial/08_iterative_workflows/conditional_break.rb +57 -0
  79. data/dsl/tutorial/09_async_cogs/README.md +197 -0
  80. data/dsl/tutorial/09_async_cogs/basic_async.rb +38 -0
  81. data/dsl/tutorial/README.md +222 -0
  82. data/dsl/working_directory.rb +16 -0
  83. data/exe/roast +1 -1
  84. data/internal/documentation/architectural-notes.md +115 -0
  85. data/internal/documentation/doc-comments-external.md +686 -0
  86. data/internal/documentation/doc-comments-internal.md +342 -0
  87. data/internal/documentation/doc-comments.md +211 -0
  88. data/lib/roast/dsl/cog/config.rb +280 -4
  89. data/lib/roast/dsl/cog/input.rb +73 -0
  90. data/lib/roast/dsl/cog/output.rb +313 -0
  91. data/lib/roast/dsl/cog/registry.rb +71 -0
  92. data/lib/roast/dsl/cog/stack.rb +3 -2
  93. data/lib/roast/dsl/cog/store.rb +11 -8
  94. data/lib/roast/dsl/cog.rb +108 -31
  95. data/lib/roast/dsl/cog_input_context.rb +44 -0
  96. data/lib/roast/dsl/cog_input_manager.rb +156 -0
  97. data/lib/roast/dsl/cogs/agent/config.rb +465 -0
  98. data/lib/roast/dsl/cogs/agent/input.rb +81 -0
  99. data/lib/roast/dsl/cogs/agent/output.rb +59 -0
  100. data/lib/roast/dsl/cogs/agent/provider.rb +51 -0
  101. data/lib/roast/dsl/cogs/agent/providers/claude/claude_invocation.rb +185 -0
  102. data/lib/roast/dsl/cogs/agent/providers/claude/message.rb +73 -0
  103. data/lib/roast/dsl/cogs/agent/providers/claude/messages/assistant_message.rb +36 -0
  104. data/lib/roast/dsl/cogs/agent/providers/claude/messages/result_message.rb +61 -0
  105. data/lib/roast/dsl/cogs/agent/providers/claude/messages/system_message.rb +47 -0
  106. data/lib/roast/dsl/cogs/agent/providers/claude/messages/text_message.rb +36 -0
  107. data/lib/roast/dsl/cogs/agent/providers/claude/messages/tool_result_message.rb +47 -0
  108. data/lib/roast/dsl/cogs/agent/providers/claude/messages/tool_use_message.rb +46 -0
  109. data/lib/roast/dsl/cogs/agent/providers/claude/messages/unknown_message.rb +27 -0
  110. data/lib/roast/dsl/cogs/agent/providers/claude/messages/user_message.rb +37 -0
  111. data/lib/roast/dsl/cogs/agent/providers/claude/tool_result.rb +51 -0
  112. data/lib/roast/dsl/cogs/agent/providers/claude/tool_use.rb +48 -0
  113. data/lib/roast/dsl/cogs/agent/providers/claude.rb +31 -0
  114. data/lib/roast/dsl/cogs/agent/stats.rb +92 -0
  115. data/lib/roast/dsl/cogs/agent/usage.rb +62 -0
  116. data/lib/roast/dsl/cogs/agent.rb +75 -0
  117. data/lib/roast/dsl/cogs/chat/config.rb +453 -0
  118. data/lib/roast/dsl/cogs/chat/input.rb +92 -0
  119. data/lib/roast/dsl/cogs/chat/output.rb +64 -0
  120. data/lib/roast/dsl/cogs/chat/session.rb +68 -0
  121. data/lib/roast/dsl/cogs/chat.rb +81 -0
  122. data/lib/roast/dsl/cogs/cmd.rb +291 -27
  123. data/lib/roast/dsl/cogs/ruby.rb +171 -0
  124. data/lib/roast/dsl/command_runner.rb +191 -0
  125. data/lib/roast/dsl/config_context.rb +2 -47
  126. data/lib/roast/dsl/config_manager.rb +143 -0
  127. data/lib/roast/dsl/control_flow.rb +41 -0
  128. data/lib/roast/dsl/execution_context.rb +9 -0
  129. data/lib/roast/dsl/execution_manager.rb +267 -0
  130. data/lib/roast/dsl/nil_assertions.rb +23 -0
  131. data/lib/roast/dsl/system_cog/params.rb +32 -0
  132. data/lib/roast/dsl/system_cog.rb +36 -0
  133. data/lib/roast/dsl/system_cogs/call.rb +162 -0
  134. data/lib/roast/dsl/system_cogs/map.rb +448 -0
  135. data/lib/roast/dsl/system_cogs/repeat.rb +242 -0
  136. data/lib/roast/dsl/workflow.rb +123 -0
  137. data/lib/roast/dsl/workflow_context.rb +20 -0
  138. data/lib/roast/dsl/workflow_params.rb +24 -0
  139. data/lib/roast/sorbet_runtime_stub.rb +154 -0
  140. data/lib/roast/tools/apply_diff.rb +1 -3
  141. data/lib/roast/tools/cmd.rb +4 -3
  142. data/lib/roast/tools/read_file.rb +1 -1
  143. data/lib/roast/tools/update_files.rb +1 -1
  144. data/lib/roast/tools/write_file.rb +1 -1
  145. data/lib/roast/version.rb +1 -1
  146. data/lib/roast/workflow/base_workflow.rb +4 -0
  147. data/lib/roast/workflow/step_loader.rb +14 -2
  148. data/lib/roast-ai.rb +4 -0
  149. data/lib/roast.rb +60 -22
  150. data/{roast.gemspec → roast-ai.gemspec} +10 -13
  151. data/sorbet/config +1 -0
  152. data/sorbet/rbi/gems/async@2.34.0.rbi +1577 -0
  153. data/sorbet/rbi/gems/cli-kit@5.2.0.rbi +2063 -0
  154. data/sorbet/rbi/gems/{cli-ui@2.3.0.rbi → cli-ui@2.7.0-6bdefd1d06305e5d6ae312ac76f9c88f88658dda.rbi} +1418 -1013
  155. data/sorbet/rbi/gems/console@1.34.2.rbi +1193 -0
  156. data/sorbet/rbi/gems/fiber-annotation@0.2.0.rbi +50 -0
  157. data/sorbet/rbi/gems/fiber-local@1.1.0.rbi +35 -0
  158. data/sorbet/rbi/gems/fiber-storage@1.0.1.rbi +41 -0
  159. data/sorbet/rbi/gems/io-event@1.14.0.rbi +724 -0
  160. data/sorbet/rbi/gems/marcel@1.1.0.rbi +239 -0
  161. data/sorbet/rbi/gems/metrics@0.15.0.rbi +9 -0
  162. data/sorbet/rbi/gems/ruby_llm@1.8.2.rbi +5703 -0
  163. data/sorbet/rbi/gems/traces@0.18.2.rbi +9 -0
  164. data/sorbet/rbi/shims/lib/roast/dsl/cog_input_context.rbi +1197 -0
  165. data/sorbet/rbi/shims/lib/roast/dsl/config_context.rbi +314 -2
  166. data/sorbet/rbi/shims/lib/roast/dsl/execution_context.rbi +498 -0
  167. data/sorbet/tapioca/config.yml +6 -0
  168. data/sorbet/tapioca/require.rb +2 -0
  169. metadata +198 -34
  170. data/dsl/less_simple.rb +0 -112
  171. data/dsl/simple.rb +0 -8
  172. data/lib/roast/dsl/cog_execution_context.rb +0 -29
  173. data/lib/roast/dsl/cogs/graph.rb +0 -53
  174. data/lib/roast/dsl/cogs.rb +0 -65
  175. data/lib/roast/dsl/executor.rb +0 -82
  176. data/lib/roast/dsl/workflow_execution_context.rb +0 -47
  177. data/sorbet/rbi/gems/cgi@0.5.0.rbi +0 -2961
  178. data/sorbet/rbi/gems/claude_swarm@0.1.19.rbi +0 -568
  179. data/sorbet/rbi/gems/cli-kit@5.0.1.rbi +0 -1991
  180. data/sorbet/rbi/gems/dry-configurable@1.3.0.rbi +0 -672
  181. data/sorbet/rbi/gems/dry-core@1.1.0.rbi +0 -1894
  182. data/sorbet/rbi/gems/dry-inflector@1.2.0.rbi +0 -659
  183. data/sorbet/rbi/gems/dry-initializer@3.2.0.rbi +0 -781
  184. data/sorbet/rbi/gems/dry-logic@1.6.0.rbi +0 -1127
  185. data/sorbet/rbi/gems/dry-schema@1.14.1.rbi +0 -3727
  186. data/sorbet/rbi/gems/dry-types@1.8.3.rbi +0 -3969
  187. data/sorbet/rbi/gems/fast-mcp-annotations@1.5.3.rbi +0 -1588
  188. data/sorbet/rbi/gems/mime-types-data@3.2025.0617.rbi +0 -136
  189. data/sorbet/rbi/gems/mime-types@3.7.0.rbi +0 -1342
  190. data/sorbet/rbi/gems/rack@2.2.18.rbi +0 -5659
  191. data/sorbet/rbi/gems/rbs-inline@0.12.0.rbi +0 -2170
  192. data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +0 -435
  193. data/sorbet/rbi/gems/yard@0.9.37.rbi +0 -18492
  194. data/sorbet/rbi/shims/lib/roast/dsl/workflow_execution_context.rbi +0 -11
data/README_LEGACY.md ADDED
@@ -0,0 +1,1464 @@
1
+ ![roast-horiz-logo](https://github.com/user-attachments/assets/f9b1ace2-5478-4f4a-ac8e-5945ed75c5b4)
2
+
3
+ # Roast
4
+
5
+ > **📢 Code Freeze Notice**: This project is currently under a limited code freeze while we work on an alternative frontend (a DSL). We are still accepting **bug fixes**, but **new features** will not be merged until the DSL is released. Thank you for your understanding!
6
+
7
+ A convention-oriented framework for creating structured AI workflows, maintained by the Augmented Engineering team at Shopify.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ $ gem install roast-ai
13
+ ```
14
+
15
+ Or add to your Gemfile:
16
+
17
+ ```ruby
18
+ gem 'roast-ai'
19
+ ```
20
+
21
+ ## Why you should use Roast
22
+
23
+ Roast provides a structured, declarative approach to building AI workflows with:
24
+
25
+ - **Convention over configuration**: Define powerful workflows using simple YAML configuration files and prompts written in markdown (with ERB support)
26
+ - **Built-in tools**: Ready-to-use tools for file operations, search, and AI interactions
27
+ - **Ruby integration**: When prompts aren't enough, write custom steps in Ruby using a clean, extensible architecture
28
+ - **Shared context**: Each step shares its conversation transcript with its parent workflow by default
29
+ - **Step customization**: Steps can be fully configured with their own AI models and parameters.
30
+ - **Session replay**: Rerun previous sessions starting at a specified step to speed up development time
31
+ - **Parallel execution**: Run multiple steps concurrently to speed up workflow execution
32
+ - **Function caching**: Flexibly cache the results of tool function calls to speed up workflows
33
+ - **Extensive instrumentation**: Monitor and track workflow execution, AI calls, and tool usage ([see instrumentation documentation](docs/INSTRUMENTATION.md))
34
+
35
+ ## What does it look like?
36
+
37
+ Here's a simple workflow that analyzes test files:
38
+
39
+ ```yaml
40
+ name: analyze_tests
41
+ # Default model for all steps
42
+ model: gpt-4o-mini
43
+ tools:
44
+ - Roast::Tools::ReadFile
45
+ - Roast::Tools::Grep
46
+
47
+ steps:
48
+ - read_test_file
49
+ - analyze_coverage
50
+ - generate_report
51
+
52
+ # Step-specific model overrides the global model
53
+ analyze_coverage:
54
+ model: gpt-4-turbo
55
+ json: true
56
+
57
+ # Step-specific config that specifies a custom path, not in the current directory
58
+ generate_report:
59
+ path: ../reporting/generate_report
60
+ ```
61
+
62
+ Each step can have its own prompt file (e.g., `analyze_coverage/prompt.md`) and configuration. Steps can be run in parallel by nesting them in arrays:
63
+
64
+ ```yaml
65
+ steps:
66
+ - prepare_data
67
+ -
68
+ - analyze_code_quality
69
+ - check_test_coverage
70
+ - verify_documentation
71
+ - generate_final_report
72
+ ```
73
+
74
+ Workflows can include steps that run bash commands (wrap in `$()`), use interpolation with `{{}}` syntax, and even simple inlined prompts as a natural language string.
75
+
76
+ ```yaml
77
+ steps:
78
+ - analyze_spec
79
+ - create_minitest
80
+ - run_and_improve
81
+ - $(bundle exec rubocop -A {{file}})
82
+ - Summarize the changes made to {{File.basename(file)}}.
83
+ ```
84
+
85
+ ## Try it
86
+
87
+ If you don’t have one already, get an OpenAI key from [here](https://platform.openai.com/settings/organization/api-keys). You will need an account with a credit card and credits applied to the associated project. Make sure that a basic completion works:
88
+
89
+ ```bash
90
+ export OPENAI_API_KEY=sk-proj-....
91
+
92
+ curl -H "Content-Type: application/json" \
93
+ -H "Authorization: Bearer $OPENAI_API_KEY" \
94
+ -d '{"model":"gpt-4.1-mini","messages":[{"role":"user","content":"What is 1+1?"}]}' \
95
+ https://api.openai.com/v1/chat/completions
96
+ ```
97
+
98
+ The [test grading workflow](examples/grading/workflow.md) in this repository is a senior software engineer and testing expert that evaluates the quality of a test based on guidelines.
99
+
100
+ Try the workflow.
101
+
102
+ ```bash
103
+ ./exe/roast execute examples/grading/workflow.yml test/roast/resources_test.rb
104
+
105
+ 🔥🔥🔥 Everyone loves a good roast 🔥🔥🔥
106
+ ...
107
+ ```
108
+
109
+ This will output a test grade.
110
+
111
+ ```
112
+ ========== TEST GRADE REPORT ==========
113
+ Test file: test/roast/resources_test.rb
114
+
115
+ FINAL GRADE:
116
+ Score: 80/100
117
+ Letter Grade: B
118
+ ```
119
+ Note that you may also need `shadowenv` and `rg`, on MacOS run `brew install shadowenv` and `brew install rg`.
120
+
121
+ ## How to use Roast
122
+
123
+ 1. Create a workflow YAML file defining your steps and tools
124
+ 2. Create prompt files for each step (e.g., `step_name/prompt.md`)
125
+ 3. Run the workflow:
126
+
127
+ ```bash
128
+ # With a target file
129
+ roast execute workflow.yml target_file.rb
130
+
131
+ # Or for a targetless workflow (API calls, data generation, etc.)
132
+ roast execute workflow.yml
133
+
134
+ # Roast will automatically search in `project_root/roast/workflow_name` if the path is incomplete.
135
+ roast execute my_cool_workflow # Equivalent to `roast execute roast/my_cool_workflow/workflow.yml
136
+ ```
137
+
138
+ ### Understanding Workflows
139
+
140
+ In Roast, workflows maintain a single conversation with the AI model throughout execution. Each step represents one or more user-assistant interactions within this conversation, with optional tool calls. Steps naturally build upon each other through the shared context.
141
+
142
+ #### Step Types
143
+
144
+ Roast supports several types of steps:
145
+
146
+ 1. **Standard step**: References a directory containing at least a `prompt.md` and optional `output.txt` template. This is the most common type of step.
147
+ ```yaml
148
+ steps:
149
+ - analyze_code
150
+ ```
151
+
152
+ As an alternative to a directory, you can also implement a custom step as a Ruby class, optionally extending `Roast::Workflow::BaseStep`.
153
+
154
+ In the example given above, the script would live at `workflow/analyze_code.rb` and should contain a class named `AnalyzeCode` with an initializer that takes a workflow object as context, and a `call` method that will be invoked to run the step. The result of the `call` method will be stored in the `workflow.output` hash.
155
+
156
+
157
+ 2. **Parallel steps**: Groups of steps executed concurrently
158
+ ```yaml
159
+ steps:
160
+ -
161
+ - analyze_code_quality
162
+ - check_test_coverage
163
+ ```
164
+
165
+ 3. **Command execution step**: Executes shell commands directly, just wrap in `$(expr)`
166
+ ```yaml
167
+ steps:
168
+ - $(command line expr)
169
+ - rubocop: $(bundle exec rubocop -A)
170
+ ```
171
+ This will execute the command and store the result in the workflow output hash. Explicit key name is optional (`rubocop` in the second line of the example).
172
+
173
+ By default, commands that exit with non-zero status will halt the workflow. You can configure steps to continue on error:
174
+ ```yaml
175
+ steps:
176
+ - lint_check: $(rubocop {{file}})
177
+ - fix_issues
178
+
179
+ # Step configuration
180
+ lint_check:
181
+ exit_on_error: false # Continue workflow even if command fails
182
+ ```
183
+ When `exit_on_error: false`, the command output will include the exit status, allowing subsequent steps to process error information.
184
+
185
+ 4. **Conditional steps**: Execute different steps based on conditions using `if/unless`
186
+ ```yaml
187
+ steps:
188
+ - check_environment:
189
+ if: "{{ENV['RAILS_ENV'] == 'production'}}"
190
+ then:
191
+ - run_production_checks
192
+ - notify_team
193
+ else:
194
+ - run_development_setup
195
+
196
+ - verify_dependencies:
197
+ unless: "$(bundle check)"
198
+ then:
199
+ - bundle_install: "$(bundle install)"
200
+ ```
201
+
202
+ Conditions can be:
203
+ - Ruby expressions: `if: "{{output['count'] > 5}}"`
204
+ - Bash commands: `if: "$(test -f config.yml && echo true)"` (exit code 0 = true)
205
+ - Step references: `if: "previous_step_name"` (uses the step's output)
206
+ - Direct values: `if: "true"` or `if: "false"`
207
+
208
+ 5. **Iteration steps**: Loop over collections or repeat steps with conditions
209
+ ```yaml
210
+ steps:
211
+ # Loop over a collection
212
+ - process_files:
213
+ each: "{{Dir.glob('**/*.rb')}}"
214
+ as: current_file
215
+ steps:
216
+ - analyze_file
217
+ - Generate a report for {{current_file}}
218
+
219
+ # Repeat until a condition is met
220
+ - improve_code:
221
+ repeat:
222
+ until: "{{output['test_pass'] == true}}"
223
+ max_iterations: 5
224
+ steps:
225
+ - run_tests
226
+ - fix_issues
227
+ ```
228
+
229
+ Each loops support:
230
+ - Collections from Ruby expressions: `each: "{{[1, 2, 3]}}"`
231
+ - Command output: `each: "$(ls *.rb)"`
232
+ - Step references: `each: "file_list"`
233
+
234
+ Repeat loops support:
235
+ - Until conditions: `until: "{{condition}}"`
236
+ - Maximum iterations: `max_iterations: 10`
237
+
238
+ 6. **Case/when/else steps**: Select different execution paths based on a value (similar to Ruby's case statement)
239
+ ```yaml
240
+ steps:
241
+ - detect_language
242
+
243
+ - case: "{{ workflow.output.detect_language }}"
244
+ when:
245
+ ruby:
246
+ - lint_with_rubocop
247
+ - test_with_rspec
248
+ javascript:
249
+ - lint_with_eslint
250
+ - test_with_jest
251
+ python:
252
+ - lint_with_pylint
253
+ - test_with_pytest
254
+ else:
255
+ - analyze_generic
256
+ - generate_basic_report
257
+ ```
258
+
259
+ Case expressions can be:
260
+ - Workflow outputs: `case: "{{ workflow.output.variable }}"`
261
+ - Ruby expressions: `case: "{{ count > 10 ? 'high' : 'low' }}"`
262
+ - Bash commands: `case: "$(echo $ENVIRONMENT)"`
263
+ - Direct values: `case: "production"`
264
+
265
+ The value is compared against each key in the `when` clause, and matching steps are executed.
266
+ If no match is found, the `else` steps are executed (if provided).
267
+
268
+ 7. **Raw prompt step**: Simple text prompts for the model without tools
269
+ ```yaml
270
+ steps:
271
+ - Summarize the changes made to the codebase.
272
+ ```
273
+ This creates a simple prompt-response interaction without tool calls or looping. It's detected by the presence of spaces in the step name and is useful for summarization or simple questions at the end of a workflow.
274
+
275
+ 8. **Agent step**: Direct pass-through to coding agents (e.g., Claude Code)
276
+ ```yaml
277
+ steps:
278
+ - ^fix_linting_errors # File-based agent prompt
279
+ - ^Review the code and identify any performance issues # Inline agent prompt
280
+ - regular_analysis # Normal step through LLM
281
+ ```
282
+ Agent steps are prefixed with `^` and send the prompt content directly to the CodingAgent tool without LLM translation. This is useful when you want to give precise instructions to a coding agent without the intermediate interpretation layer. Agent steps support both file-based prompts (`fix_linting_errors/prompt.md`) and inline prompts (text with spaces).
283
+
284
+ **Session continuity for agent steps:**
285
+
286
+ Agent steps support two options for maintaining Claude context across steps:
287
+
288
+ 1. **`continue: true`** - Continues from the immediately previous Claude Code session (note, if multiple Claude Code sessions are being run in parallel in the same working directory, this might not be the previous Claude Code session from this workflow)
289
+ 2. **`resume: step_name`** - Resumes from a specific earlier step's Claude Code session
290
+
291
+ **Continue option:**
292
+
293
+ The `continue` option allows sequential agent steps to maintain a continuous conversation:
294
+
295
+ ```yaml
296
+ steps:
297
+ - ^analyze_codebase
298
+ - ^implement_feature
299
+ - ^add_tests
300
+
301
+ # Configuration
302
+ analyze_codebase:
303
+ continue: false # Start fresh (default)
304
+
305
+ implement_feature:
306
+ continue: true # Continue from immediately previous analyze_codebase step
307
+
308
+ add_tests:
309
+ continue: true # Continue from immediately previous implement_feature step
310
+ ```
311
+
312
+ **Resume functionality for agent steps:**
313
+
314
+ Agent steps can resume from specific previous Claude Code sessions:
315
+
316
+ ```yaml
317
+ steps:
318
+ - ^analyze_codebase
319
+ - ^implement_feature
320
+ - ^polish_implementation
321
+
322
+ # Configuration
323
+ analyze_codebase:
324
+ continue: false # Start fresh
325
+
326
+ implement_feature:
327
+ continue: true # Continue from previous conversation
328
+
329
+ polish_implementation:
330
+ resume: analyze_codebase # Resume from a specific step's session not the immediately previous one
331
+ ```
332
+
333
+ Note: Session IDs are only available when the CodingAgent is configured to output JSON format (includes `--output-format stream-json` in the command). If you are using a custom CodingAgent command that does not produce JSON output, resume functionality will not be available.
334
+
335
+ If `resume` is specified but the step name given does not have CodingAgent session to resume from, the CodingAgent will start Claude Code with a fresh session.
336
+
337
+ 9. **Shell script step**: Execute shell scripts directly as workflow steps
338
+ ```yaml
339
+ steps:
340
+ - setup_environment # Executes setup_environment.sh
341
+ - run_tests # Executes run_tests.sh
342
+ - cleanup
343
+ ```
344
+
345
+ Shell script steps allow you to execute `.sh` files directly as workflow steps alongside Ruby steps and AI prompts. Scripts are automatically discovered in the same locations as other step types.
346
+
347
+ **Configuration options:**
348
+ ```yaml
349
+ # Step configuration
350
+ my_script:
351
+ json: true # Parse stdout as JSON
352
+ exit_on_error: false # Don't fail workflow on non-zero exit
353
+ env: # Custom environment variables
354
+ CUSTOM_VAR: "value"
355
+ ```
356
+
357
+ **Environment integration:** Shell scripts automatically receive workflow context:
358
+ - `ROAST_WORKFLOW_RESOURCE`: Current workflow resource
359
+ - `ROAST_STEP_NAME`: Current step name
360
+ - `ROAST_WORKFLOW_OUTPUT`: Previous step outputs as JSON
361
+
362
+ **Example script (`setup_environment.sh`):**
363
+ ```bash
364
+ #!/bin/bash
365
+ echo "Setting up environment for: $ROAST_WORKFLOW_RESOURCE"
366
+
367
+ # Create a config file that subsequent steps can use
368
+ mkdir -p tmp
369
+ echo "DATABASE_URL=sqlite://test.db" > tmp/config.env
370
+
371
+ # Output data for the workflow (available via ROAST_WORKFLOW_OUTPUT in later steps)
372
+ echo '{"status": "configured", "database": "sqlite://test.db", "config_file": "tmp/config.env"}'
373
+ ```
374
+
375
+ 10. **Input step**: Interactive prompts for user input during workflow execution
376
+ ```yaml
377
+ steps:
378
+ - analyze_code
379
+ - input:
380
+ name: get_user_feedback
381
+ prompt: "Should we proceed with the refactoring? (yes/no)"
382
+ type: confirm
383
+ - input:
384
+ name: review_changes
385
+ prompt: "Enter your review comments"
386
+ type: text
387
+ - input:
388
+ name: select_strategy
389
+ prompt: "Choose optimization strategy"
390
+ type: choice
391
+ options:
392
+ - "Performance optimization"
393
+ - "Memory optimization"
394
+ - "Code clarity"
395
+ - input:
396
+ name: api_configuration
397
+ prompt: "Enter API key"
398
+ type: password
399
+ ```
400
+
401
+ Input steps pause workflow execution to collect user input. They support several types:
402
+ - `text`: Free-form text input (default if type not specified)
403
+ - `confirm`: Yes/No confirmation prompts
404
+ - `select`: Choice from a list of options
405
+ - `password`: Masked input for sensitive data
406
+
407
+ The user's input is stored in the workflow output using the step name as the key and can be accessed in subsequent steps via interpolation (e.g., `{{output.get_user_feedback}}`).
408
+
409
+ #### Step Configuration
410
+
411
+ Steps can be configured with various options to control their behavior:
412
+
413
+ ```yaml
414
+ steps:
415
+ - analyze_code # Simple step reference
416
+ - generate_report: # Step with configuration
417
+ model: gpt-4o # Override the global model for this step
418
+ print_response: true # Explicitly control output printing
419
+ json: true # Request JSON-formatted response
420
+ params: # Additional parameters for the API call
421
+ temperature: 0.8
422
+ ```
423
+
424
+ **Configuration options:**
425
+ - `model`: Override the workflow's default model for this specific step
426
+ - `print_response`: Control whether the step's response is included in the final output (default: `false`, except for the last step which defaults to `true` as of v0.3.1)
427
+ - `json`: Request a JSON-formatted response from the model
428
+ - `params`: Additional parameters passed to the model API (temperature, max_tokens, etc.)
429
+ - `path`: Custom directory path for the step's prompt files
430
+ - `coerce_to`: Type coercion for the step result (`:boolean`, `:llm_boolean`, `:iterable`)
431
+
432
+ **Automatic Last Step Output**: As of version 0.3.1, the last step in a workflow automatically has `print_response: true` unless explicitly configured otherwise. This ensures that newcomers to Roast see output from their workflows by default.
433
+
434
+ #### Shared Configuration
435
+
436
+ Roast supports sharing common configuration and steps across multiple workflows using a `shared.yml` file.
437
+
438
+ 1. Place a `shared.yml` file one level above your workflow directory
439
+ 2. Define YAML anchors for common configurations like tools, models or steps
440
+ 3. Reference these anchors in your workflow files using YAML alias syntax
441
+
442
+ **Example structure:**
443
+ ```
444
+ my_project/
445
+ ├── shared.yml # Common configuration anchors
446
+ └── workflows/
447
+ ├── analyze_code.yml
448
+ ├── generate_docs.yml
449
+ └── test_suite.yml
450
+ ```
451
+
452
+ **Example `shared.yml`:**
453
+ ```yaml
454
+ # Define common tools
455
+ standard_tools: &standard_tools
456
+ - Roast::Tools::Grep
457
+ - Roast::Tools::ReadFile
458
+ - Roast::Tools::WriteFile
459
+ - Roast::Tools::SearchFile
460
+ ```
461
+
462
+ **Using in workflows:**
463
+ ```yaml
464
+ name: Code Analysis Workflow
465
+ tools: *standard_tools # Reference shared tools
466
+
467
+ steps:
468
+ ...
469
+ ```
470
+
471
+ #### Data Flow Between Steps
472
+
473
+ Roast handles data flow between steps in three primary ways:
474
+
475
+ 1. **Conversation Context (Implicit)**: The LLM naturally remembers the entire conversation history, including all previous prompts and responses. In most cases, this is all you need for a step to reference and build upon previous results. This is the preferred approach for most prompt-oriented workflows.
476
+
477
+ 2. **Output Hash (Explicit)**: Each step's result is automatically stored in the `workflow.output` hash using the step name as the key. This programmatic access is mainly useful when:
478
+ - You need to perform non-LLM transformations on data
479
+ - You're writing custom output logic
480
+ - You need to access specific values for presentation or logging
481
+
482
+ 3. **Interpolation (Dynamic)**: You can use `{{expression}}` syntax to inject values from the workflow context directly into step names, commands, or prompt text. For example:
483
+ ```yaml
484
+ steps:
485
+ - analyze_file
486
+ - $(rubocop -A {{file}})
487
+ - Generate a summary for {{file}}
488
+ - result_for_{{file}}: store_results
489
+ ```
490
+
491
+ Interpolation supports:
492
+ - Simple variable access: `{{file}}`, `{{resource.target}}`
493
+ - Access to step outputs: `{{output['previous_step']}}`
494
+ - Any valid Ruby expression evaluated in the workflow context: `{{File.basename(file)}}`
495
+
496
+ For typical AI workflows, the continuous conversation history provides seamless data flow without requiring explicit access to the output hash. Steps can simply refer to previous information in their prompts, and the AI model will use its memory of the conversation to provide context-aware responses. For more dynamic requirements, the interpolation syntax provides a convenient way to inject context-specific values into steps.
497
+
498
+ ### Command Line Options
499
+
500
+ #### Basic Options
501
+ - `-o, --output FILE`: Save results to a file instead of outputting to STDOUT
502
+ - `-c, --concise`: Use concise output templates (exposed as a boolean flag on `workflow`)
503
+ - `-v, --verbose`: Show output from all steps as they execute
504
+ - `-r, --replay STEP_NAME`: Resume a workflow from a specific step, optionally with a specific session timestamp
505
+ - `-f, --file-storage`: Use filesystem storage for sessions instead of SQLite (default: SQLite)
506
+
507
+ #### Workflow Validation
508
+
509
+ Roast provides a `validate` command to check workflow configuration files for errors and potential issues before execution:
510
+
511
+ ```bash
512
+ # Validate a specific workflow
513
+ roast validate workflow.yml
514
+
515
+ # Validate a workflow in a subdirectory
516
+ roast validate my_workflow
517
+
518
+ # Validate with strict mode (treats warnings as errors)
519
+ roast validate workflow.yml --strict
520
+ ```
521
+
522
+ The validator checks for:
523
+ - YAML syntax errors
524
+ - Missing required fields
525
+ - Invalid step references
526
+ - Circular dependencies
527
+ - Tool availability
528
+ - Prompt file existence
529
+ - Configuration consistency
530
+
531
+ This helps catch configuration errors early and ensures workflows will run smoothly.
532
+
533
+ #### Session Storage and Management
534
+
535
+ Roast uses SQLite by default for session storage, providing better performance and advanced querying capabilities. Sessions are automatically saved during workflow execution, capturing each step's state including conversation transcripts and outputs.
536
+
537
+ **Storage Options:**
538
+
539
+ ```bash
540
+ # Use default SQLite storage (recommended)
541
+ roast execute workflow.yml
542
+
543
+ # Use legacy filesystem storage
544
+ roast execute workflow.yml --file-storage
545
+
546
+ # Set storage type via environment variable
547
+ ROAST_STATE_STORAGE=file roast execute workflow.yml
548
+ ```
549
+
550
+ **Session Management Commands:**
551
+
552
+ ```bash
553
+ # List all sessions
554
+ roast sessions
555
+
556
+ # Filter sessions by status
557
+ roast sessions --status waiting
558
+
559
+ # Filter sessions by workflow
560
+ roast sessions --workflow my_workflow
561
+
562
+ # Show sessions older than 7 days
563
+ roast sessions --older-than 7d
564
+
565
+ # Clean up old sessions
566
+ roast sessions --cleanup --older-than 30d
567
+
568
+ # View detailed session information
569
+ roast session <session_id>
570
+ ```
571
+
572
+ #### Session Replay
573
+
574
+ The session replay feature allows you to resume workflows from specific steps, saving time during development and debugging:
575
+
576
+ ```bash
577
+ # Resume from a specific step
578
+ roast execute workflow.yml -r step_name
579
+
580
+ # Resume from a specific step in a specific session
581
+ roast execute workflow.yml -r 20250507_123456_789:step_name
582
+ ```
583
+
584
+ This feature is particularly useful when:
585
+ - Debugging specific steps in a long workflow
586
+ - Iterating on prompts without rerunning the entire workflow
587
+ - Resuming after failures in long-running workflows
588
+
589
+ **Storage Locations:**
590
+ - SQLite: `~/.roast/sessions.db` (configurable via `ROAST_SESSIONS_DB`)
591
+ - Filesystem: `.roast/sessions/` directory in your project
592
+
593
+ #### Target Option (`-t, --target`)
594
+
595
+ The target option is highly flexible and accepts several formats:
596
+
597
+ **Single file path:**
598
+ ```bash
599
+ roast execute workflow.yml -t path/to/file.rb
600
+
601
+ # is equivalent to
602
+ roast execute workflow.yml path/to/file.rb
603
+ ```
604
+
605
+ **Directory path:**
606
+ ```bash
607
+ roast execute workflow.yml -t path/to/directory
608
+
609
+ # Roast will run on the directory as a resource
610
+ ```
611
+
612
+ **Glob patterns:**
613
+ ```bash
614
+ roast execute workflow.yml -t "**/*_test.rb"
615
+
616
+ # Roast will run the workflow on each matching file
617
+ ```
618
+
619
+ **URL as target:**
620
+ ```bash
621
+ roast execute workflow.yml -t "https://api.example.com/data"
622
+
623
+ # Roast will run the workflow using the URL as a resource
624
+ ```
625
+
626
+ **API configuration (Fetch API-style):**
627
+ ```bash
628
+ roast execute workflow.yml -t '{
629
+ "url": "https://api.example.com/resource",
630
+ "options": {
631
+ "method": "POST",
632
+ "headers": {
633
+ "Content-Type": "application/json",
634
+ "Authorization": "Bearer ${API_TOKEN}"
635
+ },
636
+ "body": {
637
+ "query": "search term",
638
+ "limit": 10
639
+ }
640
+ }
641
+ }'
642
+
643
+ # Roast will recognize this as an API configuration with Fetch API-style format
644
+ ```
645
+
646
+ **Shell command execution with $(...):**
647
+ ```bash
648
+ roast execute workflow.yml -t "$(find . -name '*.rb' -mtime -1)"
649
+
650
+ # Roast will run the workflow on each file returned (expects one per line)
651
+ ```
652
+
653
+ **Git integration examples:**
654
+ ```bash
655
+ # Process changed test files
656
+ roast execute workflow.yml -t "$(git diff --name-only HEAD | grep _test.rb)"
657
+
658
+ # Process staged files
659
+ roast execute workflow.yml -t "$(git diff --cached --name-only)"
660
+ ```
661
+
662
+ #### Targetless Workflows
663
+
664
+ Roast also supports workflows that don't operate on a specific pre-defined set of target files:
665
+
666
+ **API-driven workflows:**
667
+ ```yaml
668
+ name: API Integration Workflow
669
+ tools:
670
+ - Roast::Tools::ReadFile
671
+ - Roast::Tools::WriteFile
672
+
673
+ # Dynamic API token using shell command
674
+ api_token: $(cat ~/.my_token)
675
+
676
+ # Option 1: Use a targetless workflow with API logic in steps
677
+ steps:
678
+ - fetch_api_data # Step will make API calls
679
+ - transform_data
680
+ - generate_report
681
+
682
+ # Option 2: Specify an API target directly in the workflow
683
+ target: '{
684
+ "url": "https://api.example.com/resource",
685
+ "options": {
686
+ "method": "GET",
687
+ "headers": {
688
+ "Authorization": "Bearer ${API_TOKEN}"
689
+ }
690
+ }
691
+ }'
692
+
693
+ steps:
694
+ - process_api_response
695
+ - generate_report
696
+ ```
697
+
698
+ **Data generation workflows:**
699
+ ```yaml
700
+ name: Generate Documentation
701
+ tools:
702
+ - Roast::Tools::WriteFile
703
+ steps:
704
+ - generate_outline
705
+ - write_documentation
706
+ - create_examples
707
+ ```
708
+
709
+ These targetless workflows are ideal for:
710
+ - API integrations
711
+ - Content generation
712
+ - Report creation
713
+ - Interactive tools
714
+ - Scheduled automation tasks
715
+
716
+ #### Global Model Configuration
717
+
718
+ You can set a default model for all steps in your workflow by specifying the `model` parameter at the top level:
719
+
720
+ ```yaml
721
+ name: My Workflow
722
+ model: gpt-4o-mini # Will be used for all steps unless overridden
723
+ ```
724
+
725
+ Individual steps can override this setting with their own model parameter:
726
+
727
+ ```yaml
728
+ analyze_data:
729
+ model: anthropic/claude-3-haiku # Takes precedence over the global model
730
+ ```
731
+
732
+ #### API Provider Configuration
733
+
734
+ Roast supports both OpenAI and OpenRouter as API providers. By default, Roast uses OpenAI, but you can specify OpenRouter:
735
+
736
+ ```yaml
737
+ name: My Workflow
738
+ api_provider: openrouter
739
+ api_token: $(echo $OPENROUTER_API_KEY)
740
+ model: anthropic/claude-3-opus-20240229
741
+ ```
742
+
743
+ Benefits of using OpenRouter:
744
+ - Access to multiple model providers through a single API
745
+ - Support for models from Anthropic, Meta, Mistral, and more
746
+ - Consistent API interface across different model providers
747
+
748
+ When using OpenRouter, specify fully qualified model names including the provider prefix (e.g., `anthropic/claude-3-opus-20240229`).
749
+
750
+ #### Dynamic API Tokens and URIs
751
+
752
+ Roast allows you to dynamically fetch attributes such as API token and URI base (to use with a proxy) via shell commands directly in your workflow configuration:
753
+
754
+ ```yaml
755
+ # This will execute the shell command and use the result as the API token
756
+ api_token: $(print-token --key)
757
+
758
+ # For OpenAI (default)
759
+ api_token: $(echo $OPENAI_API_KEY)
760
+
761
+ # For OpenRouter (requires api_provider setting)
762
+ api_provider: openrouter
763
+ api_token: $(echo $OPENROUTER_API_KEY)
764
+
765
+ # Static Proxy URI
766
+ uri_base: https://proxy.example.com/v1
767
+
768
+ # Dynamic Proxy URI
769
+ uri_base: $(echo $AI_PROXY_URI_BASE)
770
+ ```
771
+
772
+ This makes it easy to use environment-specific tokens without hardcoding credentials, especially useful in development environments or CI/CD pipelines. Alternatively, Roast will fall back to `OPENROUTER_API_KEY` or `OPENAI_API_KEY` environment variables based on the specified provider.
773
+
774
+
775
+ ### Template Output with ERB
776
+
777
+ Each step can have an `output.txt` file that uses ERB templating to format the final output. This allows you to customize how the AI's response is processed and displayed.
778
+
779
+ Example `step_name/output.txt`:
780
+ ```erb
781
+ <% if workflow.verbose %>
782
+ Detailed Analysis:
783
+ <%= response %>
784
+ <% else %>
785
+ Summary: <%= response.lines.first %>
786
+ <% end %>
787
+
788
+ Files analyzed: <%= workflow.file %>
789
+ Status: <%= workflow.output['status'] || 'completed' %>
790
+ ```
791
+
792
+ This is an example of where the `workflow.output` hash is useful - formatting output for display based on data from previous steps.
793
+
794
+ Available in templates:
795
+ - `response`: The AI's response for this step
796
+ - `workflow`: Access to the workflow object
797
+ - `workflow.output`: The shared hash containing results from all steps when you need programmatic access
798
+ - `workflow.file`: Current file being processed (or `nil` for targetless workflows)
799
+ - All workflow configuration options
800
+
801
+ For most workflows, you'll mainly use `response` to access the current step's results. The `workflow.output` hash becomes valuable when you need to reference specific data points from previous steps in your templates or for conditional display logic.
802
+
803
+ ## Advanced Features
804
+
805
+ ### Workflow Metadata
806
+
807
+ Roast workflows maintain a metadata store that allows steps to share structured data beyond the standard output hash. This is particularly useful for tracking state that needs to persist across steps but shouldn't be part of the conversation context.
808
+
809
+ #### Setting Metadata
810
+
811
+ Metadata can be set by custom Ruby steps that extend `BaseStep`:
812
+
813
+ ```ruby
814
+ # workflow/analyze_codebase.rb
815
+ class AnalyzeCodebase < Roast::Workflow::BaseStep
816
+ include Roast::Helpers::MetadataAccess
817
+
818
+ def call
819
+ # Perform analysis
820
+ analysis_results = perform_deep_analysis
821
+
822
+ # Store metadata for other steps to use
823
+ workflow.metadata[name.to_s] ||= {}
824
+ workflow.metadata[name.to_s]["total_files"] = analysis_results[:file_count]
825
+ workflow.metadata[name.to_s]["complexity_score"] = analysis_results[:complexity]
826
+ workflow.metadata[name.to_s]["analysis_id"] = SecureRandom.uuid
827
+
828
+ # Return the normal output for the conversation
829
+ "Analyzed #{analysis_results[:file_count]} files with average complexity of #{analysis_results[:complexity]}"
830
+ end
831
+
832
+ private
833
+
834
+ def perform_deep_analysis
835
+ # Your analysis logic here
836
+ { file_count: 42, complexity: 7.5 }
837
+ end
838
+ end
839
+ ```
840
+
841
+ #### Accessing Metadata
842
+
843
+ Metadata from previous steps can be accessed in:
844
+
845
+ 1. **Custom Ruby steps:**
846
+ ```ruby
847
+ class GenerateReport < Roast::Workflow::BaseStep
848
+ def call
849
+ # Access metadata from a previous step
850
+ total_files = workflow.metadata.dig("analyze_codebase", "total_files")
851
+ complexity = workflow.metadata.dig("analyze_codebase", "complexity_score")
852
+
853
+ "Generated report for #{total_files} files with complexity score: #{complexity}"
854
+ end
855
+ end
856
+ ```
857
+
858
+ 2. **Workflow configuration via interpolation:**
859
+ ```yaml
860
+ steps:
861
+ - analyze_codebase
862
+ - validate_threshold
863
+ - generate_report
864
+
865
+ # Use metadata in step configuration
866
+ validate_threshold:
867
+ if: "{{metadata.analyze_codebase.complexity_score > 8.0}}"
868
+ then:
869
+ - send_alert
870
+ - create_ticket
871
+ else:
872
+ - mark_as_passed
873
+
874
+ # Pass metadata to command steps
875
+ send_alert:
876
+ $(slack-notify "High complexity detected: {{metadata.analyze_codebase.complexity_score}}")
877
+ ```
878
+
879
+ 3. **Prompt templates (ERB):**
880
+ ```erb
881
+ # In analyze_codebase/output.txt
882
+ Analysis Summary:
883
+ Files analyzed: <%= workflow.metadata.dig(name.to_s, "total_files") %>
884
+ Complexity score: <%= workflow.metadata.dig(name.to_s, "complexity_score") %>
885
+ Analysis ID: <%= workflow.metadata.dig(name.to_s, "analysis_id") %>
886
+ ```
887
+
888
+ #### Metadata Best Practices
889
+
890
+ - **Use metadata for data that shouldn't be in the conversation**
891
+ - **Don't duplicate output data:** Metadata complements the output hash, it doesn't replace it
892
+
893
+ The metadata system is particularly useful for:
894
+ - Tracking session or transaction IDs across multiple steps
895
+ - Storing configuration or state that tools need to access
896
+ - Passing data between steps without cluttering the AI conversation
897
+ - Implementing complex conditional logic based on computed values
898
+
899
+ ### Instrumentation
900
+
901
+ Roast provides extensive instrumentation capabilities using ActiveSupport::Notifications. You can monitor workflow execution, track AI model usage, measure performance, and integrate with external monitoring systems. [Read the full instrumentation documentation](docs/INSTRUMENTATION.md).
902
+
903
+ ### Built-in Tools
904
+
905
+ Roast provides several built-in tools that you can use in your workflows:
906
+
907
+ #### Tool Configuration
908
+
909
+ Tools can be configured using a hash format in your workflow YAML:
910
+
911
+ ```yaml
912
+ tools:
913
+ - Roast::Tools::ReadFile # No configuration needed
914
+ - Roast::Tools::Cmd: # With configuration
915
+ allowed_commands:
916
+ - git
917
+ - npm
918
+ - yarn
919
+ - Roast::Tools::CodingAgent: # Optional configuration
920
+ coding_agent_command: claude --model opus -p --allowedTools "Bash, Glob, Grep, LS, Read"
921
+ model: opus # Model to use for all CodingAgent invocations
922
+ retries: 3 # Number of automatic retries on failure (default: 0)
923
+ ```
924
+
925
+ Currently supported configurations:
926
+ - `Roast::Tools::Cmd` via `allowed_commands`: restricts which commands can be executed (defaults to: `pwd`, `find`, `ls`, `rake`, `ruby`, `dev`, `mkdir`)
927
+ - `Roast::Tools::CodingAgent` via:
928
+ - `coding_agent_command`: customizes the Claude Code CLI command used by the agent
929
+ - `model`: sets the model for all CodingAgent invocations (e.g., `opus`, `sonnet`)
930
+ - `retries`: number of times to automatically retry if the agent encounters an error (default: 0, no retries)
931
+
932
+ ##### Cmd Tool Configuration
933
+
934
+ The `Cmd` tool's `allowed_commands` can be configured in two ways:
935
+
936
+ **1. Simple String Format** (uses default descriptions):
937
+ ```yaml
938
+ tools:
939
+ - Roast::Tools::Cmd:
940
+ allowed_commands:
941
+ - pwd
942
+ - ls
943
+ - git
944
+ ```
945
+
946
+ **2. Hash Format with Custom Descriptions**:
947
+ ```yaml
948
+ tools:
949
+ - Roast::Tools::Cmd:
950
+ allowed_commands:
951
+ - pwd
952
+ - name: git
953
+ description: "git CLI - version control system with subcommands like status, commit, push"
954
+ - name: npm
955
+ description: "npm CLI - Node.js package manager with subcommands like install, run"
956
+ - name: docker
957
+ description: "Docker CLI - container platform with subcommands like build, run, ps"
958
+ ```
959
+
960
+ Custom descriptions help the LLM understand when and how to use each command, making your workflows more effective.
961
+
962
+ ### Step-Level Tool Filtering
963
+
964
+ You can restrict which tools are available to specific steps using the `available_tools` configuration:
965
+
966
+ ```yaml
967
+ # Define all tools globally
968
+ tools:
969
+ - Roast::Tools::Grep
970
+ - Roast::Tools::ReadFile
971
+ - Roast::Tools::WriteFile
972
+ - Roast::Tools::Cmd:
973
+ allowed_commands:
974
+ - pwd
975
+ - ls
976
+ - echo
977
+
978
+ # Configure steps with specific tool access
979
+ explore_directory:
980
+ available_tools:
981
+ - pwd
982
+ - ls
983
+
984
+ analyze_files:
985
+ available_tools:
986
+ - grep
987
+ - read_file
988
+
989
+ write_summary:
990
+ available_tools:
991
+ - write_file
992
+ - echo
993
+ ```
994
+
995
+ This feature provides:
996
+ - **Security**: Each step only has access to the tools it needs
997
+ - **Performance**: Reduces the tool list sent to the LLM
998
+ - **Clarity**: Makes tool usage explicit for each step
999
+
1000
+ Key points:
1001
+ - Use snake_case tool names (e.g., `read_file` for `Roast::Tools::ReadFile`)
1002
+ - For `Cmd` tool, use the specific command names (e.g., `pwd`, `ls`)
1003
+ - When `available_tools` is not specified, all tools remain available (backward compatible)
1004
+ - Empty array (`available_tools: []`) means no tools for that step
1005
+
1006
+ See the [available_tools_demo](examples/available_tools_demo/) for a complete example.
1007
+
1008
+ #### ReadFile
1009
+
1010
+ Reads the contents of a file from the filesystem.
1011
+
1012
+ ```ruby
1013
+ # Basic usage
1014
+ read_file(path: "path/to/file.txt")
1015
+
1016
+ # Reading a specific portion of a file
1017
+ read_file(path: "path/to/large_file.txt", offset: 100, limit: 50)
1018
+ ```
1019
+
1020
+ - The `path` can be absolute or relative to the current working directory
1021
+ - Use `offset` and `limit` for large files to read specific sections (line numbers)
1022
+ - Returns the file content as a string
1023
+
1024
+ #### WriteFile
1025
+
1026
+ Writes content to a file, creating the file if it doesn't exist or overwriting it if it does.
1027
+
1028
+ ```ruby
1029
+ # Basic usage
1030
+ write_file(path: "output.txt", content: "This is the file content")
1031
+
1032
+ # With path restriction for security
1033
+ write_file(
1034
+ path: "output.txt",
1035
+ content: "Restricted content",
1036
+ restrict: "/safe/directory" # Only allows writing to files under this path
1037
+ )
1038
+ ```
1039
+
1040
+ - Creates missing directories automatically
1041
+ - Can restrict file operations to specific directories for security
1042
+ - Returns a success message with the number of lines written
1043
+
1044
+ #### UpdateFiles
1045
+
1046
+ Applies a unified diff/patch to one or more files. Changes are applied atomically when possible.
1047
+
1048
+ ```ruby
1049
+ update_files(
1050
+ diff: <<~DIFF,
1051
+ --- a/file1.txt
1052
+ +++ b/file1.txt
1053
+ @@ -1,3 +1,4 @@
1054
+ line1
1055
+ +new line
1056
+ line2
1057
+ line3
1058
+
1059
+ --- a/file2.txt
1060
+ +++ b/file2.txt
1061
+ @@ -5,7 +5,7 @@
1062
+ line5
1063
+ line6
1064
+ -old line7
1065
+ +updated line7
1066
+ line8
1067
+ DIFF
1068
+ base_path: "/path/to/project", # Optional, defaults to current working directory
1069
+ restrict_path: "/path/to/allowed", # Optional, restricts where files can be modified
1070
+ create_files: true, # Optional, defaults to true
1071
+ )
1072
+ ```
1073
+
1074
+ - Accepts standard unified diff format from tools like `git diff`
1075
+ - Supports multiple file changes in a single operation
1076
+ - Handles file creation, deletion, and modification
1077
+ - Performs atomic operations with rollback on failure
1078
+ - Includes fuzzy matching to handle minor context differences
1079
+ - This tool is especially useful for making targeted changes to multiple files at once
1080
+
1081
+ #### Grep
1082
+
1083
+ Searches file contents for a specific pattern using regular expressions.
1084
+
1085
+ ```ruby
1086
+ # Basic usage
1087
+ grep(pattern: "function\\s+myFunction")
1088
+
1089
+ # With file filtering
1090
+ grep(pattern: "class\\s+User", include: "*.rb")
1091
+
1092
+ # With directory scope
1093
+ grep(pattern: "TODO:", path: "src/components")
1094
+ ```
1095
+
1096
+ - Uses regular expressions for powerful pattern matching
1097
+ - Can filter by file types using the `include` parameter
1098
+ - Can scope searches to specific directories with the `path` parameter
1099
+ - Returns a list of files containing matches
1100
+
1101
+ #### SearchFile
1102
+
1103
+ Provides advanced file search capabilities beyond basic pattern matching.
1104
+
1105
+ ```ruby
1106
+ search_file(query: "class User", file_path: "app/models")
1107
+ ```
1108
+
1109
+ - Combines pattern matching with contextual search
1110
+ - Useful for finding specific code structures or patterns
1111
+ - Returns matched lines with context
1112
+
1113
+ #### Cmd
1114
+
1115
+ Executes shell commands with configurable restrictions. By default, only allows specific safe commands.
1116
+
1117
+ ```ruby
1118
+ # Execute allowed commands (pwd, find, ls, rake, ruby, dev, mkdir by default)
1119
+ pwd(args: "-L")
1120
+ ls(args: "-la")
1121
+ ruby(args: "-e 'puts RUBY_VERSION'")
1122
+
1123
+ # Or use the legacy cmd function with full command
1124
+ cmd(command: "ls -la")
1125
+ ```
1126
+
1127
+ - Commands are registered as individual functions based on allowed_commands configuration
1128
+ - Default allowed commands: pwd, find, ls, rake, ruby, dev, mkdir
1129
+ - Each command has built-in descriptions to help the LLM understand usage
1130
+ - Configurable via workflow YAML (see Tool Configuration section)
1131
+
1132
+ #### Bash
1133
+
1134
+ Executes shell commands without restrictions. **⚠️ WARNING: Use only in trusted environments!**
1135
+
1136
+ ```ruby
1137
+ # Execute any command - no restrictions
1138
+ bash(command: "curl https://api.example.com | jq '.data'")
1139
+
1140
+ # Complex operations with pipes and redirects
1141
+ bash(command: "find . -name '*.log' -mtime +30 -delete")
1142
+
1143
+ # System administration tasks
1144
+ bash(command: "ps aux | grep ruby | awk '{print $2}'")
1145
+ ```
1146
+
1147
+ - **No command restrictions** - full shell access
1148
+ - Designed for prototyping and development environments
1149
+ - Logs warnings by default (disable with `ROAST_BASH_WARNINGS=false`)
1150
+ - Should NOT be used in production or untrusted contexts
1151
+ - See `examples/bash_prototyping/` for usage examples
1152
+
1153
+ #### CodingAgent
1154
+
1155
+ Creates a specialized agent for complex coding tasks or long-running operations.
1156
+
1157
+ ```ruby
1158
+ # Basic usage
1159
+ coding_agent(
1160
+ prompt: "Refactor the authentication module to use JWT tokens",
1161
+ include_context_summary: true, # Include workflow context in the agent prompt
1162
+ continue: true # Continue from previous agent session
1163
+ )
1164
+
1165
+ # With automatic retries on failure
1166
+ coding_agent(
1167
+ prompt: "Implement complex feature with error handling",
1168
+ retries: 3 # Retry up to 3 times if the agent encounters errors
1169
+ )
1170
+ ```
1171
+
1172
+ - Delegates complex tasks to a specialized coding agent (Claude Code)
1173
+ - Useful for tasks that require deep code understanding or multi-step changes
1174
+ - Can work across multiple files and languages
1175
+ - Supports automatic retries on transient failures (network issues, API errors)
1176
+ - Retries can be configured globally (see Tool Configuration) or per invocation
1177
+
1178
+ ### MCP (Model Context Protocol) Tools
1179
+
1180
+ Roast supports MCP tools, allowing you to integrate external services and tools through the Model Context Protocol standard. MCP enables seamless connections to databases, APIs, and specialized tools.
1181
+
1182
+ #### Configuring MCP Tools
1183
+
1184
+ MCP tools are configured in the `tools` section of your workflow YAML alongside traditional Roast tools:
1185
+
1186
+ ```yaml
1187
+ tools:
1188
+ # Traditional Roast tools
1189
+ - Roast::Tools::ReadFile
1190
+
1191
+ # MCP tools with SSE (Server-Sent Events)
1192
+ - Documentation:
1193
+ url: https://gitmcp.io/myorg/myrepo/docs
1194
+ env:
1195
+ - "Authorization: Bearer {{ENV['API_TOKEN']}}"
1196
+
1197
+ # MCP tools with stdio
1198
+ - GitHub:
1199
+ command: npx
1200
+ args: ["-y", "@modelcontextprotocol/server-github"]
1201
+ env:
1202
+ GITHUB_PERSONAL_ACCESS_TOKEN: "{{ENV['GITHUB_TOKEN']}}"
1203
+ only:
1204
+ - search_repositories
1205
+ - get_issue
1206
+ - create_issue
1207
+ ```
1208
+
1209
+ #### SSE MCP Tools
1210
+
1211
+ Connect to HTTP endpoints implementing the MCP protocol:
1212
+
1213
+ ```yaml
1214
+ - Tool Name:
1215
+ url: https://example.com/mcp-endpoint
1216
+ env:
1217
+ - "Authorization: Bearer {{resource.api_token}}"
1218
+ only: [function1, function2] # Optional whitelist
1219
+ except: [function3] # Optional blacklist
1220
+ ```
1221
+
1222
+ #### Stdio MCP Tools
1223
+
1224
+ Connect to local processes implementing the MCP protocol:
1225
+
1226
+ ```yaml
1227
+ - Tool Name:
1228
+ command: docker
1229
+ args: ["run", "-i", "--rm", "ghcr.io/example/mcp-server"]
1230
+ env:
1231
+ API_KEY: "{{ENV['API_KEY']}}"
1232
+ ```
1233
+
1234
+ See the [MCP tools example](examples/mcp/) for complete documentation and more examples.
1235
+
1236
+ ### Custom Tools
1237
+
1238
+ You can create your own tools using the [Raix function dispatch pattern](https://github.com/OlympiaAI/raix-rails?tab=readme-ov-file#use-of-toolsfunctions). Custom tools should be placed in `.roast/initializers/` (subdirectories are supported):
1239
+
1240
+ ```ruby
1241
+ # .roast/initializers/tools/git_analyzer.rb
1242
+ module MyProject
1243
+ module Tools
1244
+ module GitAnalyzer
1245
+ extend self
1246
+
1247
+ def self.included(base)
1248
+ base.class_eval do
1249
+ function(
1250
+ :analyze_commit,
1251
+ "Analyze a git commit for code quality and changes",
1252
+ commit_sha: { type: "string", description: "The SHA of the commit to analyze" },
1253
+ include_diff: { type: "boolean", description: "Include the full diff in the analysis", default: false }
1254
+ ) do |params|
1255
+ GitAnalyzer.call(params[:commit_sha], params[:include_diff])
1256
+ end
1257
+ end
1258
+ end
1259
+
1260
+ def call(commit_sha, include_diff = false)
1261
+ Roast::Helpers::Logger.info("🔍 Analyzing commit: #{commit_sha}\n")
1262
+
1263
+ # Your implementation here
1264
+ commit_info = `git show #{commit_sha} --stat`
1265
+ commit_info += "\n\n" + `git show #{commit_sha}` if include_diff
1266
+
1267
+ commit_info
1268
+ rescue StandardError => e
1269
+ "Error analyzing commit: #{e.message}".tap do |error_message|
1270
+ Roast::Helpers::Logger.error(error_message + "\n")
1271
+ end
1272
+ end
1273
+ end
1274
+ end
1275
+ end
1276
+ ```
1277
+
1278
+ Then include your tool in the workflow:
1279
+
1280
+ ```yaml
1281
+ tools:
1282
+ - MyProject::Tools::GitAnalyzer
1283
+ ```
1284
+
1285
+ The tool will be available to the AI model during workflow execution, and it can call `analyze_commit` with the appropriate parameters.
1286
+
1287
+ ### Project-specific Configuration
1288
+
1289
+ You can extend Roast with project-specific configuration by creating initializers in `.roast/initializers/`. These are automatically loaded when workflows run, allowing you to:
1290
+
1291
+ - Add custom instrumentation
1292
+ - Configure monitoring and metrics
1293
+ - Set up project-specific tools
1294
+ - Customize workflow behavior
1295
+
1296
+ Example structure:
1297
+ ```
1298
+ your-project/
1299
+ ├── .roast/
1300
+ │ └── initializers/
1301
+ │ ├── metrics.rb
1302
+ │ ├── logging.rb
1303
+ │ └── custom_tools.rb
1304
+ └── ...
1305
+ ```
1306
+
1307
+ ### Pre/Post Processing Framework
1308
+
1309
+ Roast supports pre-processing and post-processing phases for workflows. This enables powerful workflows that need setup/teardown or result aggregation across all processed files.
1310
+
1311
+ #### Overview
1312
+
1313
+ - **Pre-processing**: Steps executed once before any targets are processed
1314
+ - **Post-processing**: Steps executed once after all targets have been processed
1315
+ - **Shared state**: Pre-processing results are available to all subsequent steps
1316
+ - **Result aggregation**: Post-processing has access to all workflow execution results
1317
+ - **Single-target support**: Pre/post processing works with single-target workflows too
1318
+ - **Output templates**: Post-processing supports `output.txt` templates for custom formatting
1319
+
1320
+ #### Configuration
1321
+
1322
+ ```yaml
1323
+ name: optimize_tests
1324
+ model: gpt-4o
1325
+ target: "test/**/*_test.rb"
1326
+
1327
+ # Pre-processing steps run once before any test files
1328
+ pre_processing:
1329
+ - gather_baseline_metrics
1330
+ - setup_test_environment
1331
+
1332
+ # Main workflow steps run for each test file
1333
+ steps:
1334
+ - analyze_test
1335
+ - improve_coverage
1336
+ - optimize_performance
1337
+
1338
+ # Post-processing steps run once after all test files
1339
+ post_processing:
1340
+ - aggregate_results
1341
+ - generate_report
1342
+ - cleanup_environment
1343
+ ```
1344
+
1345
+ #### Directory Structure
1346
+
1347
+ Pre and post-processing steps follow the same conventions as regular steps but are organized in their own directories:
1348
+
1349
+ ```
1350
+ workflow.yml
1351
+ pre_processing/
1352
+ ├── gather_baseline_metrics/
1353
+ │ └── prompt.md
1354
+ └── setup_test_environment/
1355
+ └── prompt.md
1356
+ analyze_test/
1357
+ └── prompt.md
1358
+ improve_coverage/
1359
+ └── prompt.md
1360
+ optimize_performance/
1361
+ └── prompt.md
1362
+ post_processing/
1363
+ ├── output.txt
1364
+ ├── aggregate_results/
1365
+ │ └── prompt.md
1366
+ ├── generate_report/
1367
+ │ └── prompt.md
1368
+ └── cleanup_environment/
1369
+ └── prompt.md
1370
+ ```
1371
+
1372
+ #### Data Access
1373
+
1374
+ **Pre-processing results in target workflows:**
1375
+
1376
+ Target workflows have access to pre-processing results through the `pre_processing_data` variable with dot notation:
1377
+
1378
+ ```erb
1379
+ # In a target workflow step prompt
1380
+ The baseline metrics from pre-processing:
1381
+ <%= pre_processing_data.gather_baseline_metrics %>
1382
+
1383
+ Environment setup details:
1384
+ <%= pre_processing_data.setup_test_environment %>
1385
+ ```
1386
+
1387
+ **Post-processing data access:**
1388
+
1389
+ Post-processing steps have access to:
1390
+
1391
+ - `pre_processing`: Direct access to pre-processing results with dot notation
1392
+ - `targets`: Hash of all target workflow results, keyed by file paths
1393
+
1394
+ Example post-processing prompt:
1395
+ ```markdown
1396
+ # Generate Summary Report
1397
+
1398
+ Based on the baseline metrics:
1399
+ <%= pre_processing.gather_baseline_metrics %>
1400
+
1401
+ Environment configuration:
1402
+ <%= pre_processing.setup_test_environment %>
1403
+
1404
+ And the results from processing all files:
1405
+ <% targets.each do |file, target| %>
1406
+ File: <%= file %>
1407
+ Analysis results: <%= target.output.analyze_test %>
1408
+ Coverage improvements: <%= target.output.improve_coverage %>
1409
+ Performance optimizations: <%= target.output.optimize_performance %>
1410
+ <% end %>
1411
+
1412
+ Please generate a comprehensive summary report showing:
1413
+ 1. Overall improvements achieved
1414
+ 2. Files with the most significant changes
1415
+ 3. Recommendations for further optimization
1416
+ ```
1417
+
1418
+ #### Output Templates
1419
+
1420
+ Post-processing supports custom output formatting using ERB templates. Create an `output.txt` file in your `post_processing` directory to format the final workflow output:
1421
+
1422
+ ```erb
1423
+ # post_processing/output.txt
1424
+ === Workflow Summary Report ===
1425
+ Generated at: <%= Time.now.strftime("%Y-%m-%d %H:%M:%S") %>
1426
+
1427
+ Environment: <%= pre_processing.setup_test_environment %>
1428
+
1429
+ Files Processed: <%= targets.size %>
1430
+
1431
+ <% targets.each do |file, target| %>
1432
+ - <%= file %>: <%= target.output.analyze_test %>
1433
+ <% end %>
1434
+
1435
+ <%= output.generate_report %>
1436
+ ===============================
1437
+ ```
1438
+
1439
+ The template has access to:
1440
+ - `pre_processing`: All pre-processing step outputs with dot notation
1441
+ - `targets`: Hash of all target workflow results with dot notation (each target has `.output` and `.final_output`)
1442
+ - `output`: Post-processing step outputs with dot notation
1443
+
1444
+ #### Use Cases
1445
+
1446
+ This pattern is ideal for:
1447
+
1448
+ - **Code migrations**: Setup migration tools, process files, generate migration report
1449
+ - **Test optimization**: Baseline metrics, optimize tests, aggregate improvements
1450
+ - **Documentation generation**: Analyze codebase, generate docs per module, create index
1451
+ - **Dependency updates**: Check versions, update files, verify compatibility
1452
+ - **Security audits**: Setup scanners, check each file, generate security report
1453
+ - **Performance analysis**: Establish baselines, analyze components, summarize findings
1454
+
1455
+ See the [pre/post processing example](examples/pre_post_processing) for a complete working demonstration.
1456
+
1457
+
1458
+ ## Development
1459
+
1460
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rake` to run the tests and linter. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
1461
+
1462
+ ## License
1463
+
1464
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).