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 +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +31 -1
- data/TODO.md +0 -1
- data/lib/composable_agents/artifacts_errors_max_retries_exceeded_error.rb +6 -0
- data/lib/composable_agents/cline/agent.rb +0 -2
- data/lib/composable_agents/mixins/artifact_contract.rb +9 -0
- data/lib/composable_agents/mixins/retriable_artifacts.rb +92 -0
- data/lib/composable_agents/output_artifacts_error.rb +16 -0
- data/lib/composable_agents/prompt_driven_agent.rb +16 -20
- data/lib/composable_agents/version.rb +1 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a0ee11ea2faf7acea0c666c5dda07d94ba31eca150153c07c5ed8b8a8f408195
|
|
4
|
+
data.tar.gz: f5385011f38ce4ce3968e2b67f1866fe0134c94432dcc9c58d6373a8421af901
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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,
|
|
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.
|
|
@@ -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
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
#
|
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:
|
|
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
|