composable_agents 2.2.0 β†’ 3.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 28c6711f6b4e8807c0e62349cfd5e246bf3ed56c9c1a613d14843d5b1769477d
4
- data.tar.gz: a7433cc6cc01b80eb12c1b4f0eb04c8b5391e2bd10ec88aa47608ab5d25e0f0b
3
+ metadata.gz: 13903597d02eeacf544613b91cf9c60de076d39ecd2f3cd22b549e8a992646aa
4
+ data.tar.gz: 33bfbf930e6a4e43d87efadecbc1efc8a4ccf8ba29f804757b53c281d1db702f
5
5
  SHA512:
6
- metadata.gz: dcffae4c60bfb6c5424cc41134ab5466e15c1f3a0b2580e4cef7d9d6c2634b5b43d721cd9f5e4bf604a968af35c02b561409cd9885f028b27bf61d7bbb2fc775
7
- data.tar.gz: c83544d5ab8f321a63c87a7eb035763b12a573a3d3495e653b301c0ce71edc076838e5af84216d64395c497ec8efc7aff13be136e96c399e84b277f9cd7db0b0
6
+ metadata.gz: 9102890f35cef152fa02cd9ec0a2ce960b27112c4e3134865accee7c7e8b1eb8e53914ca8030543b4c35a72dfe0d7a5c07c6172d61152c90cead8bdc4183b030
7
+ data.tar.gz: 5e0c71dd4d3aefd7b02a1e4183151d85e7b6ea5f8eb498589e2d5dde099ff4706d10d186dfa214c665bbffb30b74eaed9c8f4b2fe0ab4e81e6f5e238ed979ff5
data/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
+ # [v3.1.0](https://github.com/Muriel-Salvan/composable_agents/compare/v3.0.0...v3.1.0) (2026-11-01 13:55:18)
2
+
3
+ ## Global changes
4
+ ### Patches
5
+
6
+ * [feat(cline): introduce ClineError and extract MissingSkillError](https://github.com/Muriel-Salvan/composable_agents/commit/ba02df7d666c96c7cfc23a1b5a7eb92274a9e496)
7
+
8
+ ## Changes for cline
9
+ ### Features
10
+
11
+ * [feat(cline): introduce ClineError and extract MissingSkillError](https://github.com/Muriel-Salvan/composable_agents/commit/ba02df7d666c96c7cfc23a1b5a7eb92274a9e496)
12
+
13
+ # [v3.0.0](https://github.com/Muriel-Salvan/composable_agents/compare/v2.2.0...v3.0.0) (2026-11-01 12:34:56)
14
+
15
+ ## Global changes
16
+ ### Breaking changes
17
+
18
+ * [break: add retriable artifacts mixin for auto-retry on missing outputs and remove any mixin from prompt driven agents](https://github.com/Muriel-Salvan/composable_agents/commit/76073bebe8ee247d24a3bc3279c1db822640f47a)
19
+
20
+ ## Changes for composable-agents
21
+ ### Features
22
+
23
+ * [break: add retriable artifacts mixin for auto-retry on missing outputs and remove any mixin from prompt driven agents](https://github.com/Muriel-Salvan/composable_agents/commit/76073bebe8ee247d24a3bc3279c1db822640f47a)
24
+
1
25
  # [v2.2.0](https://github.com/Muriel-Salvan/composable_agents/compare/v2.1.3...v2.2.0) (2026-10-29 20:11:23)
2
26
 
3
27
  ## Global changes
data/README.md CHANGED
@@ -48,12 +48,14 @@ Whether you're building a code review assistant, a document summarizer, or a mul
48
48
  - [`ComposableAgents::AiAgents::Agent < PromptDrivenAgent`](#composableagentsaiagentsagent--promptdrivenagent)
49
49
  - [`ComposableAgents::Cline::Agent < PromptDrivenAgent`](#composableagentsclineagent--promptdrivenagent)
50
50
  - [`ComposableAgents::Cline::MissingSkillError < RuntimeError`](#composableagentsclinemissingskillerror--runtimeerror)
51
+ - [`ComposableAgents::Cline::ClineError < RuntimeError`](#composableagentsclineerror--runtimeerror)
51
52
  - [Mixins](#mixins)
52
53
  - [`ComposableAgents::Logger < ::Logger`](#composableagentslogger--logger)
53
54
  - [`ComposableAgents::Mixins::Resumable`](#composableagentsmixinsresumable)
54
55
  - [`ComposableAgents::Mixins::UserInteraction`](#composableagentsmixinsuserinteraction)
55
56
  - [`ComposableAgents::Mixins::ArtifactContract`](#composableagentsmixinsartifactcontract)
56
57
  - [`ComposableAgents::Mixins::Retriable`](#composableagentsmixinsretriable)
58
+ - [`ComposableAgents::Mixins::RetriableArtifacts`](#composableagentsmixinsretriableartifacts)
57
59
  - [Additional notes](#additional-notes)
58
60
  - [Documentation](#documentation)
59
61
  - [How it works](#how-it-works)
@@ -205,6 +207,7 @@ itinerary_agent = ComposableAgents::Cline::Agent.new(
205
207
  - πŸ“¦ **Typed artifact contracts** β€” Define and validate input/output schemas with descriptions, optional flags, and types (`:text`, `:markdown`, `:json`). The framework raises clear `MissingInputArtifactError`, `MissingOutputArtifactError`, or `ArtifactTypeError` on violations.
206
208
  - πŸ’Ύ **Resumable execution** β€” Persist step-by-step state to disk (via the `Resumable` mixin). Interrupted runs can be resumed seamlessly β€” previously completed steps are skipped, saving time and API costs.
207
209
  - πŸ” **Conditional retries** β€” Wrap an agent's `#run` with the `Retriable` mixin to retry on transient failures. Register condition procs via `#retry_on` that receive the agent and any raised exception (or `nil`) and return a policy hash (optional `:wait_secs`, optional `:modifier`) to wait-and-retry, or `nil` to skip. Unhandled exceptions are re-raised when no policy applies.
210
+ - πŸ” **Automatic artifact retry* β€” Prepend the RetriableArtifacts mixin to a PromptDrivenAgent subclass to automatically re-run when output artifacts declared in contracts are missing or in error. Configurable via `artifacts_errors_max_retries` (default: 3); raises `ArtifactsErrorsMaxRetriesExceededError` when the limit is exceeded.
208
211
  - πŸ—£οΈ **User interaction** β€” Agents can ask users clarifying questions mid-execution. Works out of the box via the terminal, or through an `ai-agents` tool integration for LLM-controlled workflows.
209
212
  - πŸ“‹ **Automatic conversation tracking** β€” Every prompt and response is automatically recorded with timestamps in a structured `conversation` store, ready for debugging or replay.
210
213
  - βš™οΈ **Flexible instruction system** β€” Use raw text, structured ordered-lists, or a mix of both to define agent instructions, rendered consistently by the chosen strategy.
@@ -361,6 +364,11 @@ The project is a Ruby library (not a CLI) β€” users install the gem and use its
361
364
  - **Description:** Raised by `Cline::Agent` when a referenced skill is not found in the global or project Cline configuration.
362
365
  - **Full documentation:** [cline/agent.rb on GitHub](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/cline/agent.rb)
363
366
 
367
+ #### `ComposableAgents::Cline::ClineError < RuntimeError`
368
+
369
+ - **Description:** Raised by `Cline::Agent` when a Cline CLI run fails.
370
+ - **Full documentation:** [cline/cline_error.rb on GitHub](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/cline/cline_error.rb)
371
+
364
372
  ---
365
373
 
366
374
  ### Mixins
@@ -480,6 +488,34 @@ Mixins are `prepend`-ed into an agent class to add specific capabilities.
480
488
  ```
481
489
  - **Full documentation:** [Mixins::Retriable on RubyDoc](https://www.rubydoc.info/gems/composable_agents/ComposableAgents/Mixins/Retriable) | [retriable.rb on GitHub](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/mixins/retriable.rb)
482
490
 
491
+
492
+ #### `ComposableAgents::Mixins::RetriableArtifacts`
493
+
494
+ - **Description:** Automatically retries the run when output artifacts declared in contracts are missing or in error on prompt-driven agents. Requires both the ArtifactContract and Retriable mixins to be prepended, and must be prepended on an agent descending from PromptDrivenAgent.
495
+ - **Public methods:**
496
+ - `#initialize(*args, artifacts_errors_max_retries: 3, **kwargs)` β€” Registers a retry policy that re-executes the run with a prompt asking for missing artifacts when output artifacts are missing or in error. The artifacts_errors_max_retries parameter (default: 3) sets the maximum number of retries before raising ArtifactsErrorsMaxRetriesExceededError.
497
+ - **Usage example:**
498
+ ```ruby
499
+ class MyAgent < ComposableAgents::PromptDrivenAgent
500
+ prepend ComposableAgents::Mixins::ArtifactContract
501
+ prepend ComposableAgents::Mixins::Retriable
502
+ prepend ComposableAgents::Mixins::RetriableArtifacts
503
+
504
+ def output_artifacts_contracts
505
+ { result: "Final result", logs: "Execution logs" }
506
+ end
507
+
508
+ def run(**inputs)
509
+ # Processing logic that may produce incomplete output artifacts
510
+ super
511
+ end
512
+ end
513
+
514
+ agent = MyAgent.new(artifacts_errors_max_retries: 3)
515
+ agent.run # Automatically retries if result or logs are missing or in error
516
+ ```
517
+ - **Raises:** ArtifactsErrorsMaxRetriesExceededError when the maximum number of retries is exceeded.
518
+
483
519
  ---
484
520
 
485
521
  ### Additional notes
@@ -680,7 +716,7 @@ npm install node-pty
680
716
  β”‚ β”œβ”€β”€ instructions.rb # Instruction system
681
717
  β”‚ β”œβ”€β”€ ai_agents/ # ai-agents backend
682
718
  β”‚ β”œβ”€β”€ cline/ # cline-rb backend
683
- β”‚ β”œβ”€β”€ mixins/ # Resumable, ArtifactContract, UserInteraction, Logger
719
+ β”‚ β”œβ”€β”€ mixins/ # ArtifactContract, Logger, Resumable, Retriable, RetriableArtifacts, UserInteraction
684
720
  β”‚ β”œβ”€β”€ prompt_rendering_strategy/ # Markdown & MarkdownHeavy strategies
685
721
  β”‚ └── utils/ # Markdown utilities
686
722
  β”œβ”€β”€ spec/ # Test suite
data/TODO.md CHANGED
@@ -1,5 +1,4 @@
1
1
  * Add the Retriable mixin to Cline::Agent with proper methods or config to define fallback models or changes in case of errors, with exponential waits.
2
- * Use the Retriable mixin with ArtifactContract.
3
2
  * Add a way to detect infinite loops in agents and act on it (maybe a mixin looking at prompts/messages).
4
3
  * Add a way to kill an agent run on a given timeout (generic mixin).
5
4
  * Check Rubocop-rspec exceptions and try to remove them.
@@ -0,0 +1,6 @@
1
+ ο»Ώmodule ComposableAgents
2
+ # Custom exception raised when the maximum number of retries for output artifacts
3
+ # errors (missing or in error) is exceeded and the run cannot succeed.
4
+ class ArtifactsErrorsMaxRetriesExceededError < StandardError
5
+ end
6
+ end
@@ -3,10 +3,6 @@ require 'cline'
3
3
  module ComposableAgents
4
4
  # All agents from this module work with the awesome cline-rb Rubygem
5
5
  module Cline
6
- # Missin skill error
7
- class MissingSkillError < RuntimeError
8
- end
9
-
10
6
  # Agent implementation that uses an ai-agent's AgentRunner.
11
7
  # This agent publishes the following run information:
12
8
  # - usage [Hash] Cumulative usage of the API requests (cost and tokens) made during the run.
@@ -23,8 +19,6 @@ module ComposableAgents
23
19
  class Agent < PromptDrivenAgent
24
20
  # @!group Public API
25
21
 
26
- prepend Mixins::ArtifactContract
27
-
28
22
  # Initialize a new agent that uses the Cline CLI in a dedicated config
29
23
  #
30
24
  # @param strategy [Module] The prompt rendering strategy
@@ -170,7 +164,7 @@ module ComposableAgents
170
164
  end,
171
165
  **@cli_options
172
166
  )
173
- raise "Error: #{extract_error_message(result[:error])}".strip if result[:error]
167
+ raise ClineError, "Error: #{extract_error_message(result[:error])}".strip if result[:error]
174
168
 
175
169
  # Keep the context in case we need to resume it
176
170
  if cline_cli.session&.messages
@@ -0,0 +1,7 @@
1
+ module ComposableAgents
2
+ module Cline
3
+ # Error raised when a Cline CLI run fails.
4
+ class ClineError < RuntimeError
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module ComposableAgents
2
+ module Cline
3
+ # Missing skill error
4
+ class MissingSkillError < RuntimeError
5
+ end
6
+ end
7
+ end
@@ -64,6 +64,15 @@ module ComposableAgents
64
64
 
65
65
  private
66
66
 
67
+ # Restrict the output artifacts errors that should raise an {OutputArtifactsError} at the end of a run
68
+ # to only those concerning artifacts defined in the output artifacts contracts.
69
+ # Errors on artifacts that are not part of the contracts are ignored.
70
+ #
71
+ # @return [Hash{Symbol => String}] The output artifacts errors that should raise, per artifact name
72
+ def output_artifacts_errors_to_raise
73
+ (defined?(super) ? super : {}).slice(*normalized_output_artifacts_contracts.keys)
74
+ end
75
+
67
76
  # Define input artifacts contracts
68
77
  #
69
78
  # @return [Hash{Symbol => Object}] Set of input artifacts contract, per artifact name
@@ -0,0 +1,92 @@
1
+ ο»Ώmodule ComposableAgents
2
+ module Mixins
3
+ # Mixin adding automatic retry of the run when output artifacts are missing on prompt-driven agents.
4
+ # This mixin requires the following mixins to be prepended:
5
+ # - The {ArtifactContract} mixin.
6
+ # - The {Retriable} mixin.
7
+ # This mixin requires to be prepended on an agent descending from {PromptDrivenAgent}.
8
+ #
9
+ # When prepended to an agent descending from PromptDrivenAgent, the run method will
10
+ # automatically re-execute until all expected output artifacts have been provided.
11
+ #
12
+ # On retry, the overridden #run method uses a prompt asking for missing artifacts
13
+ # as +user_instructions+ instead of the original one.
14
+ # When the run raised an {OutputArtifactsError}, artifacts having errors are also
15
+ # included in the prompt, taken from the exception data.
16
+ module RetriableArtifacts
17
+ # Initialize a new agent with the retry-on-missing-artifacts policy registered.
18
+ #
19
+ # @param artifacts_errors_max_retries [Integer] Maximum number of retries when output
20
+ # artifacts are missing or in error. Defaults to 3. When the number of retries
21
+ # exceeds this value, a {ArtifactsErrorsMaxRetriesExceededError} is raised.
22
+ # @param args [Array] Arguments passed to the parent initializer
23
+ # @param kwargs [Hash] Keyword arguments passed to the parent initializer
24
+ def initialize(*args, artifacts_errors_max_retries: 3, **kwargs)
25
+ super(*args, **kwargs)
26
+ @overridden_user_instructions = nil
27
+ max_retries = artifacts_errors_max_retries
28
+ artifacts_errors_retries = 0
29
+ last_artifact_keys = nil
30
+ retry_on(
31
+ proc do |_agent, exception|
32
+ output_artifacts_errors = exception.is_a?(OutputArtifactsError) ? exception.output_artifacts_errors : {}
33
+ missing = missing_artifacts(output_artifacts_errors)
34
+ artifact_keys = missing.keys
35
+
36
+ # Reset counter when the set of artifacts needing retry changes between iterations
37
+ if artifact_keys != last_artifact_keys
38
+ artifacts_errors_retries = 0
39
+ last_artifact_keys = artifact_keys
40
+ end
41
+
42
+ # If no artifacts need retry, return nil (no retry)
43
+ if artifact_keys.empty?
44
+ nil
45
+ elsif artifacts_errors_retries >= max_retries
46
+ artifacts_errors_retries = 0
47
+ last_artifact_keys = []
48
+ raise ArtifactsErrorsMaxRetriesExceededError,
49
+ "Maximum number of retries (#{max_retries}) exceeded. Missing or errored artifacts: #{artifact_keys.join(', ')}"
50
+ else
51
+ artifacts_errors_retries += 1
52
+ { modifier: proc { @overridden_user_instructions = missing_output_user_instructions(missing) } }
53
+ end
54
+ end
55
+ )
56
+ end
57
+
58
+ # Execute the agent to generate some output artifacts based on some input artifacts.
59
+ # On retry, uses +@overridden_user_instructions+ (set by the retry policy's modifier)
60
+ # instead of the original +user_instructions+.
61
+ #
62
+ # @param user_instructions [Object, nil] Instructions for the user prompt, that will be rendered.
63
+ # @param input_artifacts [Hash{Symbol => Object}] The input artifacts content, per artifact name
64
+ # @return [Hash{Symbol => Object}] The output artifacts
65
+ def run(user_instructions: nil, **input_artifacts)
66
+ if @overridden_user_instructions
67
+ user_instructions = @overridden_user_instructions
68
+ @overridden_user_instructions = nil
69
+ end
70
+ super
71
+ end
72
+
73
+ private
74
+
75
+ # Compute the missing output artifacts with their descriptions and errors.
76
+ #
77
+ # @param output_artifacts_errors [Hash{Symbol => String}] Artifacts having errors, per artifact name,
78
+ # as given by the {OutputArtifactsError} raised by the run (or empty if none).
79
+ # @return [Hash{Symbol => Hash{Symbol => Object}}] Missing artifacts, per artifact name.
80
+ def missing_artifacts(output_artifacts_errors)
81
+ normalized_output_artifacts_contracts
82
+ .reject { |artifact_name, _artifact_description| @output_artifacts.key?(artifact_name) }
83
+ .to_h do |artifact_name, artifact_description|
84
+ [
85
+ artifact_name,
86
+ artifact_description.merge(output_artifacts_errors[artifact_name] ? { error: output_artifacts_errors[artifact_name] } : {})
87
+ ]
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,16 @@
1
+ ο»Ώmodule ComposableAgents
2
+ # Custom exception raised when output artifacts have errors.
3
+ # The whole output_artifacts_errors structure is embedded in the exception.
4
+ class OutputArtifactsError < StandardError
5
+ # @return [Hash{Symbol => String}] The list of artifacts in error, with their corresponding error message.
6
+ attr_reader :output_artifacts_errors
7
+
8
+ # Initialize a new OutputArtifactsError with the errors hash.
9
+ #
10
+ # @param output_artifacts_errors [Hash] Hash of artifact names to errors
11
+ def initialize(output_artifacts_errors)
12
+ @output_artifacts_errors = output_artifacts_errors
13
+ super("Output artifacts errors: #{output_artifacts_errors.inspect}")
14
+ end
15
+ end
16
+ end
@@ -59,6 +59,8 @@ module ComposableAgents
59
59
  @system_instructions = system_instructions
60
60
  @constraints = constraints
61
61
  @conversation = []
62
+ @output_artifacts = {}
63
+ @output_artifacts_errors = {}
62
64
  end
63
65
 
64
66
  # Execute the agent to generate some output artifacts based on some input artifacts.
@@ -70,29 +72,14 @@ module ComposableAgents
70
72
  def run(user_instructions: nil, **input_artifacts)
71
73
  super
72
74
  @input_artifacts = input_artifacts
73
- @output_artifacts = {}
74
- @output_artifacts_errors = {}
75
75
  @system_prompt = render_system_prompt(render_instructions(@system_instructions))
76
76
  logger.debug "System prompt: #{@system_prompt}"
77
+ # TODO: Merge converse and prompt directly here. No need for this extra complexity now that Retriable has been moved out.
77
78
  converse(user_instructions, input_artifacts: @input_artifacts, author: 'User')
78
- if respond_to?(:normalized_output_artifacts_contracts, true)
79
- # We know which output artifacts we are expecting.
80
- # Therefore check if some are missing and prompt again if that's the case.
81
- # TODO: Implement a max number of retries and throw an exception if it exceeds.
82
- loop do
83
- missing_artifacts = normalized_output_artifacts_contracts
84
- .reject { |artifact_name, _artifact_description| @output_artifacts.key?(artifact_name) }
85
- .to_h do |artifact_name, artifact_description|
86
- [
87
- artifact_name,
88
- artifact_description.merge(@output_artifacts_errors[artifact_name] ? { error: @output_artifacts_errors[artifact_name] } : {})
89
- ]
90
- end
91
- break if missing_artifacts.empty?
92
-
93
- converse(missing_output_user_instructions(missing_artifacts))
94
- end
95
- end
79
+ # Raise an exception if some output artifacts have errors.
80
+ output_artifacts_errors = output_artifacts_errors_to_raise
81
+ raise OutputArtifactsError, output_artifacts_errors unless output_artifacts_errors.empty?
82
+
96
83
  @output_artifacts
97
84
  end
98
85
 
@@ -168,6 +155,15 @@ module ComposableAgents
168
155
 
169
156
  private
170
157
 
158
+ # Get the output artifacts errors that should raise an {OutputArtifactsError} at the end of a run.
159
+ # By default, all the errors that were reported during the run should raise.
160
+ # This is a hook that other mixins can override to restrict the errors that should raise.
161
+ #
162
+ # @return [Hash{Symbol => String}] The output artifacts errors that should raise, per artifact name
163
+ def output_artifacts_errors_to_raise
164
+ @output_artifacts_errors
165
+ end
166
+
171
167
  # Render instructions using the prompt rendering strategy.
172
168
  # Returns nil if instructions is nil.
173
169
  #
@@ -2,5 +2,5 @@ module ComposableAgents
2
2
  # @!group Public API
3
3
 
4
4
  # Gem version
5
- VERSION = '2.2.0'
5
+ VERSION = '3.1.0'
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: composable_agents
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Muriel Salvan
@@ -80,7 +80,10 @@ files:
80
80
  - lib/composable_agents/ai_agents/tools/base_tool.rb
81
81
  - lib/composable_agents/ai_agents/tools/create_artifact_tool.rb
82
82
  - lib/composable_agents/ai_agents/tools/get_artifact_tool.rb
83
+ - lib/composable_agents/artifacts_errors_max_retries_exceeded_error.rb
83
84
  - lib/composable_agents/cline/agent.rb
85
+ - lib/composable_agents/cline/cline_error.rb
86
+ - lib/composable_agents/cline/missing_skill_error.rb
84
87
  - lib/composable_agents/instructions.rb
85
88
  - lib/composable_agents/logger.rb
86
89
  - lib/composable_agents/logger/tagged.rb
@@ -88,7 +91,9 @@ files:
88
91
  - lib/composable_agents/mixins/artifact_contract.rb
89
92
  - lib/composable_agents/mixins/resumable.rb
90
93
  - lib/composable_agents/mixins/retriable.rb
94
+ - lib/composable_agents/mixins/retriable_artifacts.rb
91
95
  - lib/composable_agents/mixins/user_interaction.rb
96
+ - lib/composable_agents/output_artifacts_error.rb
92
97
  - lib/composable_agents/prompt_driven_agent.rb
93
98
  - lib/composable_agents/prompt_rendering_strategy/markdown.rb
94
99
  - lib/composable_agents/prompt_rendering_strategy/markdown_heavy.rb