composable_agents 3.7.0 → 3.7.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: 753d56bfc73e0b268d107ae37338f3efa465fc1fe559813228aa3498e6544d9e
4
- data.tar.gz: 21ac2d0a614a3044f392675dd988dce82c4ffcc5cb2c5192ae9a8f32416bab1d
3
+ metadata.gz: 816f8fd0898371ab594b7572d3154736711898c01d93de8269a7df6dfdeca623
4
+ data.tar.gz: be371808032d40d5464bd6e1292f6f395732ddb1e1fb0875e5f0736884856106
5
5
  SHA512:
6
- metadata.gz: 39d7fb3e1d61adb4ed78ca652b1f1aa5fc9b689cea004305de6ba14a56646725762ffb1c72965a69a516c804c5f3231197b474ebdcc4119830f708c9c85135b0
7
- data.tar.gz: b2a60316353effb352cd785e54faa5f0b3d07a01f3c46fdf447f0dfdb57dc990f495ed12fddaf70eeb800cccac4b05b71ac14543bf9057c17b32f90a313dd09a
6
+ metadata.gz: a5ec7f64ec25d84e17ef9a4e97878223fbfe12304442f0622d5f5953e9c3922b8787fea20cfd2fbe57c9c9e21928b6a7bc8aed4c14d8612d0f77abed78a3742b
7
+ data.tar.gz: e9a2a731c1a3885d00b239ccbf547dc03badc096afd57d52cc917c02b07e85cdd34d530be87cb4c384bee2fbee77867e22e3a338aae23aa1420bc790354b5546
data/CHANGELOG.md CHANGED
@@ -1,3 +1,27 @@
1
+ # [v3.7.2](https://github.com/Muriel-Salvan/composable_agents/compare/v3.7.1...v3.7.2) (2026-11-12 15:34:25)
2
+
3
+ ## Global changes
4
+ ### Patches
5
+
6
+ * [fix(strategies): expose options through agent accessors](https://github.com/Muriel-Salvan/composable_agents/commit/5a6901d858f6382e63efa49edd4dabf21efd8082)
7
+
8
+ ## Changes for strategies
9
+ ### Patches
10
+
11
+ * [fix(strategies): expose options through agent accessors](https://github.com/Muriel-Salvan/composable_agents/commit/5a6901d858f6382e63efa49edd4dabf21efd8082)
12
+
13
+ # [v3.7.1](https://github.com/Muriel-Salvan/composable_agents/compare/v3.7.0...v3.7.1) (2026-11-12 13:28:25)
14
+
15
+ ## Global changes
16
+ ### Patches
17
+
18
+ * [fix(MarkdownHeavyFiles): lazy-init MarkdownHeavyFiles artifact dirs](https://github.com/Muriel-Salvan/composable_agents/commit/bb64c69e65a60130e356c70755d0b862586a5351)
19
+
20
+ ## Changes for MarkdownHeavyFiles
21
+ ### Patches
22
+
23
+ * [fix(MarkdownHeavyFiles): lazy-init MarkdownHeavyFiles artifact dirs](https://github.com/Muriel-Salvan/composable_agents/commit/bb64c69e65a60130e356c70755d0b862586a5351)
24
+
1
25
  # [v3.7.0](https://github.com/Muriel-Salvan/composable_agents/compare/v3.6.1...v3.7.0) (2026-11-12 12:08:18)
2
26
 
3
27
  ## Global changes
data/README.md CHANGED
@@ -568,7 +568,7 @@ When a proc returns a policy, the agent waits `wait_secs` seconds, applies the `
568
568
  ### Additional notes
569
569
 
570
570
  - **No executables / CLI** — This gem is a library only; there are no scripts in `bin/`.
571
- - **Prompt rendering strategies** (`PromptRenderingStrategy::Markdown`, `PromptRenderingStrategy::MarkdownHeavy`, and `PromptRenderingStrategy::MarkdownHeavyFiles`) are not part of the public API themselves — they are included automatically by the agent's `strategy:` parameter.
571
+ - **Prompt rendering strategies** (`PromptRenderingStrategy::Markdown`, `PromptRenderingStrategy::MarkdownHeavy`, and `PromptRenderingStrategy::MarkdownHeavyFiles`) are not part of the public API themselves — they are included automatically by the agent's `strategy:` parameter. A strategy never defines `initialize`: when it can be customized, it exposes public accessors on the agent, resolved lazily and set at any time after construction (e.g. `PromptRenderingStrategy::MarkdownHeavyFiles#artifacts_directory=`).
572
572
  - **The `AiAgentUserInteraction` mixin** (`ComposableAgents::Mixins::AiAgentUserInteraction`) is an internal bridge that combines `UserInteraction` with `AiAgents::Agent`; it is used by prepending it to an `AiAgents::Agent` subclass.
573
573
  - See the [examples/](https://github.com/Muriel-Salvan/composable_agents/tree/main/examples) directory for complete, runnable scripts demonstrating all the above APIs.
574
574
 
@@ -679,7 +679,7 @@ Three strategies are included, mixed into the agent at initialization via `singl
679
679
 
680
680
  - **`PromptRenderingStrategy::Markdown`** — Clean, minimal Markdown. Simple instructions and artifact references.
681
681
  - **`PromptRenderingStrategy::MarkdownHeavy`** (default for Cline) — Elaborate prompts with execution checklists, structured artifact definition sections, and **JSON-based output parsing**. Agents format their artifacts as JSON blocks tagged with `output_artifact=NAME`, which the strategy parses back into the output hash. Includes type-aware parsing (`:text`, `:markdown`, `:json`).
682
- - **`PromptRenderingStrategy::MarkdownHeavyFiles`** — Same heavy Markdown behavior as `MarkdownHeavy`, but artifacts are persisted as files on disk instead of being in-lined or returned as JSON. Input artifacts are written to files that the assistant is asked to open, and output artifacts are expected as files that are read back from disk. Accepts an `artifacts_directory` constructor parameter (defaults to `#{composable_agents_dir}/artifacts`).
682
+ - **`PromptRenderingStrategy::MarkdownHeavyFiles`** — Same heavy Markdown behavior as `MarkdownHeavy`, but artifacts are persisted as files on disk instead of being in-lined or returned as JSON. Input artifacts are written to files that the assistant is asked to open, and output artifacts are expected as files that are read back from disk. It exposes a public `#artifacts_directory` accessor (defaulting to `#{composable_agents_dir}/artifacts`) that can be set on the agent at any time; its artifacts directories are resolved lazily and created on first use. Example: `agent.artifacts_directory = File.join(composable_agents_dir, 'my_artifacts')`.
683
683
 
684
684
  ### LLM backends: ai-agents vs cline-rb 🎯
685
685
 
@@ -56,6 +56,10 @@ module ComposableAgents
56
56
  # @return [RunInfo, nil] The current run info, or nil if no run has started yet
57
57
  attr_reader :current_run_info
58
58
 
59
+ # @return [String] Base directory where this agent stores its data (runs, artifacts...).
60
+ # Available to this agent's mixins and prompt rendering strategies.
61
+ attr_reader :composable_agents_dir
62
+
59
63
  # Get the logger to be used by this agent, with this agent's log fields (see #log_fields) prefixed to messages.
60
64
  # If the provided logger does not support fields tagging, it is used as-is (fields are ignored).
61
65
  # Memoized.
@@ -95,7 +95,7 @@ module ComposableAgents
95
95
  instructions: @system_prompt,
96
96
  tools: [
97
97
  Tools::CreateArtifactTool.new(self, logger:),
98
- Tools::GetArtifactTool.new(@input_artifacts, logger:)
98
+ Tools::GetArtifactTool.new(input_artifacts, logger:)
99
99
  ] + agent_tools
100
100
  )
101
101
  ] + @handoff_agents
@@ -214,7 +214,7 @@ module ComposableAgents
214
214
  find_skill(skill, selected_skills)
215
215
  end
216
216
  # Setup the temporary Cline global config dir
217
- agent_tmp_dir = "#{@composable_agents_dir}/tmp/#{Time.now.utc.strftime('%F-%H-%M-%S')}#{"-#{name.gsub(/[^\w.]/, '_')}" if name}"
217
+ agent_tmp_dir = "#{composable_agents_dir}/tmp/#{Time.now.utc.strftime('%F-%H-%M-%S')}#{"-#{name.gsub(/[^\w.]/, '_')}" if name}"
218
218
  ::Cline.configure do |config|
219
219
  config.debug = logger_debug?
220
220
  config.temp_dir_root = "#{agent_tmp_dir}/cline-rb"
@@ -208,7 +208,7 @@ module ComposableAgents
208
208
  # @param step_full_name [String] Step full name
209
209
  # @return [String] Corresponding JSON file that stores the step states
210
210
  def saved_step_states_json(step_full_name)
211
- "#{@composable_agents_dir}/runs/#{@run_id}/#{step_full_name.gsub(/[^\w.]/, '_')}.json"
211
+ "#{composable_agents_dir}/runs/#{@run_id}/#{step_full_name.gsub(/[^\w.]/, '_')}.json"
212
212
  end
213
213
 
214
214
  # Get saved step states from a given full step name.
@@ -10,6 +10,8 @@ module ComposableAgents
10
10
  #
11
11
  # Prompt rendering strategies are useful because different prompt-driven agents would benefit from different
12
12
  # prompt formats or structures (JSON, Markdown, explicit ordered lists, in-lining artifacts' contents without tools...).
13
+ # A strategy is a module that is included in the agent's singleton class when the agent is constructed
14
+ # (see PromptRenderingStrategy).
13
15
  # This agent automatically records non-rendered conversation (prompts + outputs) in a `conversation` store, part of its state.
14
16
  class PromptDrivenAgent < Agent
15
17
  # @!group Public API
@@ -42,7 +44,9 @@ module ComposableAgents
42
44
  # @param system_instructions [Object, nil] Original instructions for the agent, or nil if no system instructions are needed.
43
45
  # The kind of instructions that can be given are defined by the Instructions's constructor (see Instructions#initialize).
44
46
  # @param constraints [String, nil] Constraints to be respected, or nil for the agent's default
45
- # @param strategy [Module] The prompt rendering strategy
47
+ # @param strategy [Module] The prompt rendering strategy.
48
+ # Strategies must not define their own constructor: a strategy that can be customized exposes public accessors
49
+ # to be set on the agent (see PromptRenderingStrategy::MarkdownHeavyFiles#artifacts_directory=).
46
50
  def initialize(
47
51
  *args,
48
52
  role: nil,
@@ -161,6 +165,10 @@ module ComposableAgents
161
165
 
162
166
  private
163
167
 
168
+ # @return [Hash{Symbol => Object}, nil] The input artifacts of the current run, or nil if no run has started yet.
169
+ # Available to this agent's prompt rendering strategies.
170
+ attr_reader :input_artifacts
171
+
164
172
  # Get the output artifacts errors that should raise an {OutputArtifactsError} at the end of a run.
165
173
  # By default, all the errors that were reported during the run should raise.
166
174
  # This is a hook that other mixins can override to restrict the errors that should raise.
@@ -1,5 +1,15 @@
1
1
  module ComposableAgents
2
- # Collection of prompt rendering strategies that can be used with PromptDrivenAgent's agents
2
+ # Collection of prompt rendering strategies that can be used with PromptDrivenAgent's agents.
3
+ # A strategy is a module that is included in the agent's singleton class when the agent is constructed
4
+ # (see PromptDrivenAgent#initialize).
5
+ # Strategies must follow these rules:
6
+ # - They must not define an #initialize method: strategies don't have any constructor parameter.
7
+ # - A strategy that can be customized publishes public accessors on the agent, whose defaults are resolved lazily
8
+ # from the agent's public API, so that they can be set at any time on a constructed agent
9
+ # (see MarkdownHeavyFiles#artifacts_directory).
10
+ # - They use the agent's accessors (e.g. #name, #role, #objective, #constraints, #input_artifacts,
11
+ # #composable_agents_dir) — including its private ones, as strategies are part of the agent — and never access
12
+ # its instance variables.
3
13
  module PromptRenderingStrategy
4
14
  # Render prompt as Markdown documents
5
15
  module Markdown
@@ -30,44 +40,44 @@ module ComposableAgents
30
40
  end
31
41
 
32
42
  # Render the system prompt.
33
- # The following instance variables are accessible to render the prompt:
34
- # - `@input_artifacts`
35
- # - `@role`
36
- # - `@objective`
37
- # - `@constraints`
43
+ # The following accessors are available to render the prompt:
44
+ # - `#input_artifacts`
45
+ # - `#role`
46
+ # - `#objective`
47
+ # - `#constraints`
38
48
  #
39
49
  # @param rendered_instructions [String, nil] The rendered system instructions, or nil if none
40
50
  # @return [String] The rendered system prompt
41
51
  def render_system_prompt(rendered_instructions)
42
52
  sections = []
43
- sections << <<~EO_SECTION if @role && !@role.empty?
53
+ sections << <<~EO_SECTION if role && !role.empty?
44
54
  # Role
45
55
 
46
- #{Utils::Markdown.align_markdown_headers(@role, level: 2).strip}
56
+ #{Utils::Markdown.align_markdown_headers(role, level: 2).strip}
47
57
  EO_SECTION
48
- sections << <<~EO_SECTION if @objective && !@objective.empty?
58
+ sections << <<~EO_SECTION if objective && !objective.empty?
49
59
  # Objective
50
60
 
51
- #{Utils::Markdown.align_markdown_headers(@objective, level: 2).strip}
61
+ #{Utils::Markdown.align_markdown_headers(objective, level: 2).strip}
52
62
  EO_SECTION
53
63
  sections << <<~EO_SECTION if rendered_instructions && !rendered_instructions.empty?
54
64
  # Instructions
55
65
 
56
66
  #{Utils::Markdown.align_markdown_headers(rendered_instructions, level: 2).strip}
57
67
  EO_SECTION
58
- sections << <<~EO_SECTION if @constraints && !@constraints.empty?
68
+ sections << <<~EO_SECTION if constraints && !constraints.empty?
59
69
  # Constraints
60
70
 
61
- #{Utils::Markdown.align_markdown_headers(@constraints, level: 2).strip}
71
+ #{Utils::Markdown.align_markdown_headers(constraints, level: 2).strip}
62
72
  EO_SECTION
63
73
  sections.map(&:strip).join("\n\n")
64
74
  end
65
75
 
66
76
  # Render the user prompt
67
- # The following instance variables are accessible to render the prompt:
68
- # - `@role`
69
- # - `@objective`
70
- # - `@constraints`
77
+ # The following accessors are available to render the prompt:
78
+ # - `#role`
79
+ # - `#objective`
80
+ # - `#constraints`
71
81
  #
72
82
  # @param rendered_instructions [String, nil] The rendered instructions, or nil if none
73
83
  # @param input_artifacts [Hash{Symbol => Object}] The input artifacts content for which we render this prompt, per artifact name
@@ -47,11 +47,11 @@ module ComposableAgents
47
47
  end
48
48
 
49
49
  # Render the system prompt
50
- # The following instance variables are accessible to render the prompt:
51
- # - `@input_artifacts`
52
- # - `@role`
53
- # - `@objective`
54
- # - `@constraints`
50
+ # The following accessors are available to render the prompt:
51
+ # - `#input_artifacts`
52
+ # - `#role`
53
+ # - `#objective`
54
+ # - `#constraints`
55
55
  #
56
56
  # @param rendered_instructions [String, nil] The rendered system instructions, or nil if none
57
57
  # @return [String] The rendered system prompt
@@ -65,10 +65,10 @@ module ComposableAgents
65
65
  end
66
66
 
67
67
  # Render the user prompt
68
- # The following instance variables are accessible to render the prompt:
69
- # - `@role`
70
- # - `@objective`
71
- # - `@constraints`
68
+ # The following accessors are available to render the prompt:
69
+ # - `#role`
70
+ # - `#objective`
71
+ # - `#constraints`
72
72
  #
73
73
  # @param rendered_instructions [String, nil] The rendered instructions, or nil if none
74
74
  # @param input_artifacts [Hash{Symbol => Object}] The input artifacts content for which we render this prompt, per artifact name
@@ -15,33 +15,46 @@ module ComposableAgents
15
15
  # - `#parse_output_artifacts(text)` Parses a text that comes from the agent to retrieve output artifacts from files.
16
16
  # - `#artifact_ref(artifact_name) -> String` Returns the absolute path of the artifact file as seen by the assistant.
17
17
  # This can be used to refer to the artifact names properly in the user or system instructions.
18
- # The constructor accepts the following extra parameter:
19
- # - `artifacts_directory` [String, nil] Directory where artifacts are persisted, or nil to default to
20
- # `#{composable_agents_dir}/artifacts`. Input and output artifacts are persisted in the
21
- # `input` and `output` sub-directories respectively.
18
+ # Artifacts are persisted in a directory that defaults to `#{composable_agents_dir}/artifacts`, where input and
19
+ # output artifacts are persisted in its `input` and `output` sub-directories respectively.
20
+ # Those directories are resolved lazily and created on disk the first time they are needed, so that this strategy
21
+ # needs no state initialization and no constructor parameter.
22
+ # The directory can be customized at any time on an agent using this strategy by setting its
23
+ # #artifacts_directory accessor.
22
24
  module MarkdownHeavyFiles
23
- # @!group Internal
24
-
25
25
  include MarkdownHeavy
26
26
 
27
- # Constructor
27
+ # @!group Public API
28
+
29
+ # Get the directory where artifacts are persisted.
30
+ # Defaults to `#{composable_agents_dir}/artifacts`.
31
+ # Input and output artifacts are persisted in its `input` and `output` sub-directories respectively.
32
+ # Those directories are resolved lazily and created on disk the first time they are needed.
28
33
  #
29
- # @param artifacts_directory [String, nil] Directory where artifacts are persisted, or nil to default to
34
+ # @return [String] The directory where artifacts are persisted
35
+ def artifacts_directory
36
+ @artifacts_directory || "#{composable_agents_dir}/artifacts"
37
+ end
38
+
39
+ # Set the directory where artifacts are persisted (see #artifacts_directory).
40
+ # Already resolved input and output artifacts directories are reset, so that they follow the new directory
41
+ # the next time they are needed.
42
+ #
43
+ # @param directory [String, nil] The directory where artifacts are persisted, or nil to default to
30
44
  # `#{composable_agents_dir}/artifacts`
31
- def initialize(*args, artifacts_directory: nil, **kwargs)
32
- super(*args, **kwargs)
33
- artifacts_directory ||= "#{@composable_agents_dir}/artifacts"
34
- @input_artifacts_directory = File.join(artifacts_directory, 'input')
35
- @output_artifacts_directory = File.join(artifacts_directory, 'output')
36
- FileUtils.mkdir_p(@input_artifacts_directory)
37
- FileUtils.mkdir_p(@output_artifacts_directory)
45
+ def artifacts_directory=(directory)
46
+ @artifacts_directory = directory
47
+ @input_artifacts_directory = nil
48
+ @output_artifacts_directory = nil
38
49
  end
39
50
 
51
+ # @!group Internal
52
+
40
53
  # Render the user prompt
41
- # The following instance variables are accessible to render the prompt:
42
- # - `@role`
43
- # - `@objective`
44
- # - `@constraints`
54
+ # The following accessors are available to render the prompt:
55
+ # - `#role`
56
+ # - `#objective`
57
+ # - `#constraints`
45
58
  #
46
59
  # @param rendered_instructions [String, nil] The rendered instructions, or nil if none
47
60
  # @param input_artifacts [Hash{Symbol => Object}] The input artifacts content for which we render this prompt, per artifact name
@@ -134,6 +147,24 @@ module ComposableAgents
134
147
 
135
148
  private
136
149
 
150
+ # Get the directory where input artifacts are persisted.
151
+ # Created on disk the first time it is needed.
152
+ # Memoized.
153
+ #
154
+ # @return [String] The directory where input artifacts are persisted
155
+ def input_artifacts_directory
156
+ @input_artifacts_directory ||= File.join(artifacts_directory, 'input').tap { |dir| FileUtils.mkdir_p(dir) }
157
+ end
158
+
159
+ # Get the directory where output artifacts are persisted.
160
+ # Created on disk the first time it is needed.
161
+ # Memoized.
162
+ #
163
+ # @return [String] The directory where output artifacts are persisted
164
+ def output_artifacts_directory
165
+ @output_artifacts_directory ||= File.join(artifacts_directory, 'output').tap { |dir| FileUtils.mkdir_p(dir) }
166
+ end
167
+
137
168
  # Render the input artifacts concept section of the system prompt.
138
169
  #
139
170
  # @param input_contracts [Hash{Symbol => Hash{Symbol => Object}}] The normalized input artifacts contracts
@@ -218,7 +249,7 @@ module ComposableAgents
218
249
  # @return [String] The absolute path of the input artifact file
219
250
  def input_artifact_path(artifact_name)
220
251
  File.join(
221
- @input_artifacts_directory,
252
+ input_artifacts_directory,
222
253
  "#{artifact_name}#{artifact_extension(normalized_input_artifacts_contracts.dig(artifact_name, :type))}"
223
254
  )
224
255
  end
@@ -229,7 +260,7 @@ module ComposableAgents
229
260
  # @return [String] The absolute path of the output artifact file
230
261
  def output_artifact_path(artifact_name)
231
262
  File.join(
232
- @output_artifacts_directory,
263
+ output_artifacts_directory,
233
264
  "#{artifact_name}#{artifact_extension(normalized_output_artifacts_contracts.dig(artifact_name, :type))}"
234
265
  )
235
266
  end
@@ -2,5 +2,5 @@ module ComposableAgents
2
2
  # @!group Public API
3
3
 
4
4
  # Gem version
5
- VERSION = '3.7.0'
5
+ VERSION = '3.7.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: 3.7.0
4
+ version: 3.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Muriel Salvan