composable_agents 3.1.0 → 3.3.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: 13903597d02eeacf544613b91cf9c60de076d39ecd2f3cd22b549e8a992646aa
4
- data.tar.gz: 33bfbf930e6a4e43d87efadecbc1efc8a4ccf8ba29f804757b53c281d1db702f
3
+ metadata.gz: 1f49d4d1885ee0ce3abc08d156910295fa39b602450f68588259473acd62d30a
4
+ data.tar.gz: 715281f951f66e05ca81b6a237b03c690ca319d9f6ee093471db560fd03ce441
5
5
  SHA512:
6
- metadata.gz: 9102890f35cef152fa02cd9ec0a2ce960b27112c4e3134865accee7c7e8b1eb8e53914ca8030543b4c35a72dfe0d7a5c07c6172d61152c90cead8bdc4183b030
7
- data.tar.gz: 5e0c71dd4d3aefd7b02a1e4183151d85e7b6ea5f8eb498589e2d5dde099ff4706d10d186dfa214c665bbffb30b74eaed9c8f4b2fe0ab4e81e6f5e238ed979ff5
6
+ metadata.gz: ac0d9cbbfd7a3e6c6155e7f3276264b7c86c1ed6f68ebab6742665b25d44d14c99d544e07ff2ca22dcf59d68af6dd8064a1612c727fc39aa4318ad2de1a28c8d
7
+ data.tar.gz: 7bdb521e572cfc1e75f108a4d2d46aaadd7ba409e7e2cebddef4e0062b6db2e83cb0ade32429e7b7d21651061ac56786201142952c7a339cf57c23870132ae2a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
+ # [v3.3.0](https://github.com/Muriel-Salvan/composable_agents/compare/v3.2.0...v3.3.0) (2026-11-02 17:21:04)
2
+
3
+ ## Global changes
4
+ ### Patches
5
+
6
+ * [feat(cline): add RetriableCline mixin](https://github.com/Muriel-Salvan/composable_agents/commit/66daaa29898faf17026661042484320d82811c8a)
7
+
8
+ ## Changes for cline
9
+ ### Features
10
+
11
+ * [feat(cline): add RetriableCline mixin](https://github.com/Muriel-Salvan/composable_agents/commit/66daaa29898faf17026661042484320d82811c8a)
12
+
13
+ # [v3.2.0](https://github.com/Muriel-Salvan/composable_agents/compare/v3.1.0...v3.2.0) (2026-11-02 16:54:36)
14
+
15
+ ## Global changes
16
+ ### Patches
17
+
18
+ * [feat(Retriable): enhance retry policy options with new_input_artifacts, altering retried input artifacts](https://github.com/Muriel-Salvan/composable_agents/commit/e4ee0fe4105bdddb87670817006aedee6a7f3840)
19
+
20
+ ## Changes for Retriable
21
+ ### Features
22
+
23
+ * [feat(Retriable): enhance retry policy options with new_input_artifacts, altering retried input artifacts](https://github.com/Muriel-Salvan/composable_agents/commit/e4ee0fe4105bdddb87670817006aedee6a7f3840)
24
+
1
25
  # [v3.1.0](https://github.com/Muriel-Salvan/composable_agents/compare/v3.0.0...v3.1.0) (2026-11-01 13:55:18)
2
26
 
3
27
  ## Global changes
data/README.md CHANGED
@@ -463,11 +463,14 @@ Mixins are `prepend`-ed into an agent class to add specific capabilities.
463
463
 
464
464
  #### `ComposableAgents::Mixins::Retriable`
465
465
 
466
- - **Description:** Adds conditional retry capabilities to agents. Retry condition procs are registered with `#retry_on` (or `#with_retry_on` for temporary activation) and are evaluated at the end of each run. A proc receives the agent instance and the exception raised by `run` (or `nil` if none) and returns either `nil` (no retry) or a policy hash with `:wait_secs` and optional `:modifier` keys. When a proc returns a policy, the agent waits `wait_secs` seconds, optionally calls the policy's `modifier`, and re-runs. If `run` raised an exception and no proc returned a retry policy, the exception is re-raised to the caller.
466
+ - **Description:** Adds conditional retry capabilities to agents. Retry condition procs are registered with `#retry_on` (or `#with_retry_on` for temporary activation) and are evaluated at the end of each run. A proc receives the agent instance and the exception raised by `run` (or `nil` if none) and returns either `nil` (no retry) or a policy hash with optional keys to describe the policy:
467
+ - `:wait_secs` — Wait time in seconds before retrying (defaults to 0).
468
+ - `:modifier` — Proc called with the agent before retrying ( defaults to doing nothing).
469
+ - `:new_input_artifacts` — Hash of input artifacts to add to the retried run (defaults to `{}`).
470
+ When a proc returns a policy, the agent waits `wait_secs` seconds, applies the `modifier` proc (if any), merges the `new_input_artifacts` into the retry input, and re-runs. If `run` raised an exception and no proc returned a retry policy, the exception is re-raised to the caller.
467
471
  - **Public methods:**
468
- - `#retry_on(proc)` — Register a retry condition proc. Returns `self` so calls can be chained.
472
+ - `#retry_on(proc)` — Register a retry condition proc.
469
473
  - `#with_retry_on(proc, &block)` — Register a retry proc for the duration of a block, then unregister it when the block returns (even if it raises).
470
- - policy hash — Configuration with optional `:wait_secs` (seconds to wait before retrying, defaults to 0) and optional `:modifier` (proc called with the agent before retrying).
471
474
  - **Usage example:**
472
475
  ```ruby
473
476
  class MyAgent < ComposableAgents::Agent
@@ -518,6 +521,38 @@ Mixins are `prepend`-ed into an agent class to add specific capabilities.
518
521
 
519
522
  ---
520
523
 
524
+ #### `ComposableAgents::Mixins::RetriableCline`
525
+
526
+ - **Description:** Automatically retries the run when Cline raises errors (such as `UnexpectedExitStatusError` or `UnexpectedInteractiveSessionError`) using exponential backoff with jitter. Requires the `Retriable` mixin to be prepended first, and must be prepended on an agent descending from `ComposableAgents::Cline::Agent`.
527
+
528
+ - **Public methods:**
529
+ - `#initialize(*args, cline_errors_max_retries: 3, cline_errors_retry_initial_delay_secs: 5, cline_errors_retry_backoff_factor: 2, cline_errors_retry_max_delay_secs: 60, cline_errors_retry_jitter: 0.1, **kwargs)` — Registers a retry policy that re-executes the run with exponential backoff when Cline errors occur. Configuration options include:
530
+ - `cline_errors_max_retries`: Maximum number of retries (default: 3)
531
+ - `cline_errors_retry_initial_delay_secs`: Initial delay in seconds before first retry (default: 5)
532
+ - `cline_errors_retry_backoff_factor`: Multiplier for each subsequent retry (default: 2)
533
+ - `cline_errors_retry_max_delay_secs`: Maximum delay between retries (default: 60)
534
+ - `cline_errors_retry_jitter`: Amount of random jitter to add (default: 0.1 = 10%)
535
+ - `#run(user_instructions: nil, **input_artifacts)` — Re-executes the run, using stored user instructions from interactive session errors if applicable.
536
+
537
+ - **Usage example:**
538
+ ```ruby
539
+ class MyClineAgent < ComposableAgents::Cline::Agent
540
+ prepend ComposableAgents::Mixins::Retriable
541
+ prepend ComposableAgents::Mixins::RetriableCline
542
+
543
+ def run(**inputs)
544
+ super
545
+ end
546
+ end
547
+
548
+ agent = MyClineAgent.new(cline_errors_max_retries: 3, cline_errors_retry_initial_delay_secs: 2)
549
+ agent.run # Automatically retries on Cline errors with exponential backoff
550
+ ```
551
+
552
+ - **Raises:** Various Cline error classes (e.g., `ComposableAgents::Cline::ClineError`, `::Cline::Cli::UnexpectedExitStatusError`, `::Cline::Cli::UnexpectedInteractiveSessionError`) when the maximum number of retries is exceeded.
553
+
554
+ ---
555
+
521
556
  ### Additional notes
522
557
 
523
558
  - **No executables / CLI** — This gem is a library only; there are no scripts in `bin/`.
@@ -716,7 +751,7 @@ npm install node-pty
716
751
  │ ├── instructions.rb # Instruction system
717
752
  │ ├── ai_agents/ # ai-agents backend
718
753
  │ ├── cline/ # cline-rb backend
719
- │ ├── mixins/ # ArtifactContract, Logger, Resumable, Retriable, RetriableArtifacts, UserInteraction
754
+ │ ├── mixins/ # ArtifactContract, Logger, Resumable, Retriable, RetriableArtifacts, RetriableCline, UserInteraction
720
755
  │ ├── prompt_rendering_strategy/ # Markdown & MarkdownHeavy strategies
721
756
  │ └── utils/ # Markdown utilities
722
757
  ├── spec/ # Test suite
data/TODO.md CHANGED
@@ -1,4 +1,5 @@
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.
1
+ * Add a new mixin for Cline::Agent with proper methods or config to define retries in case of errors, with exponential waits.
2
+ * Add a new retry mixin for Cline::Agent with proper methods or config to define retries in case of errors and 429 issues to define fallback models in case of errors, with exponential waits (reuse code from the RetriableCline mixin).
2
3
  * Add a way to detect infinite loops in agents and act on it (maybe a mixin looking at prompts/messages).
3
4
  * Add a way to kill an agent run on a given timeout (generic mixin).
4
5
  * Check Rubocop-rspec exceptions and try to remove them.
@@ -17,14 +17,13 @@ module ComposableAgents
17
17
  # * Each registered proc is kept in an internal array, in registration order.
18
18
  # * A proc receives the agent's instance and the exception raised by `run`
19
19
  # (or `nil` if none) and returns either `nil` (no retry) or a policy hash
20
- # with optional `:wait_secs` and optional `:modifier` keys.
20
+ # (see Retriable#retry_on).
21
21
  # * The run method is wrapped: it calls the original run, then evaluates the
22
22
  # registered procs in order until one returns a non-nil policy hash.
23
23
  # * If `run` raised an exception and no proc returned a non-nil policy hash,
24
24
  # the exception is re-raised to the caller.
25
25
  # * If a proc decides to retry: wait for `wait_secs` seconds, then call the
26
- # `modifier` proc (if any), then call run again.
27
- #
26
+ # `modifier` proc (if any), merge the `new_input_artifacts` into the retry input, and call run again.
28
27
  module Retriable
29
28
  # @!group Public API
30
29
 
@@ -41,8 +40,9 @@ module ComposableAgents
41
40
  # - param exception [Exception, nil] The exception that was raised by the run, or nil if none.
42
41
  # - result [Hash\\{Symbol => Object}] Definition of the retry policy, with optional properties:
43
42
  # - wait_secs [Integer, Float] Wait time in seconds before retrying (defaults to 0).
44
- # - modifier [Proc] Code called to modify the agent's state before retrying (defaults to doing nothing).
43
+ # - modifier [#call(agent)] Code called to modify the agent's state before retrying (defaults to doing nothing).
45
44
  # - param agent [Agent] The agent for which the retry is considered.
45
+ # - new_input_artifacts [Hash\\{Symbol => Object}] New input artifacts that need to be added to the retried run (defaults to {}).
46
46
  # @return [self]
47
47
  def retry_on(proc)
48
48
  retry_policies << proc
@@ -78,12 +78,13 @@ module ComposableAgents
78
78
  # @param input_artifacts [Hash{Symbol => Object}] input artifacts
79
79
  # @return [Hash{Symbol => Object}] output artifacts from the last run
80
80
  def run(**input_artifacts)
81
+ retry_input_artifacts = input_artifacts.dup
81
82
  output_artifacts = nil
82
83
  retry_index = 0
83
84
  loop do
84
85
  exception = nil
85
86
  begin
86
- output_artifacts = super
87
+ output_artifacts = super(**retry_input_artifacts)
87
88
  rescue StandardError => e
88
89
  exception = e
89
90
  end
@@ -98,6 +99,7 @@ module ComposableAgents
98
99
  logger.debug "Retry ##{retry_index}: re-running in #{wait_secs}s#{" (exception #{exception})" if exception}"
99
100
  sleep(wait_secs) if wait_secs.positive?
100
101
  policy[:modifier]&.call(self)
102
+ retry_input_artifacts.merge!(policy[:new_input_artifacts] || {})
101
103
  else
102
104
  raise exception if exception
103
105
 
@@ -23,7 +23,7 @@
23
23
  # @param kwargs [Hash] Keyword arguments passed to the parent initializer
24
24
  def initialize(*args, artifacts_errors_max_retries: 3, **kwargs)
25
25
  super(*args, **kwargs)
26
- @overridden_user_instructions = nil
26
+ @retriable_artifacts_user_instructions = nil
27
27
  max_retries = artifacts_errors_max_retries
28
28
  artifacts_errors_retries = 0
29
29
  last_artifact_keys = nil
@@ -49,27 +49,12 @@
49
49
  "Maximum number of retries (#{max_retries}) exceeded. Missing or errored artifacts: #{artifact_keys.join(', ')}"
50
50
  else
51
51
  artifacts_errors_retries += 1
52
- { modifier: proc { @overridden_user_instructions = missing_output_user_instructions(missing) } }
52
+ { new_input_artifacts: { user_instructions: missing_output_user_instructions(missing) } }
53
53
  end
54
54
  end
55
55
  )
56
56
  end
57
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
58
  private
74
59
 
75
60
  # Compute the missing output artifacts with their descriptions and errors.
@@ -0,0 +1,68 @@
1
+ require 'cline'
2
+
3
+ module ComposableAgents
4
+ module Mixins
5
+ # Mixin adding automatic retry of the run when Cline is raising errors.
6
+ # This mixin requires the following mixins to be prepended:
7
+ # - The {Retriable} mixin.
8
+ # This mixin requires to be prepended on an agent descending from {ComposableAgents::Cline::Agent}.
9
+ #
10
+ # When prepended to an agent descending from ComposableAgents::Cline::Agent, the run method will
11
+ # automatically re-execute while errors are being raised, using exponential backoff with jitter.
12
+ module RetriableCline
13
+ # Constructor
14
+ #
15
+ # @param cline_errors_max_retries [Integer] Maximum number of retries for Cline errors
16
+ # @param cline_errors_retry_initial_delay_secs [Float] Initial delay in seconds before the first retry
17
+ # @param cline_errors_retry_backoff_factor [Float] Backoff multiplier for each subsequent retry
18
+ # @param cline_errors_retry_max_delay_secs [Float] Maximum delay in seconds between retries
19
+ # @param cline_errors_retry_jitter [Float] Amount of random jitter to add to the delay
20
+ def initialize(
21
+ *args,
22
+ cline_errors_max_retries: 3,
23
+ cline_errors_retry_initial_delay_secs: 5,
24
+ cline_errors_retry_backoff_factor: 2,
25
+ cline_errors_retry_max_delay_secs: 60,
26
+ cline_errors_retry_jitter: 0.1,
27
+ **kwargs
28
+ )
29
+ super(*args, **kwargs)
30
+ @retriable_cline_user_instructions = nil
31
+ max_retries = cline_errors_max_retries
32
+ initial_delay = cline_errors_retry_initial_delay_secs
33
+ backoff_factor = cline_errors_retry_backoff_factor
34
+ max_delay = cline_errors_retry_max_delay_secs
35
+ jitter = cline_errors_retry_jitter
36
+ retries = 0
37
+ retry_on(
38
+ proc do |_agent, exception|
39
+ next nil if retries >= max_retries || CLINE_ERROR_CLASSES.none? { |klass| exception.is_a?(klass) }
40
+
41
+ retries += 1
42
+ delay = initial_delay * (backoff_factor**(retries - 1))
43
+ delay *= (1 + (jitter * ((2 * rand) - 1))) if jitter.positive?
44
+ retry_policy = { wait_secs: delay.clamp(0, max_delay) }
45
+ if exception.is_a?(::Cline::Cli::UnexpectedInteractiveSessionError)
46
+ retry_policy[:new_input_artifacts] = { user_instructions: INTERACTIVE_SESSION_USER_INSTRUCTIONS }
47
+ end
48
+ retry_policy
49
+ end
50
+ )
51
+ end
52
+
53
+ # @return [Array<Class>] List of Cline error classes that trigger a retry of the run when raised by Cline.
54
+ CLINE_ERROR_CLASSES = [
55
+ ComposableAgents::Cline::ClineError,
56
+ ::Cline::Cli::UnexpectedExitStatusError,
57
+ ::Cline::Cli::UnexpectedInteractiveSessionError
58
+ ].freeze
59
+
60
+ # @return [String] Default user instructions used to retry a run that raised an {::Cline::Cli::UnexpectedInteractiveSessionError}.
61
+ INTERACTIVE_SESSION_USER_INSTRUCTIONS = <<~TEXT.freeze
62
+ You are not running in interactive mode, so you will not receive any input from the user.
63
+ If you were expecting to ask the user a question, you must find the best answer to your question by yourself
64
+ and proceed without user input.
65
+ TEXT
66
+ end
67
+ end
68
+ end
@@ -2,5 +2,5 @@ module ComposableAgents
2
2
  # @!group Public API
3
3
 
4
4
  # Gem version
5
- VERSION = '3.1.0'
5
+ VERSION = '3.3.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: 3.1.0
4
+ version: 3.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Muriel Salvan
@@ -92,6 +92,7 @@ files:
92
92
  - lib/composable_agents/mixins/resumable.rb
93
93
  - lib/composable_agents/mixins/retriable.rb
94
94
  - lib/composable_agents/mixins/retriable_artifacts.rb
95
+ - lib/composable_agents/mixins/retriable_cline.rb
95
96
  - lib/composable_agents/mixins/user_interaction.rb
96
97
  - lib/composable_agents/output_artifacts_error.rb
97
98
  - lib/composable_agents/prompt_driven_agent.rb