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 +4 -4
- data/CHANGELOG.md +31 -0
- data/README.md +1 -1
- data/lib/composable_agents/mixins/resumable.rb +113 -50
- data/lib/composable_agents/version.rb +1 -1
- metadata +7 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 83d046d554869aa540c97465182338129f2aefbf14f92b20fc0aaf81ef54b632
|
|
4
|
+
data.tar.gz: 0b86bd8489a1b4b2f66a1ae20c33c543b055c559e07cafddc18924628a58ef01
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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**
|
|
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
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
@
|
|
128
|
-
|
|
129
|
-
|
|
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.
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
67
|
+
version: '2.8'
|
|
68
68
|
email: muriel@x-aeon.com
|
|
69
69
|
executables: []
|
|
70
70
|
extensions: []
|