agent_c 2.9

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 (65) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +10 -0
  3. data/.ruby-version +1 -0
  4. data/CLAUDE.md +21 -0
  5. data/README.md +360 -0
  6. data/Rakefile +16 -0
  7. data/TODO.md +105 -0
  8. data/agent_c.gemspec +38 -0
  9. data/docs/batch.md +503 -0
  10. data/docs/chat-methods.md +156 -0
  11. data/docs/cost-reporting.md +86 -0
  12. data/docs/pipeline-tips-and-tricks.md +453 -0
  13. data/docs/session-configuration.md +274 -0
  14. data/docs/testing.md +747 -0
  15. data/docs/tools.md +103 -0
  16. data/docs/versioned-store.md +840 -0
  17. data/lib/agent_c/agent/chat.rb +211 -0
  18. data/lib/agent_c/agent/chat_response.rb +38 -0
  19. data/lib/agent_c/agent/chats/anthropic_bedrock.rb +48 -0
  20. data/lib/agent_c/batch.rb +102 -0
  21. data/lib/agent_c/configs/repo.rb +90 -0
  22. data/lib/agent_c/context.rb +56 -0
  23. data/lib/agent_c/costs/data.rb +39 -0
  24. data/lib/agent_c/costs/report.rb +219 -0
  25. data/lib/agent_c/db/store.rb +162 -0
  26. data/lib/agent_c/errors.rb +19 -0
  27. data/lib/agent_c/pipeline.rb +152 -0
  28. data/lib/agent_c/pipelines/agent.rb +219 -0
  29. data/lib/agent_c/processor.rb +98 -0
  30. data/lib/agent_c/prompts.yml +53 -0
  31. data/lib/agent_c/schema.rb +71 -0
  32. data/lib/agent_c/session.rb +206 -0
  33. data/lib/agent_c/store.rb +72 -0
  34. data/lib/agent_c/test_helpers.rb +173 -0
  35. data/lib/agent_c/tools/dir_glob.rb +46 -0
  36. data/lib/agent_c/tools/edit_file.rb +114 -0
  37. data/lib/agent_c/tools/file_metadata.rb +43 -0
  38. data/lib/agent_c/tools/git_status.rb +30 -0
  39. data/lib/agent_c/tools/grep.rb +119 -0
  40. data/lib/agent_c/tools/paths.rb +36 -0
  41. data/lib/agent_c/tools/read_file.rb +94 -0
  42. data/lib/agent_c/tools/run_rails_test.rb +87 -0
  43. data/lib/agent_c/tools.rb +61 -0
  44. data/lib/agent_c/utils/git.rb +87 -0
  45. data/lib/agent_c/utils/shell.rb +58 -0
  46. data/lib/agent_c/version.rb +5 -0
  47. data/lib/agent_c.rb +32 -0
  48. data/lib/versioned_store/base.rb +314 -0
  49. data/lib/versioned_store/config.rb +26 -0
  50. data/lib/versioned_store/stores/schema.rb +127 -0
  51. data/lib/versioned_store/version.rb +5 -0
  52. data/lib/versioned_store.rb +5 -0
  53. data/template/Gemfile +9 -0
  54. data/template/Gemfile.lock +152 -0
  55. data/template/README.md +61 -0
  56. data/template/Rakefile +50 -0
  57. data/template/bin/rake +27 -0
  58. data/template/lib/autoload.rb +10 -0
  59. data/template/lib/config.rb +59 -0
  60. data/template/lib/pipeline.rb +19 -0
  61. data/template/lib/prompts.yml +57 -0
  62. data/template/lib/store.rb +17 -0
  63. data/template/test/pipeline_test.rb +221 -0
  64. data/template/test/test_helper.rb +18 -0
  65. metadata +194 -0
