roast-ai 0.4.10 → 0.5.1
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.
- checksums.yaml +4 -4
- data/.claude/commands/docs/write-comments.md +36 -0
- data/.github/CODEOWNERS +1 -1
- data/.github/workflows/ci.yaml +10 -6
- data/.gitignore +0 -1
- data/.rubocop.yml +7 -1
- data/.ruby-version +1 -1
- data/CLAUDE.md +2 -2
- data/CONTRIBUTING.md +2 -0
- data/Gemfile +19 -18
- data/Gemfile.lock +35 -58
- data/README.md +118 -1432
- data/README_LEGACY.md +1464 -0
- data/Rakefile +39 -4
- data/dev.yml +29 -0
- data/dsl/agent_sessions.rb +20 -0
- data/dsl/async_cogs.rb +49 -0
- data/dsl/async_cogs_complex.rb +67 -0
- data/dsl/call.rb +44 -0
- data/dsl/collect_from.rb +72 -0
- data/dsl/json_output.rb +28 -0
- data/dsl/map.rb +55 -0
- data/dsl/map_reduce.rb +37 -0
- data/dsl/map_with_index.rb +49 -0
- data/dsl/next_break.rb +45 -0
- data/dsl/next_break_parallel.rb +44 -0
- data/dsl/outputs.rb +39 -0
- data/dsl/outputs_bang.rb +36 -0
- data/dsl/parallel_map.rb +37 -0
- data/dsl/prompts/simple_prompt.md.erb +3 -0
- data/dsl/prototype.rb +5 -7
- data/dsl/repeat_loop_results.rb +53 -0
- data/dsl/ruby_cog.rb +72 -0
- data/dsl/simple_agent.rb +18 -0
- data/dsl/simple_chat.rb +15 -1
- data/dsl/simple_repeat.rb +29 -0
- data/dsl/skip.rb +36 -0
- data/dsl/step_communication.rb +2 -3
- data/dsl/targets_and_params.rb +57 -0
- data/dsl/temperature.rb +17 -0
- data/dsl/temporary_directory.rb +22 -0
- data/dsl/tutorial/01_your_first_workflow/README.md +179 -0
- data/dsl/tutorial/01_your_first_workflow/configured_chat.rb +33 -0
- data/dsl/tutorial/01_your_first_workflow/hello.rb +23 -0
- data/dsl/tutorial/02_chaining_cogs/README.md +310 -0
- data/dsl/tutorial/02_chaining_cogs/code_review.rb +104 -0
- data/dsl/tutorial/02_chaining_cogs/session_resumption.rb +92 -0
- data/dsl/tutorial/02_chaining_cogs/simple_chain.rb +84 -0
- data/dsl/tutorial/03_targets_and_params/README.md +230 -0
- data/dsl/tutorial/03_targets_and_params/multiple_targets.rb +65 -0
- data/dsl/tutorial/03_targets_and_params/single_target.rb +65 -0
- data/dsl/tutorial/04_configuration_options/README.md +209 -0
- data/dsl/tutorial/04_configuration_options/control_display_and_temperature.rb +104 -0
- data/dsl/tutorial/04_configuration_options/simple_config.rb +68 -0
- data/dsl/tutorial/05_control_flow/README.md +156 -0
- data/dsl/tutorial/05_control_flow/conditional_execution.rb +62 -0
- data/dsl/tutorial/05_control_flow/handling_failures.rb +77 -0
- data/dsl/tutorial/06_reusable_scopes/README.md +172 -0
- data/dsl/tutorial/06_reusable_scopes/accessing_scope_outputs.rb +126 -0
- data/dsl/tutorial/06_reusable_scopes/basic_scope.rb +63 -0
- data/dsl/tutorial/06_reusable_scopes/parameterized_scope.rb +78 -0
- data/dsl/tutorial/07_processing_collections/README.md +152 -0
- data/dsl/tutorial/07_processing_collections/basic_map.rb +70 -0
- data/dsl/tutorial/07_processing_collections/parallel_map.rb +74 -0
- data/dsl/tutorial/08_iterative_workflows/README.md +231 -0
- data/dsl/tutorial/08_iterative_workflows/basic_repeat.rb +57 -0
- data/dsl/tutorial/08_iterative_workflows/conditional_break.rb +57 -0
- data/dsl/tutorial/09_async_cogs/README.md +197 -0
- data/dsl/tutorial/09_async_cogs/basic_async.rb +38 -0
- data/dsl/tutorial/README.md +222 -0
- data/dsl/working_directory.rb +16 -0
- data/exe/roast +1 -1
- data/internal/documentation/architectural-notes.md +115 -0
- data/internal/documentation/doc-comments-external.md +686 -0
- data/internal/documentation/doc-comments-internal.md +342 -0
- data/internal/documentation/doc-comments.md +211 -0
- data/lib/roast/dsl/cog/config.rb +274 -3
- data/lib/roast/dsl/cog/input.rb +53 -10
- data/lib/roast/dsl/cog/output.rb +297 -8
- data/lib/roast/dsl/cog/registry.rb +35 -3
- data/lib/roast/dsl/cog/stack.rb +1 -1
- data/lib/roast/dsl/cog/store.rb +5 -5
- data/lib/roast/dsl/cog.rb +70 -14
- data/lib/roast/dsl/cog_input_context.rb +36 -1
- data/lib/roast/dsl/cog_input_manager.rb +116 -7
- data/lib/roast/dsl/cogs/agent/config.rb +465 -0
- data/lib/roast/dsl/cogs/agent/input.rb +81 -0
- data/lib/roast/dsl/cogs/agent/output.rb +59 -0
- data/lib/roast/dsl/cogs/agent/provider.rb +51 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/claude_invocation.rb +185 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/message.rb +73 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/assistant_message.rb +36 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/result_message.rb +61 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/system_message.rb +47 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/text_message.rb +36 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/tool_result_message.rb +47 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/tool_use_message.rb +46 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/unknown_message.rb +27 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/messages/user_message.rb +37 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/tool_result.rb +51 -0
- data/lib/roast/dsl/cogs/agent/providers/claude/tool_use.rb +48 -0
- data/lib/roast/dsl/cogs/agent/providers/claude.rb +31 -0
- data/lib/roast/dsl/cogs/agent/stats.rb +92 -0
- data/lib/roast/dsl/cogs/agent/usage.rb +62 -0
- data/lib/roast/dsl/cogs/agent.rb +75 -0
- data/lib/roast/dsl/cogs/chat/config.rb +453 -0
- data/lib/roast/dsl/cogs/chat/input.rb +92 -0
- data/lib/roast/dsl/cogs/chat/output.rb +64 -0
- data/lib/roast/dsl/cogs/chat/session.rb +68 -0
- data/lib/roast/dsl/cogs/chat.rb +59 -56
- data/lib/roast/dsl/cogs/cmd.rb +251 -61
- data/lib/roast/dsl/cogs/ruby.rb +171 -0
- data/lib/roast/dsl/command_runner.rb +191 -0
- data/lib/roast/dsl/config_manager.rb +58 -11
- data/lib/roast/dsl/control_flow.rb +41 -0
- data/lib/roast/dsl/execution_manager.rb +162 -32
- data/lib/roast/dsl/nil_assertions.rb +23 -0
- data/lib/roast/dsl/system_cog/params.rb +32 -0
- data/lib/roast/dsl/system_cog.rb +36 -0
- data/lib/roast/dsl/system_cogs/call.rb +163 -0
- data/lib/roast/dsl/system_cogs/map.rb +454 -0
- data/lib/roast/dsl/system_cogs/repeat.rb +242 -0
- data/lib/roast/dsl/workflow.rb +26 -16
- data/lib/roast/dsl/workflow_context.rb +20 -0
- data/lib/roast/dsl/workflow_params.rb +24 -0
- data/lib/roast/helpers/minitest_coverage_runner.rb +1 -1
- data/lib/roast/sorbet_runtime_stub.rb +154 -0
- data/lib/roast/tools/apply_diff.rb +1 -3
- data/lib/roast/tools/cmd.rb +4 -3
- data/lib/roast/tools/read_file.rb +1 -1
- data/lib/roast/tools/update_files.rb +1 -1
- data/lib/roast/tools/write_file.rb +1 -1
- data/lib/roast/version.rb +1 -1
- data/lib/roast/workflow/base_workflow.rb +4 -0
- data/lib/roast/workflow/step_loader.rb +14 -2
- data/lib/roast-ai.rb +4 -0
- data/lib/roast.rb +58 -21
- data/{roast.gemspec → roast-ai.gemspec} +9 -13
- data/sorbet/rbi/gems/async@2.34.0.rbi +1577 -0
- data/sorbet/rbi/gems/cli-kit@5.2.0.rbi +2063 -0
- data/sorbet/rbi/gems/{cli-ui@2.3.0.rbi → cli-ui@2.7.0-6bdefd1d06305e5d6ae312ac76f9c88f88658dda.rbi} +1418 -1013
- data/sorbet/rbi/gems/console@1.34.2.rbi +1193 -0
- data/sorbet/rbi/gems/fiber-annotation@0.2.0.rbi +50 -0
- data/sorbet/rbi/gems/fiber-local@1.1.0.rbi +35 -0
- data/sorbet/rbi/gems/fiber-storage@1.0.1.rbi +41 -0
- data/sorbet/rbi/gems/io-event@1.14.0.rbi +724 -0
- data/sorbet/rbi/gems/metrics@0.15.0.rbi +9 -0
- data/sorbet/rbi/gems/traces@0.18.2.rbi +9 -0
- data/sorbet/rbi/shims/lib/roast/dsl/cog_input_context.rbi +1185 -5
- data/sorbet/rbi/shims/lib/roast/dsl/config_context.rbi +311 -5
- data/sorbet/rbi/shims/lib/roast/dsl/execution_context.rbi +486 -5
- data/sorbet/tapioca/config.yml +6 -0
- data/sorbet/tapioca/require.rb +2 -0
- metadata +157 -30
- data/dsl/less_simple.rb +0 -112
- data/dsl/scoped_executors.rb +0 -28
- data/dsl/simple.rb +0 -8
- data/lib/roast/dsl/cogs/execute.rb +0 -46
- data/lib/roast/dsl/cogs/graph.rb +0 -53
- data/sorbet/rbi/gems/cgi@0.5.0.rbi +0 -2961
- data/sorbet/rbi/gems/claude_swarm@0.1.19.rbi +0 -568
- data/sorbet/rbi/gems/cli-kit@5.0.1.rbi +0 -1991
- data/sorbet/rbi/gems/dry-configurable@1.3.0.rbi +0 -672
- data/sorbet/rbi/gems/dry-core@1.1.0.rbi +0 -1894
- data/sorbet/rbi/gems/dry-inflector@1.2.0.rbi +0 -659
- data/sorbet/rbi/gems/dry-initializer@3.2.0.rbi +0 -781
- data/sorbet/rbi/gems/dry-logic@1.6.0.rbi +0 -1127
- data/sorbet/rbi/gems/dry-schema@1.14.1.rbi +0 -3727
- data/sorbet/rbi/gems/dry-types@1.8.3.rbi +0 -3969
- data/sorbet/rbi/gems/fast-mcp-annotations@1.5.3.rbi +0 -1588
- data/sorbet/rbi/gems/mime-types-data@3.2025.0617.rbi +0 -136
- data/sorbet/rbi/gems/mime-types@3.7.0.rbi +0 -1342
- data/sorbet/rbi/gems/rack@2.2.19.rbi +0 -5676
- data/sorbet/rbi/gems/yard-sorbet@0.9.0.rbi +0 -435
- data/sorbet/rbi/gems/yard@0.9.37.rbi +0 -18492
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# Chapter 2: Chaining Cogs Together
|
|
2
|
+
|
|
3
|
+
In the previous chapter, you learned how to use a single chat cog. Now you'll learn how to chain multiple cogs together,
|
|
4
|
+
where the output of one step becomes the input to the next. This is where workflows become truly powerful.
|
|
5
|
+
|
|
6
|
+
## What You'll Learn
|
|
7
|
+
|
|
8
|
+
- How to name cogs so you can reference them
|
|
9
|
+
- How to access outputs from previous cogs
|
|
10
|
+
- How to chain cogs together to build multi-step workflows
|
|
11
|
+
- The difference between `agent` and `chat` cogs
|
|
12
|
+
- How to use the `ruby` cog for custom logic
|
|
13
|
+
|
|
14
|
+
## Naming Cogs
|
|
15
|
+
|
|
16
|
+
When you have multiple steps, you need to name them so you can reference their outputs. Give each cog a descriptive name
|
|
17
|
+
using a symbol:
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
execute do
|
|
21
|
+
chat(:analyze) { "Analyze this text..." }
|
|
22
|
+
chat(:summarize) { "Summarize that analysis..." }
|
|
23
|
+
end
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The name goes in parentheses after the cog type.
|
|
27
|
+
|
|
28
|
+
## Accessing Outputs
|
|
29
|
+
|
|
30
|
+
To access the output from a previous cog, reference it by name just like you defined it:
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
execute do
|
|
34
|
+
chat(:analyze) { "Analyze this: #{data}" }
|
|
35
|
+
|
|
36
|
+
# Access the 'analyze' cog's output
|
|
37
|
+
chat(:summarize) do
|
|
38
|
+
analysis = chat(:analyze).response
|
|
39
|
+
"Summarize this analysis: #{analysis}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This returns the cog's output, or `nil` if the cog didn't run (we'll learn about conditionally skipping steps in a later
|
|
45
|
+
lesson.
|
|
46
|
+
|
|
47
|
+
### Different Output Methods
|
|
48
|
+
|
|
49
|
+
Different cog types provide different types of output. Here are few highlights:
|
|
50
|
+
|
|
51
|
+
- `chat(:name).response` - The text response from a chat cog
|
|
52
|
+
- `agent(:name).response` - The text response from an agent cog
|
|
53
|
+
- `ruby(:name).value` - The return value from a ruby cog
|
|
54
|
+
- `cmd(:name).out` - The stdout from a command cog
|
|
55
|
+
- `cmd(:name).err` - The stderr from a command cog
|
|
56
|
+
|
|
57
|
+
Check out the documentation for each cog to see the other output values it can provide.
|
|
58
|
+
|
|
59
|
+
### Text Output
|
|
60
|
+
|
|
61
|
+
All cogs that produce textual output (`agent`, `chat`, and `cmd`) include some standard convenience methods to make
|
|
62
|
+
working with that output easy:
|
|
63
|
+
|
|
64
|
+
- `.text` - The output text with surrounding whitespace removed.
|
|
65
|
+
Using `cmd(:name).text` is particularly useful in place of `cmd(:name).out`
|
|
66
|
+
when you're expecting a single line of output and just want that value without
|
|
67
|
+
a trailing newline.
|
|
68
|
+
- `.lines` - An array containing the individual lines of the output text, each with
|
|
69
|
+
surrounding whitespace removed. `chat(:name).lines` is equivalent to
|
|
70
|
+
`chat(:name).response.lines.map(&:strip)`
|
|
71
|
+
- `.json` - A hash parsed from the output text in JSON format, or `nil` if the text could not
|
|
72
|
+
be parsed as valid JSON. `.json!` is equivalent, but will raise an exception if the
|
|
73
|
+
parsing fails.
|
|
74
|
+
|
|
75
|
+
### Checking If a Cog Ran
|
|
76
|
+
|
|
77
|
+
Use the `?` suffix to check whether a cog ran:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
execute do
|
|
81
|
+
chat do |my|
|
|
82
|
+
result = if chat?(:optional_step)
|
|
83
|
+
# It ran, safe to access
|
|
84
|
+
chat(:optional_step).response
|
|
85
|
+
else
|
|
86
|
+
# It didn't run (maybe it was skipped)
|
|
87
|
+
"No analysis available"
|
|
88
|
+
end
|
|
89
|
+
my.prompt = "Characterize this result: #{result}"
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### The `!` Suffix: Convenient Shorthand
|
|
95
|
+
|
|
96
|
+
When you're not intentionally skipping cogs, use the `!` suffix for convenient shorthand:
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
execute do
|
|
100
|
+
chat(:analyze) { "Analyze this: #{data}" }
|
|
101
|
+
|
|
102
|
+
chat(:summarize) do
|
|
103
|
+
analysis = chat!(:analyze).response # Raises error if analyze didn't run
|
|
104
|
+
"Summarize this analysis: #{analysis}"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The `!` suffix means "get this cog's output, and raise an error if it didn't run." This catches mistakes early. If you
|
|
110
|
+
try to access a cog that was accidentally skipped, or failed, you'll get a clear error right away, instead of a `nil`
|
|
111
|
+
result and a potential `NoMethodError` later on.
|
|
112
|
+
|
|
113
|
+
## The Agent Cog
|
|
114
|
+
|
|
115
|
+
The `agent` cog is similar to `chat`, but it runs locally and has access to your filesystem and the ability to use
|
|
116
|
+
a suite of local tools. Use `agent` when you need to read files, search code, or interact with your local environment:
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
execute do
|
|
120
|
+
agent(:code_review) do
|
|
121
|
+
<<~PROMPT
|
|
122
|
+
Read the Ruby files in the src/ directory and identify
|
|
123
|
+
any potential security issues. Focus on input validation
|
|
124
|
+
and data sanitization.
|
|
125
|
+
PROMPT
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The `agent` cog is backed by a locally installed coding agent -- Anthropic's Claude Code is the default provider.
|
|
131
|
+
You'll need to have Claude Code installed and configured correctly for this cog to run.
|
|
132
|
+
|
|
133
|
+
### When to Use Agent vs Chat
|
|
134
|
+
|
|
135
|
+
- **Use `agent`** when you need to:
|
|
136
|
+
- Read or write local files
|
|
137
|
+
- Search through code
|
|
138
|
+
- Run shell commands
|
|
139
|
+
- Interact with your development environment
|
|
140
|
+
|
|
141
|
+
- **Use `chat`** when you need to:
|
|
142
|
+
- Process data already in memory
|
|
143
|
+
- Perform reasoning without file access
|
|
144
|
+
- Generate text or analysis from provided context
|
|
145
|
+
- Use less expensive/faster models for simple tasks
|
|
146
|
+
|
|
147
|
+
Both are equally capable of complex reasoning. The difference is **access**, not intelligence.
|
|
148
|
+
|
|
149
|
+
## The Ruby Cog
|
|
150
|
+
|
|
151
|
+
The `ruby` cog lets you run custom Ruby code within your workflow. Use it for data processing, formatting, or any logic
|
|
152
|
+
that doesn't need an LLM:
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
execute do
|
|
156
|
+
chat(:analyze) { "Analyze this data..." }
|
|
157
|
+
|
|
158
|
+
ruby(:format_output) do
|
|
159
|
+
analysis = chat!(:analyze).response
|
|
160
|
+
|
|
161
|
+
# Custom formatting logic
|
|
162
|
+
formatted = "=" * 60 + "\n"
|
|
163
|
+
formatted += "ANALYSIS RESULTS\n"
|
|
164
|
+
formatted += "=" * 60 + "\n"
|
|
165
|
+
formatted += analysis + "\n"
|
|
166
|
+
formatted += "=" * 60
|
|
167
|
+
|
|
168
|
+
puts formatted
|
|
169
|
+
|
|
170
|
+
# The return value is stored for later usage by other cogs
|
|
171
|
+
{ status: "complete", length: analysis.length }
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
The ruby cog's return value is accessible via `ruby!(:name).value`.
|
|
177
|
+
|
|
178
|
+
## Data Flow Example
|
|
179
|
+
|
|
180
|
+
Here's how data flows through a typical workflow:
|
|
181
|
+
|
|
182
|
+
```ruby
|
|
183
|
+
execute do
|
|
184
|
+
# Step 1: Select some files
|
|
185
|
+
cmd(:recent_files_changes) do
|
|
186
|
+
"git show --name-only HEAD~3..HEAD"
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Step 2: Agent analyzes the code
|
|
190
|
+
agent(:security_review) do
|
|
191
|
+
<<~PROMPT
|
|
192
|
+
Review these recently changed files for security vulnerabilities:
|
|
193
|
+
|
|
194
|
+
#{cmd!(:recent_files_changes).text}
|
|
195
|
+
|
|
196
|
+
Identify specific issues and explain the risk.
|
|
197
|
+
PROMPT
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Step 3: Chat creates a simple summary
|
|
201
|
+
chat(:simple_summary) do
|
|
202
|
+
review = agent!(:security_review).response
|
|
203
|
+
<<~PROMPT
|
|
204
|
+
Summarize this security review in 2-3 sentences that
|
|
205
|
+
a non-technical manager would understand:
|
|
206
|
+
|
|
207
|
+
#{review}
|
|
208
|
+
PROMPT
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Step 4: Ruby formats the final output
|
|
212
|
+
ruby(:display) do
|
|
213
|
+
puts "\n" + "=" * 60
|
|
214
|
+
puts "EXECUTIVE SUMMARY"
|
|
215
|
+
puts "-" * 60
|
|
216
|
+
puts chat!(:simple_summary).response
|
|
217
|
+
puts "=" * 60 + "\n"
|
|
218
|
+
puts "SECURITY REVIEW"
|
|
219
|
+
puts "=" * 60
|
|
220
|
+
puts agent!(:security_review).response
|
|
221
|
+
puts "\n" + "-" * 60
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Data flows: **shell command → agent analysis → chat summary → ruby formatting**
|
|
227
|
+
|
|
228
|
+
## Resuming Conversations
|
|
229
|
+
|
|
230
|
+
Both `chat` and `agent` cogs can resume previous conversations by passing their session to a subsequent cog. This allows
|
|
231
|
+
multi-turn conversations where the LLM remembers context from earlier exchanges:
|
|
232
|
+
|
|
233
|
+
```ruby
|
|
234
|
+
execute do
|
|
235
|
+
# First turn - tell the LLM something
|
|
236
|
+
chat(:introduce_topic) do
|
|
237
|
+
"The secret code word is 'thunderbolt'. Remember it."
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# Second turn - resume the session and ask about it
|
|
241
|
+
chat(:recall_code) do |my|
|
|
242
|
+
my.session = chat!(:introduce_topic).session
|
|
243
|
+
my.prompt = "What was the secret code word?"
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The same pattern works with `agent` cogs:
|
|
249
|
+
|
|
250
|
+
```ruby
|
|
251
|
+
execute do
|
|
252
|
+
agent(:analyze) { "List files in this directory" }
|
|
253
|
+
|
|
254
|
+
agent(:followup) do |my|
|
|
255
|
+
my.session = agent!(:analyze).session
|
|
256
|
+
my.prompt = "Tell me more about one of those files"
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Important:** You cannot resume an agent's session in a chat cog, or vice versa. They are not interchangeable.
|
|
262
|
+
|
|
263
|
+
## Running the Workflows
|
|
264
|
+
|
|
265
|
+
To run the examples in this chapter:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
# Simple chaining example
|
|
269
|
+
bin/roast execute --executor=dsl dsl/tutorial/02_chaining_cogs/simple_chain.rb
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
# Realistic code review workflow
|
|
274
|
+
bin/roast execute --executor=dsl dsl/tutorial/02_chaining_cogs/code_review.rb
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
# Session resumption with multi-turn conversations
|
|
279
|
+
bin/roast execute --executor=dsl dsl/tutorial/02_chaining_cogs/session_resumption.rb
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Try It Yourself
|
|
283
|
+
|
|
284
|
+
1. **Run both workflows** and observe how data flows between steps
|
|
285
|
+
2. **Modify the prompts** - Change what the agent looks for or how the chat summarizes
|
|
286
|
+
3. **Add a new step** - Try adding another chat step that processes the summary differently
|
|
287
|
+
4. **Change the order** - What happens if you try to access a cog before it runs?
|
|
288
|
+
5. **Try the `!` suffix** - Replace `chat(:analyze)` with `chat!(:analyze)` and see the difference
|
|
289
|
+
|
|
290
|
+
## Key Takeaways
|
|
291
|
+
|
|
292
|
+
- Name cogs with descriptive symbols: `chat(:analyze)`
|
|
293
|
+
- Access outputs by referencing the cog: `chat(:analyze).response`
|
|
294
|
+
- Use `?` to check if a cog ran: `chat?(:analyze)`
|
|
295
|
+
- Use `!` for convenient shorthand that raises an error right away if the cog didn't run
|
|
296
|
+
- Different cogs have different output methods: `.response`, `.value`, `.out`
|
|
297
|
+
- Many cogs have common convenience methods: `.text`, `.lines`, `.json!`
|
|
298
|
+
- Use `agent` for filesystem access, `chat` for pure reasoning
|
|
299
|
+
- Use `ruby` for custom logic and formatting
|
|
300
|
+
- Data flows through your workflow in the order you define steps
|
|
301
|
+
- Reference any past steps' output in a current step's input
|
|
302
|
+
- Resume conversations by passing `.session` to subsequent cogs with `my.session = ...`
|
|
303
|
+
- Cannot mix chat and agent sessions - they are different types
|
|
304
|
+
|
|
305
|
+
## What's Next?
|
|
306
|
+
|
|
307
|
+
In the next chapter, you'll learn how to make workflows more flexible by accepting targets and custom parameters
|
|
308
|
+
from the command line, so you can reuse the same workflow with different inputs.
|
|
309
|
+
|
|
310
|
+
But first, make sure you understand how outputs flow between cogs. This is the foundation of all workflows!
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#: self as Roast::DSL::Workflow
|
|
5
|
+
|
|
6
|
+
# This workflow demonstrates using agent and chat cogs together.
|
|
7
|
+
# Agent reads/analyzes code, chat summarizes findings, ruby formats output.
|
|
8
|
+
|
|
9
|
+
config do
|
|
10
|
+
agent do
|
|
11
|
+
model "claude-3-5-haiku-20241022"
|
|
12
|
+
provider :claude
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
chat do
|
|
16
|
+
model "gpt-4o-mini"
|
|
17
|
+
provider :openai
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
execute do
|
|
22
|
+
# Step 1: Agent performs detailed security review
|
|
23
|
+
# The agent has access to file system and tools, though we're providing code directly here
|
|
24
|
+
agent(:review_security) do
|
|
25
|
+
<<~PROMPT
|
|
26
|
+
You are a security-focused code reviewer. Analyze the files changed by the latest commit in this project
|
|
27
|
+
and identify security vulnerabilities.
|
|
28
|
+
|
|
29
|
+
For each issue found, explain:
|
|
30
|
+
1. What the vulnerability is
|
|
31
|
+
2. Why it's dangerous
|
|
32
|
+
3. How to fix it
|
|
33
|
+
PROMPT
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Step 2: Chat creates a prioritized summary
|
|
37
|
+
# (There's no reason you couldn't use the `agent` to generate a summary;
|
|
38
|
+
# we're just using `chat` here for illustration purposes.)
|
|
39
|
+
chat(:prioritize) do
|
|
40
|
+
review = agent!(:review_security).text
|
|
41
|
+
<<~PROMPT
|
|
42
|
+
Review this security analysis and create a prioritized action list.
|
|
43
|
+
Rank issues by severity (Critical, High, Medium, Low).
|
|
44
|
+
|
|
45
|
+
Security Review:
|
|
46
|
+
#{review}
|
|
47
|
+
|
|
48
|
+
Format as:
|
|
49
|
+
- **[Severity]** Issue: Brief description
|
|
50
|
+
PROMPT
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Step 3: Chat creates an executive summary
|
|
54
|
+
chat(:summarize_for_executive) do
|
|
55
|
+
review = agent!(:review_security).response
|
|
56
|
+
<<~PROMPT
|
|
57
|
+
Create a 2-3 sentence executive summary of this security review
|
|
58
|
+
for a non-technical stakeholder:
|
|
59
|
+
|
|
60
|
+
#{review}
|
|
61
|
+
|
|
62
|
+
Focus on business impact and urgency.
|
|
63
|
+
PROMPT
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Step 4: Ruby formats and displays the complete report
|
|
67
|
+
ruby(:display_report) do
|
|
68
|
+
puts "\n" + "=" * 80
|
|
69
|
+
puts "CODE SECURITY REVIEW REPORT"
|
|
70
|
+
puts "=" * 80
|
|
71
|
+
|
|
72
|
+
puts "\nEXECUTIVE SUMMARY"
|
|
73
|
+
puts "-" * 80
|
|
74
|
+
puts chat!(:summarize_for_executive).response
|
|
75
|
+
|
|
76
|
+
puts "\n\nPRIORITIZED ACTION ITEMS"
|
|
77
|
+
puts "-" * 80
|
|
78
|
+
puts chat!(:prioritize).response
|
|
79
|
+
|
|
80
|
+
puts "\n\nDETAILED FINDINGS"
|
|
81
|
+
puts "-" * 80
|
|
82
|
+
puts agent!(:review_security).response
|
|
83
|
+
|
|
84
|
+
puts "\n" + "=" * 80 + "\n"
|
|
85
|
+
|
|
86
|
+
# Return structured data
|
|
87
|
+
{
|
|
88
|
+
report_generated_at: Time.now.iso8601,
|
|
89
|
+
sections: ["executive_summary", "priority_summary", "detailed_findings"],
|
|
90
|
+
total_length: agent!(:review_security).response.length,
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Optional: Use the ruby cog's return value
|
|
95
|
+
ruby(:check_report) do
|
|
96
|
+
report_data = ruby!(:display_report).value
|
|
97
|
+
|
|
98
|
+
if report_data[:total_length] > 0
|
|
99
|
+
puts "✓ Report generated successfully at #{report_data[:report_generated_at]}"
|
|
100
|
+
else
|
|
101
|
+
puts "⚠ Warning: Report appears to be empty"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#: self as Roast::DSL::Workflow
|
|
5
|
+
|
|
6
|
+
config do
|
|
7
|
+
chat do
|
|
8
|
+
model "gpt-4o-mini"
|
|
9
|
+
no_display!
|
|
10
|
+
show_stats!
|
|
11
|
+
end
|
|
12
|
+
chat(:recall_code) do
|
|
13
|
+
model "gpt-4.1-nano"
|
|
14
|
+
end
|
|
15
|
+
agent do
|
|
16
|
+
model "haiku"
|
|
17
|
+
no_display!
|
|
18
|
+
show_stats!
|
|
19
|
+
end
|
|
20
|
+
agent(:followup_question) do
|
|
21
|
+
model "sonnet"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
execute do
|
|
26
|
+
# First conversation turn - tell the LLM something
|
|
27
|
+
chat(:introduce_topic) do
|
|
28
|
+
<<~PROMPT
|
|
29
|
+
I'm going to tell you a secret code word. Remember it for later.
|
|
30
|
+
The secret code word is: "thunderbolt"
|
|
31
|
+
|
|
32
|
+
Just respond with "OK, I'll remember that."
|
|
33
|
+
PROMPT
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
ruby do
|
|
37
|
+
puts "First turn: #{chat!(:introduce_topic).text}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Second turn - resume the session and ask about it
|
|
41
|
+
chat(:recall_code) do |my|
|
|
42
|
+
# Resume the previous conversation by passing the session
|
|
43
|
+
# You can even resume with a different model that you used earlier in the session
|
|
44
|
+
my.session = chat!(:introduce_topic).session
|
|
45
|
+
my.prompt = "What was the secret code word I told you?"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
ruby do
|
|
49
|
+
puts "Second turn: #{chat!(:recall_code).text}"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Third turn - continue the conversation further
|
|
53
|
+
chat(:update_code) do |my|
|
|
54
|
+
# Can resume from any previous step in the conversation chain
|
|
55
|
+
my.session = chat!(:recall_code).session
|
|
56
|
+
my.prompt = "The new code word is 'mermaid'"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Fourth turn - resume from an earlier point
|
|
60
|
+
chat(:resume_from_beginning) do |my|
|
|
61
|
+
# Every time you resume from a particular previous session, a new session is forked.
|
|
62
|
+
# You can always resume from that point again, without any new context being present.
|
|
63
|
+
my.session = chat!(:introduce_topic).session
|
|
64
|
+
my.prompt = "What is the current secret code word?"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
ruby do
|
|
68
|
+
puts "Third turn: #{chat!(:update_code).response}"
|
|
69
|
+
# This will be the original word from the first turn
|
|
70
|
+
puts "Fourth turn: #{chat!(:resume_from_beginning).response}"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Example with agent cog - works the same way
|
|
74
|
+
agent(:analyze_file) do
|
|
75
|
+
"What files are in the current directory? Just list a few."
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
ruby do
|
|
79
|
+
puts "\n--- Agent Session ---"
|
|
80
|
+
puts "Agent response: #{agent!(:analyze_file).response}"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
agent(:followup_question) do |my|
|
|
84
|
+
# Resume the agent session
|
|
85
|
+
my.session = agent!(:analyze_file).session
|
|
86
|
+
"Pick one of those files and tell me what it is."
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
ruby do
|
|
90
|
+
puts "Agent followup: #{agent!(:followup_question).response}"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# typed: true
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
#: self as Roast::DSL::Workflow
|
|
5
|
+
|
|
6
|
+
# This workflow demonstrates basic chaining of cogs.
|
|
7
|
+
# Data flows: sample text → analysis → summary → formatted output
|
|
8
|
+
|
|
9
|
+
config do
|
|
10
|
+
chat do
|
|
11
|
+
model "gpt-4o-mini"
|
|
12
|
+
provider :openai
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
execute do
|
|
17
|
+
# Sample data embedded in the workflow
|
|
18
|
+
customer_feedback = <<~TEXT
|
|
19
|
+
I've been using your product for 3 months now. The interface is
|
|
20
|
+
really intuitive and I love the dark mode. However, the mobile
|
|
21
|
+
app crashes frequently when I try to upload photos. Also, the
|
|
22
|
+
export feature could be faster... it takes forever to download
|
|
23
|
+
large files! Overall though, I'm happy with the purchase and
|
|
24
|
+
would recommend it to friends.
|
|
25
|
+
TEXT
|
|
26
|
+
|
|
27
|
+
# Step 1: Analyze the feedback
|
|
28
|
+
chat(:analyze) do
|
|
29
|
+
<<~PROMPT
|
|
30
|
+
Analyze this customer feedback and identify:
|
|
31
|
+
1. Positive points
|
|
32
|
+
2. Issues/problems
|
|
33
|
+
3. Feature requests
|
|
34
|
+
|
|
35
|
+
Feedback:
|
|
36
|
+
#{customer_feedback}
|
|
37
|
+
|
|
38
|
+
Provide a structured analysis.
|
|
39
|
+
PROMPT
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Step 2: Create a concise summary
|
|
43
|
+
# Note how we access the previous cog's output
|
|
44
|
+
chat(:summarize) do
|
|
45
|
+
analysis = chat!(:analyze).response
|
|
46
|
+
|
|
47
|
+
<<~PROMPT
|
|
48
|
+
Take this feedback analysis and create a 2-3 sentence
|
|
49
|
+
summary suitable for a product team standup:
|
|
50
|
+
|
|
51
|
+
#{analysis}
|
|
52
|
+
|
|
53
|
+
Focus on actionable items.
|
|
54
|
+
PROMPT
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Step 3: Format and display the results
|
|
58
|
+
ruby(:display) do
|
|
59
|
+
puts "\n" + "=" * 70
|
|
60
|
+
puts "CUSTOMER FEEDBACK ANALYSIS"
|
|
61
|
+
puts "=" * 70
|
|
62
|
+
|
|
63
|
+
puts "\nORIGINAL FEEDBACK:"
|
|
64
|
+
puts "-" * 70
|
|
65
|
+
puts customer_feedback
|
|
66
|
+
|
|
67
|
+
puts "\nEXECUTIVE SUMMARY:"
|
|
68
|
+
puts "-" * 70
|
|
69
|
+
puts chat!(:summarize).text
|
|
70
|
+
|
|
71
|
+
puts "\nDETAILED ANALYSIS:"
|
|
72
|
+
puts "-" * 70
|
|
73
|
+
puts chat!(:analyze).text
|
|
74
|
+
|
|
75
|
+
puts "=" * 70 + "\n"
|
|
76
|
+
|
|
77
|
+
# Return a value that could be used by subsequent steps
|
|
78
|
+
{
|
|
79
|
+
status: "complete",
|
|
80
|
+
feedback_length: customer_feedback.length,
|
|
81
|
+
summary_lines: chat!(:summarize).lines.length,
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
end
|