composable_agents 2.1.3 → 2.2.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: 6b82cf2e2c4224e28888344a4b352dcbd74433103d15123361c1e88e8717708a
4
- data.tar.gz: bda57cd1244fb916d68557aea692c52657db06a7d26ad6e578dcb1893923b7a7
3
+ metadata.gz: 28c6711f6b4e8807c0e62349cfd5e246bf3ed56c9c1a613d14843d5b1769477d
4
+ data.tar.gz: a7433cc6cc01b80eb12c1b4f0eb04c8b5391e2bd10ec88aa47608ab5d25e0f0b
5
5
  SHA512:
6
- metadata.gz: 07c251a27221c7a59785f9c5ab442b97c3c6679ece82bca14057112e70021b3a8878c23b6aa8cf36adda474a158a64491e75d4c78228c6ce98886f3fafba8327
7
- data.tar.gz: 8a758b5c34330e777253e19598e72b15705d3c81445df788fba73d79edcb3354693e3b5aa6ce85ff794056493128943510afb095d7b6ed725e403af0241e91e8
6
+ metadata.gz: dcffae4c60bfb6c5424cc41134ab5466e15c1f3a0b2580e4cef7d9d6c2634b5b43d721cd9f5e4bf604a968af35c02b561409cd9885f028b27bf61d7bbb2fc775
7
+ data.tar.gz: c83544d5ab8f321a63c87a7eb035763b12a573a3d3495e653b301c0ce71edc076838e5af84216d64395c497ec8efc7aff13be136e96c399e84b277f9cd7db0b0
data/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ # [v2.2.0](https://github.com/Muriel-Salvan/composable_agents/compare/v2.1.3...v2.2.0) (2026-10-29 20:11:23)
2
+
3
+ ## Global changes
4
+ ### Patches
5
+
6
+ * [feat(composable_agents): add Retriable mixin with conditional retries](https://github.com/Muriel-Salvan/composable_agents/commit/5217fb5b448f7fffc155f864888d55da48d66074)
7
+
8
+ ## Changes for composable_agents
9
+ ### Features
10
+
11
+ * [feat(composable_agents): add Retriable mixin with conditional retries](https://github.com/Muriel-Salvan/composable_agents/commit/5217fb5b448f7fffc155f864888d55da48d66074)
12
+
13
+ ## Changes for retriable
14
+ ### Features
15
+
16
+ * [feat(composable_agents): add Retriable mixin with conditional retries](https://github.com/Muriel-Salvan/composable_agents/commit/5217fb5b448f7fffc155f864888d55da48d66074)
17
+
1
18
  # [v2.1.3](https://github.com/Muriel-Salvan/composable_agents/compare/v2.1.2...v2.1.3) (2026-10-27 13:52:46)
2
19
 
3
20
  ### Patches
data/README.md CHANGED
@@ -53,6 +53,7 @@ Whether you're building a code review assistant, a document summarizer, or a mul
53
53
  - [`ComposableAgents::Mixins::Resumable`](#composableagentsmixinsresumable)
54
54
  - [`ComposableAgents::Mixins::UserInteraction`](#composableagentsmixinsuserinteraction)
55
55
  - [`ComposableAgents::Mixins::ArtifactContract`](#composableagentsmixinsartifactcontract)
56
+ - [`ComposableAgents::Mixins::Retriable`](#composableagentsmixinsretriable)
56
57
  - [Additional notes](#additional-notes)
57
58
  - [Documentation](#documentation)
58
59
  - [How it works](#how-it-works)
@@ -203,6 +204,7 @@ itinerary_agent = ComposableAgents::Cline::Agent.new(
203
204
  - 📝 **Two prompt rendering strategies** — Choose between clean **Markdown** for simple agents, or **MarkdownHeavy** with structured output parsing, execution checklists, and typed artifact support for complex agentic systems.
204
205
  - 📦 **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.
205
206
  - 💾 **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
+ - 🔁 **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.
206
208
  - 🗣️ **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.
207
209
  - 📋 **Automatic conversation tracking** — Every prompt and response is automatically recorded with timestamps in a structured `conversation` store, ready for debugging or replay.
208
210
  - ⚙️ **Flexible instruction system** — Use raw text, structured ordered-lists, or a mix of both to define agent instructions, rendered consistently by the chosen strategy.
@@ -448,8 +450,36 @@ Mixins are `prepend`-ed into an agent class to add specific capabilities.
448
450
  agent.run(name: 'World') # => { greeting: "Hello, World!" }
449
451
  agent.run(foo: 'bar') # Raises MissingInputArtifactError
450
452
  ```
453
+
451
454
  - **Full documentation:** [Mixins::ArtifactContract on RubyDoc](https://www.rubydoc.info/gems/composable_agents/ComposableAgents/Mixins/ArtifactContract) | [artifact_contract.rb on GitHub](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/mixins/artifact_contract.rb)
452
455
 
456
+ #### `ComposableAgents::Mixins::Retriable`
457
+
458
+ - **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.
459
+ - **Public methods:**
460
+ - `#retry_on(proc)` — Register a retry condition proc. Returns `self` so calls can be chained.
461
+ - `#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).
462
+ - 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).
463
+ - **Usage example:**
464
+ ```ruby
465
+ class MyAgent < ComposableAgents::Agent
466
+ prepend ComposableAgents::Mixins::Retriable
467
+
468
+ def run(**inputs)
469
+ # Your processing here, may raise transient errors
470
+ { result: inputs[:input].upcase }
471
+ end
472
+ end
473
+
474
+ agent = MyAgent.new
475
+ # Retry once with a short wait when the first run fails.
476
+ agent.retry_on(lambda do |agent_instance, exception|
477
+ agent_instance.run_count == 1 ? { wait_secs: 1 } : nil
478
+ end)
479
+ agent.run(input: 'hello')
480
+ ```
481
+ - **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
+
453
483
  ---
454
484
 
455
485
  ### Additional notes
@@ -477,6 +507,7 @@ Mixins are `prepend`-ed into an agent class to add specific capabilities.
477
507
  - [`Mixins::Resumable`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/mixins/resumable.rb) — Resumable workflow mixin
478
508
  - [`Mixins::ArtifactContract`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/mixins/artifact_contract.rb) — Artifact validation mixin
479
509
  - [`Mixins::UserInteraction`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/mixins/user_interaction.rb) — User question-asking mixin
510
+ - [`Mixins::Retriable`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/mixins/retriable.rb) — Conditional retry mixin
480
511
  - [`Logger`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/logger.rb) — Standard logger class
481
512
  - [`PromptRenderingStrategy::Markdown`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/prompt_rendering_strategy/markdown.rb) — Markdown prompt strategy
482
513
  - [`PromptRenderingStrategy::MarkdownHeavy`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/prompt_rendering_strategy/markdown_heavy.rb) — Heavy Markdown prompt strategy
@@ -579,6 +610,7 @@ Two strategies are included, mixed into the agent at initialization via `singlet
579
610
 
580
611
  Mixins are **prepended** (using `prepend`) or **included** (using `include`) to override `#run` or add new methods:
581
612
 
613
+ - **[`Mixins::Retriable`](lib/composable_agents/mixins/retriable.rb)** — Wraps `#run` with conditional retry logic. Register condition procs via `#retry_on` that receive the agent and any exception raised by `run` (or `nil`) and return a policy hash (`:wait_secs`, `:modifier`) to trigger a wait-and-retry, or `nil` to skip retrying. If `run` raises and no policy is returned, the exception is re-raised.
582
614
  - **[`Mixins::ArtifactContract`](lib/composable_agents/mixins/artifact_contract.rb)** — Wraps `#run` to validate inputs before and outputs after execution against declared contracts. Raises `MissingInputArtifactError`, `MissingOutputArtifactError`, or `ArtifactTypeError` on violations.
583
615
  - **[`Mixins::Resumable`](lib/composable_agents/mixins/resumable.rb)** — Overrides `#run` with a **step-based execution model**. Each `step` block is persisted to `.composable_agents/runs/{run_id}/` as JSON. On re-run, completed steps are skipped — only new steps execute. Supports nested steps and agent state serialization via `export_state`/`import_state`.
584
616
  - **[`Mixins::UserInteraction`](lib/composable_agents/mixins/user_interaction.rb)** — Adds an `#ask(question)` method. By default prompts the terminal; override `#answer_to` for custom behavior.
data/TODO.md CHANGED
@@ -1,3 +1,7 @@
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
+ * Add a way to detect infinite loops in agents and act on it (maybe a mixin looking at prompts/messages).
4
+ * Add a way to kill an agent run on a given timeout (generic mixin).
1
5
  * Check Rubocop-rspec exceptions and try to remove them.
2
6
  * Add agents manifesto for contributors.
3
7
  * Add some info logging (not debug) that would allow detecting when infinite loops or blocked agents occur.
@@ -0,0 +1,118 @@
1
+ module ComposableAgents
2
+ module Mixins
3
+ # Mixin adding retriable capabilities to agents.
4
+ #
5
+ # An agent prepending this mixin can define retry conditions that are
6
+ # evaluated at the end of each run. The first condition that decides to
7
+ # retry triggers a wait and a re-execution of the run.
8
+ #
9
+ # # Public interface
10
+ #
11
+ # * `#retry_on(&block)` registers a new retry condition proc.
12
+ # * `#with_retry_on(proc, &block)` registers a retry proc for the duration
13
+ # of a code block and unregisters it afterwards.
14
+ #
15
+ # # Behaviour
16
+ #
17
+ # * Each registered proc is kept in an internal array, in registration order.
18
+ # * A proc receives the agent's instance and the exception raised by `run`
19
+ # (or `nil` if none) and returns either `nil` (no retry) or a policy hash
20
+ # with optional `:wait_secs` and optional `:modifier` keys.
21
+ # * The run method is wrapped: it calls the original run, then evaluates the
22
+ # registered procs in order until one returns a non-nil policy hash.
23
+ # * If `run` raised an exception and no proc returned a non-nil policy hash,
24
+ # the exception is re-raised to the caller.
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
+ #
28
+ module Retriable
29
+ # @!group Public API
30
+
31
+ # Register a proc that evaluates whether the agent should be retried
32
+ # after a run.
33
+ #
34
+ # This method can be called several times with different procs to define
35
+ # various criteria to retry. The registered procs are evaluated in the
36
+ # order they were registered.
37
+ #
38
+ # @param proc [Proc] the retry condition proc. It receives the agent's
39
+ # instance and the exception raised by `run` (or `nil` if none).
40
+ # - param agent [Agent] The agent for which the retry is considered.
41
+ # - param exception [Exception, nil] The exception that was raised by the run, or nil if none.
42
+ # - result [Hash\\{Symbol => Object}] Definition of the retry policy, with optional properties:
43
+ # - 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).
45
+ # - param agent [Agent] The agent for which the retry is considered.
46
+ # @return [self]
47
+ def retry_on(proc)
48
+ retry_policies << proc
49
+ self
50
+ end
51
+
52
+ # Register a retry proc for the duration of a code block, then
53
+ # unregister it when the block returns (even if it raises).
54
+ #
55
+ # The block can itself register other procs (with {#retry_on}) and
56
+ # those are kept after the block ends; only the proc given to this
57
+ # method is unregistered.
58
+ #
59
+ # @param proc [Proc] the retry condition proc (see #retry_on)
60
+ # @yield the code block in which the proc is active
61
+ def with_retry_on(proc, &)
62
+ retry_on(proc)
63
+ yield
64
+ ensure
65
+ retry_policies.delete(proc)
66
+ end
67
+
68
+ # Wrap the agent's run method with retry logic.
69
+ #
70
+ # It calls the original run, then evaluates the registered procs in
71
+ # order until one returns a non-nil policy hash. Each proc receives
72
+ # the agent's instance and the exception raised by `run` (or `nil` if
73
+ # none). If `run` raised an exception and no proc returned a non-nil
74
+ # policy hash, the exception is re-raised to the caller. If one does,
75
+ # it waits for the policy's `wait_secs`, applies the `modifier` proc
76
+ # (if any) and calls run again.
77
+ #
78
+ # @param input_artifacts [Hash{Symbol => Object}] input artifacts
79
+ # @return [Hash{Symbol => Object}] output artifacts from the last run
80
+ def run(**input_artifacts)
81
+ output_artifacts = nil
82
+ retry_index = 0
83
+ loop do
84
+ exception = nil
85
+ begin
86
+ output_artifacts = super
87
+ rescue StandardError => e
88
+ exception = e
89
+ end
90
+ policy = nil
91
+ retry_policies.each do |policy_proc|
92
+ policy = policy_proc.call(self, exception)
93
+ break if policy
94
+ end
95
+ if policy
96
+ retry_index += 1
97
+ wait_secs = policy[:wait_secs] || 0
98
+ logger.debug "Retry ##{retry_index}: re-running in #{wait_secs}s#{" (exception #{exception})" if exception}"
99
+ sleep(wait_secs) if wait_secs.positive?
100
+ policy[:modifier]&.call(self)
101
+ else
102
+ raise exception if exception
103
+
104
+ break
105
+ end
106
+ end
107
+ output_artifacts
108
+ end
109
+
110
+ private
111
+
112
+ # @return [Array<Proc>] the registered retry condition procs
113
+ def retry_policies
114
+ @retry_policies ||= []
115
+ end
116
+ end
117
+ end
118
+ end
@@ -2,5 +2,5 @@ module ComposableAgents
2
2
  # @!group Public API
3
3
 
4
4
  # Gem version
5
- VERSION = '2.1.3'
5
+ VERSION = '2.2.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.1.3
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Muriel Salvan
@@ -87,6 +87,7 @@ files:
87
87
  - lib/composable_agents/mixins/ai_agent_user_interaction.rb
88
88
  - lib/composable_agents/mixins/artifact_contract.rb
89
89
  - lib/composable_agents/mixins/resumable.rb
90
+ - lib/composable_agents/mixins/retriable.rb
90
91
  - lib/composable_agents/mixins/user_interaction.rb
91
92
  - lib/composable_agents/prompt_driven_agent.rb
92
93
  - lib/composable_agents/prompt_rendering_strategy/markdown.rb