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 +4 -4
- data/CHANGELOG.md +24 -0
- data/README.md +39 -4
- data/TODO.md +2 -1
- data/lib/composable_agents/mixins/retriable.rb +7 -5
- data/lib/composable_agents/mixins/retriable_artifacts.rb +2 -17
- data/lib/composable_agents/mixins/retriable_cline.rb +68 -0
- data/lib/composable_agents/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1f49d4d1885ee0ce3abc08d156910295fa39b602450f68588259473acd62d30a
|
|
4
|
+
data.tar.gz: 715281f951f66e05ca81b6a237b03c690ca319d9f6ee093471db560fd03ce441
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
#
|
|
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),
|
|
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 [
|
|
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
|
-
@
|
|
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
|
-
{
|
|
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
|
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.
|
|
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
|