composable_agents 2.2.0 β†’ 3.0.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: a0ee11ea2faf7acea0c666c5dda07d94ba31eca150153c07c5ed8b8a8f408195
4
+ data.tar.gz: f5385011f38ce4ce3968e2b67f1866fe0134c94432dcc9c58d6373a8421af901
5
5
  SHA512:
6
- metadata.gz: dcffae4c60bfb6c5424cc41134ab5466e15c1f3a0b2580e4cef7d9d6c2634b5b43d721cd9f5e4bf604a968af35c02b561409cd9885f028b27bf61d7bbb2fc775
7
- data.tar.gz: c83544d5ab8f321a63c87a7eb035763b12a573a3d3495e653b301c0ce71edc076838e5af84216d64395c497ec8efc7aff13be136e96c399e84b277f9cd7db0b0
6
+ metadata.gz: 21967310c2483efc63f164ca2a2c46f9d96c5965253bcd6fc036a4717d2ad0ce46536001a172ea440476df0e6c0039134d98baed9e8d359b429130882837f83b
7
+ data.tar.gz: 93d7d8bea59ec3a9a6aa7a7a021948a7af38a9292566418de1387dfc35ef22b98cad4bd56d4bee7c607e5591f1050bab6cb576488ba444db62184839572cc46b
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ # [v3.0.0](https://github.com/Muriel-Salvan/composable_agents/compare/v2.2.0...v3.0.0) (2026-11-01 12:34:56)
2
+
3
+ ## Global changes
4
+ ### Breaking changes
5
+
6
+ * [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)
7
+
8
+ ## Changes for composable-agents
9
+ ### Features
10
+
11
+ * [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)
12
+
1
13
  # [v2.2.0](https://github.com/Muriel-Salvan/composable_agents/compare/v2.1.3...v2.2.0) (2026-10-29 20:11:23)
2
14
 
3
15
  ## Global changes
data/README.md CHANGED
@@ -54,6 +54,7 @@ Whether you're building a code review assistant, a document summarizer, or a mul
54
54
  - [`ComposableAgents::Mixins::UserInteraction`](#composableagentsmixinsuserinteraction)
55
55
  - [`ComposableAgents::Mixins::ArtifactContract`](#composableagentsmixinsartifactcontract)
56
56
  - [`ComposableAgents::Mixins::Retriable`](#composableagentsmixinsretriable)
57
+ - [`ComposableAgents::Mixins::RetriableArtifacts`](#composableagentsmixinsretriableartifacts)
57
58
  - [Additional notes](#additional-notes)
58
59
  - [Documentation](#documentation)
59
60
  - [How it works](#how-it-works)
@@ -205,6 +206,7 @@ itinerary_agent = ComposableAgents::Cline::Agent.new(
205
206
  - πŸ“¦ **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
207
  - πŸ’Ύ **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
208
  - πŸ” **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.
209
+ - πŸ” **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
210
  - πŸ—£οΈ **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
211
  - πŸ“‹ **Automatic conversation tracking** β€” Every prompt and response is automatically recorded with timestamps in a structured `conversation` store, ready for debugging or replay.
210
212
  - βš™οΈ **Flexible instruction system** β€” Use raw text, structured ordered-lists, or a mix of both to define agent instructions, rendered consistently by the chosen strategy.
@@ -480,6 +482,34 @@ Mixins are `prepend`-ed into an agent class to add specific capabilities.
480
482
  ```
481
483
  - **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
484
 
485
+
486
+ #### `ComposableAgents::Mixins::RetriableArtifacts`
487
+
488
+ - **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.
489
+ - **Public methods:**
490
+ - `#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.
491
+ - **Usage example:**
492
+ ```ruby
493
+ class MyAgent < ComposableAgents::PromptDrivenAgent
494
+ prepend ComposableAgents::Mixins::ArtifactContract
495
+ prepend ComposableAgents::Mixins::Retriable
496
+ prepend ComposableAgents::Mixins::RetriableArtifacts
497
+
498
+ def output_artifacts_contracts
499
+ { result: "Final result", logs: "Execution logs" }
500
+ end
501
+
502
+ def run(**inputs)
503
+ # Processing logic that may produce incomplete output artifacts
504
+ super
505
+ end
506
+ end
507
+
508
+ agent = MyAgent.new(artifacts_errors_max_retries: 3)
509
+ agent.run # Automatically retries if result or logs are missing or in error
510
+ ```
511
+ - **Raises:** ArtifactsErrorsMaxRetriesExceededError when the maximum number of retries is exceeded.
512
+
483
513
  ---
484
514
 
485
515
  ### Additional notes
@@ -680,7 +710,7 @@ npm install node-pty
680
710
  β”‚ β”œβ”€β”€ instructions.rb # Instruction system
681
711
  β”‚ β”œβ”€β”€ ai_agents/ # ai-agents backend
682
712
  β”‚ β”œβ”€β”€ cline/ # cline-rb backend
683
- β”‚ β”œβ”€β”€ mixins/ # Resumable, ArtifactContract, UserInteraction, Logger
713
+ β”‚ β”œβ”€β”€ mixins/ # ArtifactContract, Logger, Resumable, Retriable, RetriableArtifacts, UserInteraction
684
714
  β”‚ β”œβ”€β”€ prompt_rendering_strategy/ # Markdown & MarkdownHeavy strategies
685
715
  β”‚ └── utils/ # Markdown utilities
686
716
  β”œβ”€β”€ 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
@@ -23,8 +23,6 @@ module ComposableAgents
23
23
  class Agent < PromptDrivenAgent
24
24
  # @!group Public API
25
25
 
26
- prepend Mixins::ArtifactContract
27
-
28
26
  # Initialize a new agent that uses the Cline CLI in a dedicated config
29
27
  #
30
28
  # @param strategy [Module] The prompt rendering strategy
@@ -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.0.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.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Muriel Salvan
@@ -80,6 +80,7 @@ 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
84
85
  - lib/composable_agents/instructions.rb
85
86
  - lib/composable_agents/logger.rb
@@ -88,7 +89,9 @@ files:
88
89
  - lib/composable_agents/mixins/artifact_contract.rb
89
90
  - lib/composable_agents/mixins/resumable.rb
90
91
  - lib/composable_agents/mixins/retriable.rb
92
+ - lib/composable_agents/mixins/retriable_artifacts.rb
91
93
  - lib/composable_agents/mixins/user_interaction.rb
94
+ - lib/composable_agents/output_artifacts_error.rb
92
95
  - lib/composable_agents/prompt_driven_agent.rb
93
96
  - lib/composable_agents/prompt_rendering_strategy/markdown.rb
94
97
  - lib/composable_agents/prompt_rendering_strategy/markdown_heavy.rb