composable_agents 1.0.1 → 1.1.2

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: 3f9996134748aa3bf3ad0893b706a99a9c4611adcf5455591ef427230cebd64f
4
- data.tar.gz: 283f258cf24bf3dfca6575a0fe29f5cee5d8096de2cf0b40c73ac6060a78f76d
3
+ metadata.gz: 83d046d554869aa540c97465182338129f2aefbf14f92b20fc0aaf81ef54b632
4
+ data.tar.gz: 0b86bd8489a1b4b2f66a1ae20c33c543b055c559e07cafddc18924628a58ef01
5
5
  SHA512:
6
- metadata.gz: e7009799753fdd7216b8ef26c7756da633aa17665e4ca9ae6fc4f586fcc2501c2eade1e2ab7ac906fba5ddfe42888f4317cdd1611e20649d93eb3ab624ca58bc
7
- data.tar.gz: 5f374253ab859627a3c3a745f12b003e53149c3a358808990d67daef639013c0f57b3cbb6687fc2366d784937b09d932afaac893887f1d14e9a9ea3529298d3e
6
+ metadata.gz: 313bf41f9c1b24ce950ef3071adbf79a1433e4dea0d1b586964ef30854153c4f06e9a44ccbf852c2d798d8bd90e6bf2328f5f35beb799e433b0712250ad076c9
7
+ data.tar.gz: 94dfd245657963c6566b796d99bd7be1d5609d30c04b68d0ee0a6845f9b1082a33ed6494a3c0b45447120f20ef0f498d064ad3c89e5d7be09f873554de1bb0f4
data/CHANGELOG.md CHANGED
@@ -1,3 +1,34 @@
1
+ # [v1.1.2](https://github.com/Muriel-Salvan/composable_agents/compare/v1.1.1...v1.1.2) (2026-09-03 16:08:18)
2
+
3
+ ### Patches
4
+
5
+ * [ci: add RubyGems credential configuration workflow](https://github.com/Muriel-Salvan/composable_agents/commit/76219abd1356bf1fc070347a74aa4472c24b4fc6)
6
+
7
+ # [v1.1.1](https://github.com/Muriel-Salvan/composable_agents/compare/v1.1.0...v1.1.1) (2026-09-03 15:35:07)
8
+
9
+ ### Patches
10
+
11
+ * [build(deps): update ai-agents, commonmarker, and zeitwerk versions](https://github.com/Muriel-Salvan/composable_agents/commit/66fc281db2e68e6f07edcde15371a865bcf97d13)
12
+
13
+ # [v1.1.0](https://github.com/Muriel-Salvan/composable_agents/compare/v1.0.1...v1.1.0) (2026-09-03 14:36:17)
14
+
15
+ ## Global changes
16
+ ### Patches
17
+
18
+ * [docs(resumable): simplify type annotations in steps_run docs](https://github.com/Muriel-Salvan/composable_agents/commit/dc4ef8838791f0d62a65646b46cd2fc9600027c6)
19
+ * [feat: record error status and message for failed steps](https://github.com/Muriel-Salvan/composable_agents/commit/db54a74e92381012db19a5a00bb02b86c0885422)
20
+ * [feat(resumable): add created_at timestamp to step run tracking](https://github.com/Muriel-Salvan/composable_agents/commit/5158c186003ec8bbcde5fb5a10fad026f94f849c)
21
+ * [refactor: track step index in steps_run hierarchy](https://github.com/Muriel-Salvan/composable_agents/commit/8c0d73c1a3abb9b762d2d09be8430480eeb5f384)
22
+ * [[Feature(Resumable)] Implement steps_run with history of all runs.](https://github.com/Muriel-Salvan/composable_agents/commit/97e3f7477155aa6e2d3e6ee85e5389bb1d2ac70e)
23
+ * [[Feature(Resumable)] Add status to steps_run](https://github.com/Muriel-Salvan/composable_agents/commit/2abd016de83cee43a821674e219deba1f6b79a24)
24
+ * [feat: add steps_run hierarchy tracking to resumable mixin](https://github.com/Muriel-Salvan/composable_agents/commit/c656ea56a8ce81729075f8d98f0227c7402106e3)
25
+
26
+ ## Changes for Resumable
27
+ ### Features
28
+
29
+ * [[Feature(Resumable)] Implement steps_run with history of all runs.](https://github.com/Muriel-Salvan/composable_agents/commit/97e3f7477155aa6e2d3e6ee85e5389bb1d2ac70e)
30
+ * [[Feature(Resumable)] Add status to steps_run](https://github.com/Muriel-Salvan/composable_agents/commit/2abd016de83cee43a821674e219deba1f6b79a24)
31
+
1
32
  # [v1.0.1](https://github.com/Muriel-Salvan/composable_agents/compare/v1.0.0...v1.0.1) (2026-07-01 16:37:12)
2
33
 
3
34
  ### Patches
data/README.md CHANGED
@@ -610,7 +610,7 @@ flowchart LR
610
610
  E --> F
611
611
  ```
612
612
 
613
- The `Resumable` mixin tracks a **hierarchical step index** (`@steps_idx`) that mirrors the nesting of `step` blocks. Each step's input/output state is saved as a JSON file. On re-execution with the same `run_id`, the framework loads the saved state instead of re-running completed steps — saving both time and API costs.
613
+ The `Resumable` mixin tracks a **hierarchical step index** for each step, stored as the `index` property of each node in the `steps_run` hierarchy, mirroring the nesting of `step` blocks. Each step's input/output state is saved as a JSON file. On re-execution with the same `run_id`, the framework loads the saved state instead of re-running completed steps — saving both time and API costs.
614
614
 
615
615
  ## Development
616
616
 
@@ -23,6 +23,7 @@ module ComposableAgents
23
23
  # @param run_id [String, nil] ID identifying this run to reuse previously executed steps, or nil if there is no resumability needed
24
24
  def initialize(*args, run_id: nil, **kwargs)
25
25
  super(*args, **kwargs)
26
+ @steps_run = []
26
27
  @run_id = run_id
27
28
  end
28
29
 
@@ -35,37 +36,59 @@ module ComposableAgents
35
36
  def run(**input_artifacts)
36
37
  # The artifacts store, JSON serializable
37
38
  @artifacts = input_artifacts.dup
38
- # List of the levels' next step index, following the hierarchy of recusive step calls.
39
- # This is only used if there is a persistent run ID.
40
- # For example here are the values of this variable if we have this code:
41
- # # @steps_idx == [0]
42
- # step(:a) do
43
- # # @steps_idx == [0, 0]
44
- # step(:a1) do
45
- # # @steps_idx == [0, 0, 0]
46
- # end
47
- # # @steps_idx == [0, 1]
48
- # step(:a2) do
49
- # # @steps_idx == [0, 1, 0]
50
- # step(:a21) do
51
- # # @steps_idx == [0, 1, 0, 0]
52
- # end
53
- # # @steps_idx == [0, 1, 1]
54
- # step(:a22) do
55
- # # @steps_idx == [0, 1, 1, 0]
56
- # end
57
- # # @steps_idx == [0, 1, 2]
58
- # end
59
- # # @steps_idx == [0, 2]
60
- # end
61
- # # @steps_idx == [1]
62
- # step(:b) do
63
- # # @steps_idx == [1, 0]
64
- # end
65
- @steps_idx = [0] unless @run_id.nil?
39
+ # List of the steps_run hierarchies, one per run. Each run appends its own new list.
40
+ @steps_run << []
41
+ @current_step_node = nil
66
42
  super
67
43
  end
68
44
 
45
+ # Hierarchies of the steps executed during each run of this agent.
46
+ #
47
+ # @return [Array<Array<Hash{Symbol => Object}>>>] List of per-run step hierarchies.
48
+ # Each element of this array corresponds to a run, and is the ordered list of top-level step nodes of that run.
49
+ # Each step node has those properties:
50
+ # - step_name [Symbol] The step name.
51
+ # - index [Array<Integer>] Position of the step in the hierarchy of recursive step calls.
52
+ # It is the succession of indices from the root of the hierarchy to this step node,
53
+ # each index being the position of the node among its sibling nodes (children of the same parent).
54
+ # For example here are the values of the index property of the step nodes if we have this code:
55
+ # # index == [0]
56
+ # step(:a) do
57
+ # # index == [0, 0]
58
+ # step(:a1) do
59
+ # # index == [0, 0, 0]
60
+ # end
61
+ # # index == [0, 1]
62
+ # step(:a2) do
63
+ # # index == [0, 1, 0]
64
+ # step(:a21) do
65
+ # # index == [0, 1, 0, 0]
66
+ # end
67
+ # # index == [0, 1, 1]
68
+ # step(:a22) do
69
+ # # index == [0, 1, 1, 0]
70
+ # end
71
+ # # index == [0, 1, 2]
72
+ # end
73
+ # # index == [0, 2]
74
+ # end
75
+ # # index == [1]
76
+ # step(:b) do
77
+ # # index == [1, 0]
78
+ # end
79
+ # - status [Symbol] The status of the step:
80
+ # - `started``: The step has started its execution.
81
+ # - `cached``: The step was skipped because of a previous execution.
82
+ # - `executed``: The step has completed its execution.
83
+ # - `error``: The step has raised an exception during its execution.
84
+ # - created_at [Time] Timestamp of the creation of this step node.
85
+ # - extra_input_artifacts [Hash] Additional artifacts given as input to this step.
86
+ # - agent [Agent, nil] The agent that is invoked in this step, or nil if none.
87
+ # - error [String, nil] The message of the exception raised during the step execution.
88
+ # This property is only present when the status is `error`.
89
+ # - children [Array<Hash>] Sub-steps that are invoked from this step.
90
+ attr_reader :steps_run
91
+
69
92
  private
70
93
 
71
94
  # Define a step that can be serialized and resumed.
@@ -105,33 +128,73 @@ module ComposableAgents
105
128
  # @param extra_input_artifacts [Hash{Symbol => Object}] Additional input artifacts to merge before the step executes.
106
129
  # @yield The code called for this step
107
130
  def internal_step(step_name:, agent:, extra_input_artifacts: {})
108
- @artifacts.merge!(extra_input_artifacts)
109
- if @run_id.nil?
110
- yield
111
- else
112
- # Compute the current step state
113
- step_state = current_step_state(agent:)
114
- # Read the persisted step state if any
115
- step_full_name = "#{@steps_idx.join('-')}-#{step_name}"
116
- saved_input_state, saved_output_state = saved_step_states(step_full_name)
117
- # If the input exists, it means the step was already executed.
118
- # If it is the same state as the current one, skip the step and set the current state to the stored output step state.
119
- if step_state == saved_input_state
120
- set_current_step_state(saved_output_state, agent:)
121
- log_debug "[Step #{step_full_name}] - Already executed - Got #{@artifacts.size} artifacts from persistence: #{@artifacts.keys.join(', ')}"
122
- else
123
- # Clone state before yielding because it will certainly be modified
124
- input_step_state = clone_step_state(step_state)
125
- @steps_idx << 0
131
+ record_step(step_name:, agent:, extra_input_artifacts:) do
132
+ @artifacts.merge!(extra_input_artifacts)
133
+ if @run_id.nil?
126
134
  yield
127
- @steps_idx.pop
128
- store_step_states(step_full_name, input: input_step_state, output: current_step_state(agent:))
129
- log_debug "[Step #{step_full_name}] - Executed - Stored #{@artifacts.size} artifacts in persistence: #{@artifacts.keys.join(', ')}"
135
+ @current_step_node[:status] = :executed
136
+ else
137
+ # Compute the current step state
138
+ step_state = current_step_state(agent:)
139
+ # Read the persisted step state if any
140
+ step_full_name = "#{@current_step_node[:index].join('-')}-#{step_name}"
141
+ saved_input_state, saved_output_state = saved_step_states(step_full_name)
142
+ # If the input exists, it means the step was already executed.
143
+ # If it is the same state as the current one, skip the step and set the current state to the stored output step state.
144
+ if step_state == saved_input_state
145
+ @current_step_node[:status] = :cached
146
+ set_current_step_state(saved_output_state, agent:)
147
+ log_debug "[Step #{step_full_name}] - Already executed - Got #{@artifacts.size} artifacts from persistence: #{@artifacts.keys.join(', ')}"
148
+ else
149
+ # Clone state before yielding because it will certainly be modified
150
+ input_step_state = clone_step_state(step_state)
151
+ yield
152
+ @current_step_node[:status] = :executed
153
+ store_step_states(step_full_name, input: input_step_state, output: current_step_state(agent:))
154
+ log_debug "[Step #{step_full_name}] - Executed - Stored #{@artifacts.size} artifacts in persistence: #{@artifacts.keys.join(', ')}"
155
+ end
130
156
  end
131
- @steps_idx[-1] += 1
132
157
  end
133
158
  end
134
159
 
160
+ # Record a step node in the steps hierarchy, then execute the step's code.
161
+ # Steps executed while this node is the current one are recorded as its children.
162
+ #
163
+ # @param step_name [Symbol] Name of the step, mirroring the one used by the persisted steps.
164
+ # @param agent [ComposableAgents::Agent, nil] The agent run by this step, or nil for plain steps.
165
+ # @param extra_input_artifacts [Hash{Symbol => Object}] Input artifacts given to the step.
166
+ # @yield The code of the step to be executed
167
+ def record_step(step_name:, agent:, extra_input_artifacts:)
168
+ @steps_run ||= []
169
+ parent_node = @current_step_node
170
+ node = {
171
+ step_name:,
172
+ # Compute the index of this step in the hierarchy of recursive step calls:
173
+ # the indices of the ancestor step nodes, followed by this node's position among its sibling nodes.
174
+ index: (parent_node ? parent_node[:index] : []) + [(parent_node ? parent_node[:children] : @steps_run.last).size],
175
+ status: :started,
176
+ created_at: Time.now,
177
+ agent:,
178
+ extra_input_artifacts: extra_input_artifacts.clone,
179
+ children: []
180
+ }
181
+ if parent_node
182
+ parent_node[:children] << node
183
+ else
184
+ @steps_run.last << node
185
+ end
186
+ @current_step_node = node
187
+ begin
188
+ yield
189
+ rescue Exception => e # rubocop:disable Lint/RescueException -- Record the error status of any failing step, including Ctrl-C interruptions (Interrupt)
190
+ node[:status] = :error
191
+ node[:error] = e.message
192
+ raise
193
+ end
194
+ ensure
195
+ @current_step_node = parent_node
196
+ end
197
+
135
198
  # Get the current step state.
136
199
  #
137
200
  # @param agent [Agent, nil] Agent that is used in this step, or nil if none.
@@ -2,5 +2,5 @@ module ComposableAgents
2
2
  # @!group Public API
3
3
 
4
4
  # Gem version
5
- VERSION = '1.0.1'
5
+ VERSION = '1.1.2'
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: 1.0.1
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Muriel Salvan
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '0.10'
18
+ version: '0.12'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '0.10'
25
+ version: '0.12'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: cline-rb
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -43,28 +43,28 @@ dependencies:
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '2.7'
46
+ version: '2.10'
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '2.7'
53
+ version: '2.10'
54
54
  - !ruby/object:Gem::Dependency
55
55
  name: zeitwerk
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '2.7'
60
+ version: '2.8'
61
61
  type: :runtime
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: '2.7'
67
+ version: '2.8'
68
68
  email: muriel@x-aeon.com
69
69
  executables: []
70
70
  extensions: []