@@ -0,0 +1,86 @@
1
+ # Cost Reporting
2
+
3
+ Note: You probably want to make a `Batch`. See the [main README](../README.md)
4
+
5
+ ## Overview
6
+
7
+ AgentC automatically tracks all LLM interactions in a SQLite database, allowing you to generate detailed cost reports.
8
+
9
+ ## Generating Reports
10
+
11
+ Track your LLM usage and costs:
12
+
13
+ ```ruby
14
+ # See the [configuration](./session-configuration.md) for session args
15
+ session = AgentC::Session.new(...)
16
+
17
+ # Generate a cost report for all projects
18
+ AgentC::Costs::Report.call(agent_store: session.agent_store)
19
+
20
+ # For a specific project
21
+ AgentC::Costs::Report.call(
22
+ agent_store: session.agent_store,
23
+ project: 'my_project'
24
+ )
25
+
26
+ # For a specific run
27
+ AgentC::Costs::Report.call(
28
+ agent_store: session.agent_store,
29
+ project: 'my_project',
30
+ run_id: 1234567890
31
+ )
32
+
33
+ # Or use the session's cost method for current project/run
34
+ puts "Project cost: $#{session.cost.project}"
35
+ puts "Run cost: $#{session.cost.run}"
36
+ ```
37
+
38
+ ## What's Included
39
+
40
+ The report includes:
41
+ - Input/output token counts
42
+ - Cache hit rates
43
+ - Per-interaction costs
44
+ - Total spending
45
+ - Pricing for both normal and long-context models
46
+
47
+ ## Report Format
48
+
49
+ The cost report displays information organized by project and run:
50
+
51
+ ```
52
+ Project: my_project
53
+ Run ID: 1234567890
54
+
55
+ Interaction 1 (2024-01-15 10:30:00)
56
+ Input tokens: 1,250
57
+ Output tokens: 450
58
+ Cached tokens: 800
59
+ Cost: $0.0234
60
+
61
+ Interaction 2 (2024-01-15 10:35:00)
62
+ Input tokens: 2,100
63
+ Output tokens: 680
64
+ Cached tokens: 1,500
65
+ Cost: $0.0356
66
+
67
+ Total Cost: $0.0590
68
+ ```
69
+
70
+ ## Cost Optimization Tips
71
+
72
+ 1. **Use cached prompts** - System prompts that rarely change can be cached, significantly reducing costs
73
+ 2. **Choose appropriate models** - Use lighter models (Haiku) for simple tasks, heavier models (Sonnet/Opus) for complex ones
74
+ 3. **Batch operations** - Group similar tasks together to maximize cache hits
75
+ 4. **Monitor costs regularly** - Run cost reports after each pipeline run to identify expensive operations
76
+
77
+ ## Database Schema
78
+
79
+ All queries are persisted in SQLite with:
80
+ - Full conversation history
81
+ - Token usage metrics
82
+ - Timestamps and metadata
83
+ - Tool calls and responses
84
+ - Project and run ID associations
85
+
86
+ This allows for detailed analysis and debugging of AI interactions over time.
@@ -0,0 +1,453 @@
1
+ # Pipeline Tips and Tricks
2
+
3
+ This document contains useful patterns and techniques for working with AgentC pipelines.
4
+
5
+ ## Index
6
+
7
+ - [Custom I18n Attributes](#custom-i18n-attributes)
8
+ - [Rewinding to Previous Steps](#rewinding-to-previous-steps)
9
+ - [Agent Review Loop](#agent-review-loop)
10
+
11
+ ## Custom I18n Attributes
12
+
13
+ By default, when using i18n interpolation in your prompts, AgentC will use `record.attributes` to provide values for interpolation. However, you can customize this behavior by implementing an `i18n_attributes` method on your record.
14
+
15
+ ### Use Case
16
+
17
+ This is useful when:
18
+ - You want to interpolate values that aren't stored as attributes on the record
19
+ - You need to compute or format values specifically for prompts
20
+ - You want to limit which attributes are exposed to i18n interpolation
21
+ - You need to provide different data than what's in the database
22
+
23
+ ### Example
24
+
25
+ ```ruby
26
+ class MyStore < VersionedStore::Base
27
+ include AgentC::Store
28
+
29
+ record(:my_record) do
30
+ schema do |t|
31
+ t.string(:file_path)
32
+ t.text(:file_contents)
33
+ end
34
+
35
+ # Override the default i18n_attributes
36
+ def i18n_attributes
37
+ {
38
+ file_name: File.basename(file_path),
39
+ file_extension: File.extname(file_path),
40
+ lines_count: file_contents&.lines&.count || 0
41
+ }
42
+ end
43
+ end
44
+ end
45
+ ```
46
+
47
+ Now in your prompts, you can interpolate these computed values:
48
+
49
+ ```yaml
50
+ en:
51
+ analyze_file:
52
+ prompt: "Analyze %{file_name} which has %{lines_count} lines and is a %{file_extension} file"
53
+ ```
54
+
55
+ ### How It Works
56
+
57
+ When you use `agent_step` with i18n (either via `prompt_key` or the shorthand syntax), AgentC checks if your record responds to `i18n_attributes`. If it does, that method's return value is used for interpolation. Otherwise, it falls back to `record.attributes`.
58
+
59
+ This works with both explicit prompt keys:
60
+
61
+ ```ruby
62
+ agent_step(
63
+ :my_step,
64
+ prompt_key: "my_step.prompt",
65
+ cached_prompt_keys: ["my_step.cached"]
66
+ )
67
+ ```
68
+
69
+ And with the shorthand syntax:
70
+
71
+ ```ruby
72
+ agent_step(:my_step)
73
+ ```
74
+
75
+ ### Return Value
76
+
77
+ The `i18n_attributes` method should return a Hash with symbol or string keys. These keys will be used for interpolation in your i18n strings.
78
+
79
+ ## Rewinding to Previous Steps
80
+
81
+ The `rewind_to!` method allows you to restart execution from a previously completed step. This is useful when you need to retry or re-execute steps based on runtime conditions.
82
+
83
+ ### Use Case
84
+
85
+ This is useful when:
86
+ - An agent determines that a previous step needs to be re-executed
87
+ - You want to implement retry logic based on validation results
88
+ - You need to loop through steps until certain conditions are met
89
+ - A later step discovers that earlier work needs to be redone
90
+
91
+ ### Basic Usage
92
+
93
+ ```ruby
94
+ class Store < AgentC::Store
95
+ record(:refactor) do
96
+ schema do
97
+ t.boolean(
98
+ :review_passed,
99
+ default: false
100
+ )
101
+
102
+ t.string(
103
+ :review_feedback,
104
+ default: "none"
105
+ )
106
+ end
107
+ end
108
+ end
109
+
110
+ class MyPipeline < AgentC::Pipeline
111
+ # prompt:
112
+ # Perform the refactor.
113
+ # Here is feedback from the reviewer (if any):
114
+ # %{review_feedback}
115
+ agent_step(:perform_refactor)
116
+
117
+ # capture the diff
118
+ step(:capture_diff) do
119
+ record.update!(diff: git.diff)
120
+ end
121
+
122
+ # prompt:
123
+ # Review this diff: %{diff}
124
+ # schema:
125
+ # review_passed:
126
+ # type: boolean
127
+ # review_feedback:
128
+ # type: string
129
+ agent_step(:review_refactor)
130
+
131
+ step(:verify_output) do
132
+ # if the review hasn't passed,
133
+ # then review_feedback is now
134
+ # present and will be passed
135
+ # back in to refactor step above
136
+ unless record.review_passed
137
+ rewind_to!(:perform_refactor)
138
+ end
139
+ end
140
+ end
141
+ ```
142
+
143
+ ### How It Works
144
+
145
+ When you call `rewind_to!(step_name)`, the pipeline:
146
+ 1. Validates that the specified step has already been completed
147
+ 2. Validates that the step name appears only once in `completed_steps`
148
+ 3. Removes the specified step and all subsequent steps from `completed_steps`
149
+ 4. Continues execution from the rewound step
150
+
151
+ ### Important Notes
152
+
153
+ **Infinite loops**: There's no automatic infinite loop detection. Use your record's state to count rewinds if you are concerned about a potential infinite loop.
154
+
155
+ **Must be called from within a step**: The `rewind_to!` method must be invoked from within a pipeline step during execution.
156
+
157
+ **Step must be completed**: You can only rewind to steps that have already been completed in the current pipeline run. Attempting to rewind to a step that hasn't been completed will raise an `ArgumentError`.
158
+
159
+ **Step must be unique**: If a step name appears multiple times in `completed_steps`, attempting to rewind to it will raise an `ArgumentError`. This prevents ambiguous rewind operations.
160
+
161
+ **State considerations**: When rewinding, be aware that any side effects from the original execution of the rewound steps will remain unless explicitly cleaned up. The pipeline doesn't automatically rollback database changes or other state modifications.
162
+
163
+ ### Example: Retry Logic
164
+
165
+ ```ruby
166
+ class ProcessWithRetry < AgentC::Pipeline
167
+ step(:attempt_processing) do
168
+ result = process_with_agent
169
+ record.update!(
170
+ attempt_count: record.attempt_count + 1,
171
+ last_result: result
172
+ )
173
+ end
174
+
175
+ step(:check_result) do
176
+ if record.last_result.failed? && record.attempt_count < 3
177
+ # Retry by going back to the processing step
178
+ rewind_to!(:attempt_processing)
179
+ elsif record.last_result.failed?
180
+ task.fail!("Failed after 3 attempts")
181
+ else
182
+ record.update!(status: "completed")
183
+ end
184
+ end
185
+ end
186
+ ```
187
+
188
+ ### Error Handling
189
+
190
+ If you try to rewind to a step that hasn't been completed yet:
191
+
192
+ ```ruby
193
+ step(:early_step) do
194
+ rewind_to!(:later_step) # ArgumentError: Cannot rewind to a step that's not been completed yet
195
+ end
196
+ ```
197
+
198
+ If a step name appears multiple times in `completed_steps`:
199
+
200
+ ```ruby
201
+ # This will raise an ArgumentError about non-distinct step names
202
+ rewind_to!(:duplicate_step)
203
+ ```
204
+
205
+ ## Agent Review Loop
206
+
207
+ The `agent_review_loop` method provides a declarative way to implement iterative review and refinement workflows. It automatically handles the loop logic where an agent implements a solution, reviewers provide feedback, and the agent iterates based on that feedback until the reviewers approve or a maximum number of tries is reached.
208
+
209
+ ### Use Case
210
+
211
+ This is useful when:
212
+ - You need an agent to generate code, designs, or content that requires review
213
+ - Multiple reviewers need to evaluate the work from different perspectives
214
+ - The agent should iterate based on feedback until reviewers approve
215
+ - You want to capture review history for audit or debugging purposes
216
+ - You need to limit the number of iteration attempts
217
+
218
+ ### Basic Example
219
+
220
+ ```ruby
221
+ class RefactorPipeline < AgentC::Pipeline
222
+ agent_review_loop(
223
+ :refactor_code,
224
+ max_tries: 5,
225
+ implement: :initial_refactor,
226
+ iterate: :improve_refactor,
227
+ review: :code_review
228
+ )
229
+ end
230
+ ```
231
+
232
+ With i18n translations:
233
+
234
+ ```yaml
235
+ en:
236
+ initial_refactor:
237
+ prompt: "Refactor the code to improve readability"
238
+ response_schema:
239
+ code:
240
+ description: "The refactored code"
241
+
242
+ improve_refactor:
243
+ prompt: |
244
+ The previous refactor received this feedback:
245
+ %{feedback}
246
+
247
+ Please improve the refactor based on this feedback.
248
+ response_schema:
249
+ code:
250
+ description: "The improved refactored code"
251
+
252
+ code_review:
253
+ prompt: |
254
+ Review this code change:
255
+ %{diff}
256
+
257
+ Is it ready to merge?
258
+ response_schema:
259
+ approved:
260
+ type: boolean
261
+ description: "Whether the code is approved"
262
+ feedback:
263
+ type: string
264
+ description: "Feedback if not approved (empty if approved)"
265
+ ```
266
+
267
+ ### How It Works
268
+
269
+ The `agent_review_loop` executes in iterations:
270
+
271
+ 1. **First iteration (try 0)**:
272
+ - Runs all `implement` steps in order
273
+ - If any implement step fails, the loop stops and marks the task as failed
274
+ - Captures git diff of changes
275
+ - Runs all `review` steps with the diff
276
+ - Collects feedback from any reviewers who don't approve
277
+
278
+ 2. **Subsequent iterations (try 1+)**:
279
+ - Runs all `iterate` steps with accumulated feedback
280
+ - If any iterate step fails, the loop stops and marks the task as failed
281
+ - Captures git diff of changes
282
+ - Runs all `review` steps with the new diff
283
+ - Collects feedback from any reviewers who don't approve
284
+
285
+ 3. **Loop continues until**:
286
+ - All reviewers approve (feedback list is empty), OR
287
+ - `max_tries` is reached, OR
288
+ - Any step fails, OR
289
+ - The task is marked as failed by other means
290
+
291
+ ### Multiple Steps
292
+
293
+ You can specify multiple steps for implement, iterate, and review:
294
+
295
+ ```ruby
296
+ agent_review_loop(
297
+ :multi_file_refactor,
298
+ max_tries: 5,
299
+ implement: [
300
+ :refactor_controller,
301
+ :refactor_model,
302
+ :refactor_view
303
+ ],
304
+ iterate: [
305
+ :improve_controller,
306
+ :improve_model,
307
+ :improve_view
308
+ ],
309
+ review: [
310
+ :code_quality_review,
311
+ :security_review,
312
+ :performance_review
313
+ ]
314
+ )
315
+ ```
316
+
317
+ Steps are executed in order. If any step fails, the loop stops immediately.
318
+
319
+ ### Feedback Interpolation
320
+
321
+ The `iterate` steps automatically receive a `%{feedback}` interpolation variable containing all feedback from reviewers, joined with `"\n---\n"` as a separator:
322
+
323
+ ```yaml
324
+ improve_refactor:
325
+ prompt: |
326
+ Previous feedback from reviewers:
327
+ %{feedback}
328
+
329
+ Please address all concerns.
330
+ ```
331
+
332
+ ### Review Schema
333
+
334
+ Your "review" I18n should **not** include any response schema. AgentC will
335
+ configure the schema for you.
336
+
337
+ Review steps must return a response with these fields:
338
+ - `approved` (boolean): Whether the work is approved
339
+ - `feedback` (string): Feedback message if not approved (can be empty string if approved)
340
+
341
+ If a review step fails to return valid data, the task is marked as failed.
342
+
343
+ ### Optional: Recording Reviews
344
+
345
+ If your record implements an `add_review` method, it will be called after each review iteration with the diff and collected feedback:
346
+
347
+ ```ruby
348
+ record(:my_record) do
349
+ schema do |t|
350
+ t.json(:reviews, default: [])
351
+ end
352
+
353
+ def add_review(diff:, feedbacks:)
354
+ self.reviews ||= []
355
+ self.reviews << {
356
+ timestamp: Time.now.iso8601,
357
+ diff: diff,
358
+ feedbacks: feedbacks
359
+ }
360
+ save!
361
+ end
362
+ end
363
+ ```
364
+
365
+ This allows you to maintain a complete history of all review iterations.
366
+
367
+ ### Default Iterate Behavior
368
+
369
+ If you don't specify `iterate`, it defaults to the same value as `implement`:
370
+
371
+ ```ruby
372
+ # These are equivalent:
373
+ agent_review_loop(:refactor, implement: :refactor_code, review: :review)
374
+ agent_review_loop(:refactor, implement: :refactor_code, iterate: :refactor_code, review: :review)
375
+ ```
376
+
377
+ This is useful when the same prompt can handle both initial implementation and iteration based on feedback.
378
+
379
+ ### Important Notes
380
+
381
+ **Required parameters**: You must provide either `implement` or `iterate` (or both). Providing only `review` will raise an `ArgumentError`.
382
+
383
+ **Max tries behavior**: When `max_tries` is reached, the loop completes the step successfully even if reviews haven't all approved. The loop doesn't fail the task when max tries is exceeded.
384
+
385
+ **Git diff**: The git diff is captured after each iteration's implementation/iteration steps complete, and is passed to review steps via the `%{diff}` interpolation variable.
386
+
387
+ **Failure handling**: If any implement, iterate, or review step returns invalid data or raises an exception, the entire agent_review_loop step is marked as failed and the task stops.
388
+
389
+ **Step naming**: The `agent_review_loop` counts as a single pipeline step with the name you provide (e.g., `:refactor_code`), not separate steps for each iteration.
390
+
391
+ ### Complete Example
392
+
393
+ ```ruby
394
+ class DocumentationPipeline < AgentC::Pipeline
395
+ agent_review_loop(
396
+ :write_documentation,
397
+ max_tries: 3,
398
+ implement: [:draft_readme, :draft_examples],
399
+ iterate: [:improve_readme, :improve_examples],
400
+ review: [:technical_review, :style_review]
401
+ )
402
+
403
+ step(:publish) do
404
+ # Only reached if reviews passed or max_tries exceeded
405
+ record.update!(published: true)
406
+ end
407
+ end
408
+ ```
409
+
410
+ With a record that tracks review history:
411
+
412
+ ```ruby
413
+ record(:documentation) do
414
+ schema do |t|
415
+ t.text(:readme_content)
416
+ t.text(:examples_content)
417
+ t.json(:review_history, default: [])
418
+ t.boolean(:published, default: false)
419
+ end
420
+
421
+ def add_review(diff:, feedbacks:)
422
+ self.review_history << {
423
+ iteration: review_history.length + 1,
424
+ timestamp: Time.now.iso8601,
425
+ diff_size: diff.length,
426
+ feedback_count: feedbacks.length,
427
+ feedbacks: feedbacks
428
+ }
429
+ save!
430
+ end
431
+
432
+ def i18n_attributes
433
+ attributes.merge(
434
+ total_reviews: review_history.length,
435
+ last_feedback: review_history.last&.dig("feedbacks")&.join("\n---\n") || "none"
436
+ )
437
+ end
438
+ end
439
+ ```
440
+
441
+ ### When to Use agent_review_loop vs rewind_to!
442
+
443
+ Use `agent_review_loop` when:
444
+ - The review and iteration logic is straightforward and consistent
445
+ - You want a declarative approach with less boilerplate
446
+ - Multiple reviewers are involved
447
+ - You want automatic feedback collection and interpolation
448
+
449
+ Use `rewind_to!` when:
450
+ - You need custom logic to determine whether to retry
451
+ - The retry conditions are complex or context-dependent
452
+ - You need to rewind to steps other than the immediate previous one
453
+ - You want explicit control over the retry logic