composable_agents 1.0.0 โ 1.0.1
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 +6 -0
- data/README.md +851 -11
- data/TODO.md +0 -1
- data/lib/composable_agents/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3f9996134748aa3bf3ad0893b706a99a9c4611adcf5455591ef427230cebd64f
|
|
4
|
+
data.tar.gz: 283f258cf24bf3dfca6575a0fe29f5cee5d8096de2cf0b40c73ac6060a78f76d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e7009799753fdd7216b8ef26c7756da633aa17665e4ca9ae6fc4f586fcc2501c2eade1e2ab7ac906fba5ddfe42888f4317cdd1611e20649d93eb3ab624ca58bc
|
|
7
|
+
data.tar.gz: 5f374253ab859627a3c3a745f12b003e53149c3a358808990d67daef639013c0f57b3cbb6687fc2366d784937b09d932afaac893887f1d14e9a9ea3529298d3e
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
# [v1.0.1](https://github.com/Muriel-Salvan/composable_agents/compare/v1.0.0...v1.0.1) (2026-07-01 16:37:12)
|
|
2
|
+
|
|
3
|
+
### Patches
|
|
4
|
+
|
|
5
|
+
* [docs: expand README with full documentation, usage examples, and badges](https://github.com/Muriel-Salvan/composable_agents/commit/d7ba08fe6408248a2a00338a6638135ba9626116)
|
|
6
|
+
|
|
1
7
|
# [v0.0.1](https://github.com/Muriel-Salvan/composable_agents/compare/...v0.0.1) (2026-07-01 15:49:59)
|
|
2
8
|
|
|
3
9
|
### Patches
|
data/README.md
CHANGED
|
@@ -1,29 +1,869 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
1
3
|
# composable_agents
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
A Ruby framework for building **composable, prompt-driven AI agent pipelines** โ mix, match, and orchestrate agents into reusable workflows.
|
|
6
|
+
|
|
7
|
+
[](https://github.com/Muriel-Salvan/composable_agents/actions/workflows/continuous_integration.yml)
|
|
8
|
+
[](https://codecov.io/gh/Muriel-Salvan/composable_agents)
|
|
9
|
+
[](https://github.com/Muriel-Salvan/composable_agents/stargazers)
|
|
10
|
+
[](LICENSE)
|
|
11
|
+
[](https://rubygems.org/gems/composable_agents)
|
|
12
|
+
[](https://rubygems.org/gems/composable_agents)
|
|
13
|
+
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
**composable_agents** is a Ruby gem that lets you build modular AI agent pipelines ๐งฉ โ compose simple agents together into complex, resumable workflows.
|
|
17
|
+
|
|
18
|
+
Think of it as **LEGOยฎ for AI agents**: each agent is a self-contained unit that takes input artifacts, processes them (via an LLM, custom Ruby code, or a sub-agent), and produces output artifacts. You can:
|
|
19
|
+
|
|
20
|
+
- ๐ง **Create prompt-driven agents** with role, objective, instructions, and constraints
|
|
21
|
+
- ๐ **Chain agents together** so the output of one becomes the input of another
|
|
22
|
+
- ๐ฆ **Define typed artifact contracts** with validation for inputs/outputs
|
|
23
|
+
- ๐พ **Resume interrupted runs** โ long workflows keep their state between executions
|
|
24
|
+
- ๐ฃ๏ธ **Let agents ask users questions** when they need clarification
|
|
25
|
+
- ๐ฏ **Integrate with multiple LLM backends** via [cline-rb](https://github.com/Muriel-Salvan/cline-rb) or [ai-agents](https://github.com/nicbarker/ai-agents)
|
|
26
|
+
- ๐ **Use flexible prompt rendering** (Markdown, or heavy Markdown with structured outputs)
|
|
27
|
+
|
|
28
|
+
Whether you're building a code review assistant, a document summarizer, or a multi-step research pipeline, composable_agents gives you the building blocks to design, test, and run AI agent systems โ all from Ruby.
|
|
29
|
+
|
|
30
|
+
## Table of contents
|
|
31
|
+
|
|
32
|
+
- [Quick start](#quick-start)
|
|
33
|
+
- [Installation](#installation)
|
|
34
|
+
- [Basic usage: create a composed pipeline of agents](#basic-usage-create-a-composed-pipeline-of-agents)
|
|
35
|
+
- [Using the Cline backend instead](#using-the-cline-backend-instead)
|
|
36
|
+
- [Next steps](#next-steps)
|
|
37
|
+
- [Requirements](#requirements)
|
|
38
|
+
- [Features](#features)
|
|
39
|
+
- [Public API](#public-api)
|
|
40
|
+
- [Module constant](#module-constant)
|
|
41
|
+
- [`ComposableAgents::VERSION`](#composableagentsversion)
|
|
42
|
+
- [Core agent classes](#core-agent-classes)
|
|
43
|
+
- [`ComposableAgents::Agent`](#composableagentsagent)
|
|
44
|
+
- [`ComposableAgents::RubyAgent < Agent`](#composableagentsrubyagent--agent)
|
|
45
|
+
- [`ComposableAgents::Instructions`](#composableagentsinstructions)
|
|
46
|
+
- [`ComposableAgents::PromptDrivenAgent < Agent`](#composableagentspromptdrivenagent--agent)
|
|
47
|
+
- [LLM backend agent classes](#llm-backend-agent-classes)
|
|
48
|
+
- [`ComposableAgents::AiAgents::Agent < PromptDrivenAgent`](#composableagentsaiagentsagent--promptdrivenagent)
|
|
49
|
+
- [`ComposableAgents::Cline::Agent < PromptDrivenAgent`](#composableagentsclineagent--promptdrivenagent)
|
|
50
|
+
- [`ComposableAgents::Cline::MissingSkillError < RuntimeError`](#composableagentsclinemissingskillerror--runtimeerror)
|
|
51
|
+
- [Mixins](#mixins)
|
|
52
|
+
- [`ComposableAgents::Mixins::Logger`](#composableagentsmixinslogger)
|
|
53
|
+
- [`ComposableAgents::Mixins::Resumable`](#composableagentsmixinsresumable)
|
|
54
|
+
- [`ComposableAgents::Mixins::UserInteraction`](#composableagentsmixinsuserinteraction)
|
|
55
|
+
- [`ComposableAgents::Mixins::ArtifactContract`](#composableagentsmixinsartifactcontract)
|
|
56
|
+
- [Additional notes](#additional-notes)
|
|
57
|
+
- [Documentation](#documentation)
|
|
58
|
+
- [How it works](#how-it-works)
|
|
59
|
+
- [Architecture overview ๐๏ธ](#architecture-overview-)
|
|
60
|
+
- [Composition: the pipeline pattern ๐](#composition-the-pipeline-pattern-)
|
|
61
|
+
- [Prompt-driven execution flow ๐ง ](#prompt-driven-execution-flow-)
|
|
62
|
+
- [Prompt rendering strategies ๐](#prompt-rendering-strategies-)
|
|
63
|
+
- [LLM backends: ai-agents vs cline-rb ๐ฏ](#llm-backends-ai-agents-vs-cline-rb-)
|
|
64
|
+
- [Mixin system โ augment agents with capabilities ๐งฐ](#mixin-system--augment-agents-with-capabilities-)
|
|
65
|
+
- [Instruction system ๐](#instruction-system-)
|
|
66
|
+
- [Code loading โก](#code-loading-)
|
|
67
|
+
- [State persistence for resumable workflows ๐พ](#state-persistence-for-resumable-workflows-)
|
|
68
|
+
- [Development](#development)
|
|
69
|
+
- [Prerequisites](#prerequisites)
|
|
70
|
+
- [Clone the repository](#clone-the-repository)
|
|
71
|
+
- [Install dependencies](#install-dependencies)
|
|
72
|
+
- [Project structure (high-level)](#project-structure-high-level)
|
|
73
|
+
- [Run tests](#run-tests)
|
|
74
|
+
- [Test debugging](#test-debugging)
|
|
75
|
+
- [Code coverage](#code-coverage)
|
|
76
|
+
- [Code linting](#code-linting)
|
|
77
|
+
- [Generate documentation](#generate-documentation)
|
|
78
|
+
- [Package the gem](#package-the-gem)
|
|
79
|
+
- [Common development tasks](#common-development-tasks)
|
|
80
|
+
- [Adding a new feature](#adding-a-new-feature)
|
|
81
|
+
- [Adding a test helper](#adding-a-test-helper)
|
|
82
|
+
- [Running examples](#running-examples)
|
|
83
|
+
- [CI pipeline](#ci-pipeline)
|
|
84
|
+
- [Release process](#release-process)
|
|
85
|
+
- [Contributing](#contributing)
|
|
86
|
+
- [๐ Issues](#-issues)
|
|
87
|
+
- [๐ด Fork & Branch](#-fork--branch)
|
|
88
|
+
- [๐งช Setting up test dependencies & running tests](#-setting-up-test-dependencies--running-tests)
|
|
89
|
+
- [โ
Linting & code style](#-linting--code-style)
|
|
90
|
+
- [๐ CI / Build pipeline](#-ci--build-pipeline)
|
|
91
|
+
- [๐ Pull request guidelines](#-pull-request-guidelines)
|
|
92
|
+
- [๐ License](#-license)
|
|
93
|
+
- [License](#license)
|
|
94
|
+
|
|
95
|
+
## Quick start
|
|
96
|
+
|
|
97
|
+
### Installation
|
|
98
|
+
|
|
99
|
+
Add the gem to your application's Gemfile:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
bundle add composable_agents
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Or install it globally:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
gem install composable_agents
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Requires **Ruby >= 3.1**.
|
|
112
|
+
|
|
113
|
+
### Basic usage: create a composed pipeline of agents
|
|
114
|
+
|
|
115
|
+
Here's a minimal example that chains three agents together to build a holiday planner.
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
require 'composable_agents'
|
|
119
|
+
|
|
120
|
+
# --- 1. Define the agents ---
|
|
121
|
+
|
|
122
|
+
# An LLM-powered agent (uses ai-agents gem, needs an API key)
|
|
123
|
+
class ItineraryAgent < ComposableAgents::AiAgents::Agent
|
|
124
|
+
def initialize
|
|
125
|
+
super(
|
|
126
|
+
role: 'You are a travel planner',
|
|
127
|
+
objective: 'Find cities matching the user preferences',
|
|
128
|
+
system_instructions: <<~EO_INSTRUCTIONS,
|
|
129
|
+
Get the user preferences from the artifact named `preferences`.
|
|
130
|
+
Find the best cities.
|
|
131
|
+
Create an artifact named `cities` as a JSON list of city names.
|
|
132
|
+
EO_INSTRUCTIONS
|
|
133
|
+
model: 'openai/gpt-4o-mini' # or any model supported by your provider
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# A plain Ruby agent (no LLM needed)
|
|
139
|
+
class BudgetAgent < ComposableAgents::RubyAgent
|
|
140
|
+
def initialize
|
|
141
|
+
super(proc do |input_artifacts|
|
|
142
|
+
cities = JSON.parse(input_artifacts[:cities])
|
|
143
|
+
{ budget: cities.size * 1000 }
|
|
144
|
+
end)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# --- 2. Configure the LLM provider (for ai-agents backend) ---
|
|
149
|
+
require 'agents'
|
|
150
|
+
Agents.configure do |config|
|
|
151
|
+
config.openrouter_api_key = ENV.fetch('OPENROUTER_API_KEY', nil)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# --- 3. Compose and run them ---
|
|
155
|
+
preferences = { preferences: 'Cultural city trips in Europe' }
|
|
156
|
+
|
|
157
|
+
itinerary_outputs = ItineraryAgent.new.run(**preferences)
|
|
158
|
+
budget_outputs = BudgetAgent.new.run(**itinerary_outputs)
|
|
159
|
+
|
|
160
|
+
puts "Cities: #{itinerary_outputs[:cities]}"
|
|
161
|
+
puts "Budget: $#{budget_outputs[:budget]}"
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Using the Cline backend instead
|
|
165
|
+
|
|
166
|
+
If you prefer the `cline-rb` backend, set the `CLINE_API_KEY` environment variable:
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
# Use Cline-powered agents instead
|
|
170
|
+
itinerary_agent = ComposableAgents::Cline::Agent.new(
|
|
171
|
+
role: 'You are a travel planner',
|
|
172
|
+
objective: 'Find cities matching the user preferences',
|
|
173
|
+
model: 'anthropic/claude-sonnet-4.6',
|
|
174
|
+
api_key: ENV.fetch('CLINE_API_KEY', nil),
|
|
175
|
+
input_artifacts_contracts: { preferences: 'User travel preferences' },
|
|
176
|
+
output_artifacts_contracts: { cities: 'List of best cities' }
|
|
177
|
+
)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Next steps
|
|
181
|
+
|
|
182
|
+
- Browse the [examples/](https://github.com/Muriel-Salvan/composable_agents/tree/main/examples) directory for full working scripts.
|
|
183
|
+
- Use the `ArtifactContract` mixin to validate inputs/outputs.
|
|
184
|
+
- Use the `Resumable` mixin to persist and resume long-running workflows.
|
|
185
|
+
- Use the `AiAgentUserInteraction` mixin to let agents ask the user questions.
|
|
186
|
+
|
|
187
|
+
## Requirements
|
|
188
|
+
|
|
189
|
+
- **Ruby** >= 3.1 โ The gem requires Ruby 3.1 or newer.
|
|
190
|
+
- **Bundler** โ Used to install the gem and manage its dependencies (comes with Ruby).
|
|
191
|
+
- **Node.js** โ Required at runtime by the `cline-rb` backend for pseudo-terminal (PTY) support via `node-pty`.
|
|
192
|
+
- **An LLM provider API key** โ One of the following (depending on the agent backend you use):
|
|
193
|
+
- **OpenRouter API key** โ Set via the `OPENROUTER_API_KEY` environment variable when using the `AiAgents` backend.
|
|
194
|
+
- **Cline API key** โ Set via the `CLINE_API_KEY` environment variable when using the `Cline` backend.
|
|
195
|
+
|
|
196
|
+
## Features
|
|
197
|
+
|
|
198
|
+
**composable_agents** is a Ruby framework for building **modular, prompt-driven AI agent pipelines** ๐งฉ. Here are its key capabilities:
|
|
199
|
+
|
|
200
|
+
- ๐ง **Three agent types** โ Create LLM-powered agents via [`PromptDrivenAgent`](lib/composable_agents/prompt_driven_agent.rb), wrap plain Ruby logic with [`RubyAgent`](lib/composable_agents/ruby_agent.rb), or compose complex multi-step workflows using the [`Resumable`](lib/composable_agents/mixins/resumable.rb) mixin.
|
|
201
|
+
- ๐ **Composable pipelines** โ Pass output artifacts from one agent directly as input to another, forming reusable, chainable workflows.
|
|
202
|
+
- ๐ฏ **Multiple LLM backends** โ Plug into different AI providers via the [`ai-agents`](https://github.com/nicbarker/ai-agents) gem (OpenRouter) or [`cline-rb`](https://github.com/Muriel-Salvan/cline-rb) (Claude, GPT, and many more).
|
|
203
|
+
- ๐ **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
|
+
- ๐ฆ **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
|
+
- ๐พ **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.
|
|
206
|
+
- ๐ฃ๏ธ **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
|
+
- ๐ **Automatic conversation tracking** โ Every prompt and response is automatically recorded with timestamps in a structured `conversation` store, ready for debugging or replay.
|
|
208
|
+
- โ๏ธ **Flexible instruction system** โ Use raw text, structured ordered-lists, or a mix of both to define agent instructions, rendered consistently by the chosen strategy.
|
|
209
|
+
- ๐ ๏ธ **Cline skill support** โ Select and enable specific Cline skills per agent, with automatic dependency resolution.
|
|
210
|
+
- ๐ **State export/import** โ Agents can serialize and restore their internal state via `export_state`/`import_state`, enabling deep integration with the resumable workflow system.
|
|
211
|
+
- ๐ **Debug logging** โ Toggle verbose debug output with the `COMPOSABLE_AGENTS_DEBUG=1` environment variable.
|
|
212
|
+
- ๐ **Markdown header alignment** โ A built-in utility normalizes Markdown header levels across composed prompts, maintaining a clean document hierarchy.
|
|
213
|
+
|
|
214
|
+
## Public API
|
|
215
|
+
|
|
216
|
+
This section documents all public entry points of the **composable_agents** gem.
|
|
217
|
+
The project is a Ruby library (not a CLI) โ users install the gem and use its classes and mixins in their own Ruby code.
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
### Module constant
|
|
222
|
+
|
|
223
|
+
#### `ComposableAgents::VERSION`
|
|
224
|
+
|
|
225
|
+
- **Description:** The current version of the gem (`'0.1.0'`).
|
|
226
|
+
- **Full documentation:** [version.rb on GitHub](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/version.rb)
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
### Core agent classes
|
|
231
|
+
|
|
232
|
+
#### `ComposableAgents::Agent`
|
|
233
|
+
|
|
234
|
+
- **Description:** Abstract base class for all agents. An agent is a computational unit that transforms input artifacts into output artifacts. Agents are stateless by default.
|
|
235
|
+
- **Public methods:**
|
|
236
|
+
- `#initialize(name: nil, composable_agents_dir: '.composable_agents')` โ Create a new agent with an optional name and a working directory.
|
|
237
|
+
- `#name` โ Return the agent's name (`String`, or `nil`).
|
|
238
|
+
- `#full_name` โ Return a human-readable full name for logs and traces (can be overridden by subclasses).
|
|
239
|
+
- **Usage example:**
|
|
240
|
+
```ruby
|
|
241
|
+
class MyCustomAgent < ComposableAgents::Agent
|
|
242
|
+
def run(**input_artifacts)
|
|
243
|
+
# Process input_artifacts and return output artifacts
|
|
244
|
+
{ result: input_artifacts[:data].upcase }
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
agent = MyCustomAgent.new(name: 'uppercaser')
|
|
249
|
+
output = agent.run(data: 'hello')
|
|
250
|
+
puts output[:result] # => "HELLO"
|
|
251
|
+
```
|
|
252
|
+
- **Full documentation:** [Agent on RubyDoc](https://www.rubydoc.info/gems/composable_agents/ComposableAgents/Agent) | [agent.rb on GitHub](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/agent.rb)
|
|
253
|
+
|
|
254
|
+
#### `ComposableAgents::RubyAgent < Agent`
|
|
255
|
+
|
|
256
|
+
- **Description:** An agent that wraps arbitrary Ruby logic as a `Proc`. No LLM is needed โ ideal for deterministic or simple processing steps.
|
|
257
|
+
- **Public methods:**
|
|
258
|
+
- `#initialize(processor, *args, **kwargs)` โ The `processor` is a `#call`-able object (e.g. a `Proc`) that receives input artifacts and returns output artifacts.
|
|
259
|
+
- `#run(**input_artifacts)` โ Execute the proc with the given input artifacts.
|
|
260
|
+
- **Usage example:**
|
|
261
|
+
```ruby
|
|
262
|
+
# A simple agent that doubles a number
|
|
263
|
+
double_agent = ComposableAgents::RubyAgent.new(
|
|
264
|
+
proc { |inputs| { double: inputs[:number] * 2 } }
|
|
265
|
+
)
|
|
266
|
+
result = double_agent.run(number: 21)
|
|
267
|
+
puts result[:double] # => 42
|
|
268
|
+
```
|
|
269
|
+
- **Full documentation:** [RubyAgent on RubyDoc](https://www.rubydoc.info/gems/composable_agents/ComposableAgents/RubyAgent) | [ruby_agent.rb on GitHub](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/ruby_agent.rb)
|
|
270
|
+
|
|
271
|
+
#### `ComposableAgents::Instructions`
|
|
272
|
+
|
|
273
|
+
- **Description:** Normalizes instructions (system prompts, user prompts) into a canonical list format. Supports plain text strings and structured hashes (e.g. with `ordered_list` keys). Includes `Enumerable`.
|
|
274
|
+
- **Public methods:**
|
|
275
|
+
- `#initialize(instructions)` โ Accepts a `String`, an `Array`, or a `Hash{text:, ordered_list:}`.
|
|
276
|
+
- `#each(&)` โ Iterate over each instruction as `(type, content)` pairs.
|
|
277
|
+
- **Usage example:**
|
|
278
|
+
```ruby
|
|
279
|
+
instructions = ComposableAgents::Instructions.new({
|
|
280
|
+
ordered_list: ['Step one', 'Step two']
|
|
281
|
+
})
|
|
282
|
+
instructions.each do |type, content|
|
|
283
|
+
puts "#{type}: #{content}"
|
|
284
|
+
end
|
|
285
|
+
```
|
|
286
|
+
- **Full documentation:** [Instructions on RubyDoc](https://www.rubydoc.info/gems/composable_agents/ComposableAgents/Instructions) | [instructions.rb on GitHub](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/instructions.rb)
|
|
287
|
+
|
|
288
|
+
#### `ComposableAgents::PromptDrivenAgent < Agent`
|
|
289
|
+
|
|
290
|
+
- **Description:** An agent that uses a **prompt rendering strategy** (Markdown or MarkdownHeavy) to build prompts for an LLM. It manages role, objective, instructions, constraints, and a conversation history.
|
|
291
|
+
- **Public methods:**
|
|
292
|
+
- `#role` / `#role=` โ Agent's role description.
|
|
293
|
+
- `#objective` / `#objective=` โ Agent's objective.
|
|
294
|
+
- `#system_instructions` / `#system_instructions=` โ Instructions for the agent.
|
|
295
|
+
- `#constraints` / `#constraints=` โ Constraints the agent must respect.
|
|
296
|
+
- `#conversation` โ Read the conversation history (array of message hashes).
|
|
297
|
+
- `#initialize(*args, role:, objective:, system_instructions:, constraints:, strategy:, **kwargs)` โ The `strategy` defaults to `PromptRenderingStrategy::Markdown`.
|
|
298
|
+
- `#full_name` โ Human-readable name for logs.
|
|
299
|
+
- `#run(user_instructions: nil, **input_artifacts)` โ Execute the agent and produce output artifacts.
|
|
300
|
+
- **Usage example:**
|
|
301
|
+
```ruby
|
|
302
|
+
agent = ComposableAgents::PromptDrivenAgent.new(
|
|
303
|
+
role: 'A helpful assistant',
|
|
304
|
+
objective: 'Answer user questions'
|
|
305
|
+
)
|
|
306
|
+
# Subclass and implement #prompt(user_prompt) to provide the LLM backend.
|
|
307
|
+
```
|
|
308
|
+
- **Full documentation:** [PromptDrivenAgent on RubyDoc](https://www.rubydoc.info/gems/composable_agents/ComposableAgents/PromptDrivenAgent) | [prompt_driven_agent.rb on GitHub](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/prompt_driven_agent.rb)
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
### LLM backend agent classes
|
|
313
|
+
|
|
314
|
+
#### `ComposableAgents::AiAgents::Agent < PromptDrivenAgent`
|
|
315
|
+
|
|
316
|
+
- **Description:** An agent that uses the [`ai-agents`](https://github.com/nicbarker/ai-agents) gem as its LLM backend. Requires an OpenRouter API key configured via `Agents.configure`.
|
|
317
|
+
- **Public methods:**
|
|
318
|
+
- `#initialize(*args, model:, params:, handoff_agents:, **kwargs)` โ Specify the `model` (e.g. `'openai/gpt-4o-mini'`), optional `params` for model configuration, and a list of `handoff_agents`.
|
|
319
|
+
- `#full_name` โ Returns `"<name> (AiAgent <model>)"`.
|
|
320
|
+
- **Usage example:**
|
|
321
|
+
```ruby
|
|
322
|
+
require 'agents'
|
|
323
|
+
Agents.configure { |c| c.openrouter_api_key = ENV['OPENROUTER_API_KEY'] }
|
|
324
|
+
|
|
325
|
+
agent = ComposableAgents::AiAgents::Agent.new(
|
|
326
|
+
role: 'Travel planner',
|
|
327
|
+
objective: 'Suggest destinations',
|
|
328
|
+
system_instructions: 'Create an artifact named `cities` with city names.',
|
|
329
|
+
model: 'openai/gpt-4o-mini'
|
|
330
|
+
)
|
|
331
|
+
result = agent.run(preferences: 'beach holidays')
|
|
332
|
+
puts result[:cities]
|
|
333
|
+
```
|
|
334
|
+
- **Full documentation:** [AiAgents::Agent on RubyDoc](https://www.rubydoc.info/gems/composable_agents/ComposableAgents/AiAgents/Agent) | [ai_agents/agent.rb on GitHub](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/ai_agents/agent.rb)
|
|
335
|
+
|
|
336
|
+
#### `ComposableAgents::Cline::Agent < PromptDrivenAgent`
|
|
337
|
+
|
|
338
|
+
- **Description:** An agent that uses the [`cline-rb`](https://github.com/Muriel-Salvan/cline-rb) gem as its LLM backend. Requires a `CLINE_API_KEY` environment variable. Automatically prepends the `ArtifactContract` mixin.
|
|
339
|
+
- **Public methods:**
|
|
340
|
+
- `#initialize(*args, strategy:, provider:, model:, api_key:, configure_provider:, configure_global:, skills:, cli_options:, **kwargs)` โ Configure provider, model, optional skill list, and CLI options. Defaults to `'cline'` provider, `'anthropic/claude-sonnet-4.6'` model.
|
|
341
|
+
- `#full_name` โ Returns `"<name> (Cline <provider>/<model>)"`.
|
|
342
|
+
- **Usage example:**
|
|
343
|
+
```ruby
|
|
344
|
+
agent = ComposableAgents::Cline::Agent.new(
|
|
345
|
+
role: 'Travel planner',
|
|
346
|
+
objective: 'Suggest destinations',
|
|
347
|
+
model: 'deepseek/deepseek-v4-flash',
|
|
348
|
+
api_key: ENV['CLINE_API_KEY'],
|
|
349
|
+
input_artifacts_contracts: { preferences: 'User preferences' },
|
|
350
|
+
output_artifacts_contracts: { cities: 'City list' }
|
|
351
|
+
)
|
|
352
|
+
result = agent.run(preferences: 'cultural trips in Italy')
|
|
353
|
+
puts result[:cities]
|
|
354
|
+
```
|
|
355
|
+
- **Full documentation:** [Cline::Agent on RubyDoc](https://www.rubydoc.info/gems/composable_agents/ComposableAgents/Cline/Agent) | [cline/agent.rb on GitHub](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/cline/agent.rb)
|
|
356
|
+
|
|
357
|
+
#### `ComposableAgents::Cline::MissingSkillError < RuntimeError`
|
|
358
|
+
|
|
359
|
+
- **Description:** Raised by `Cline::Agent` when a referenced skill is not found in the global or project Cline configuration.
|
|
360
|
+
- **Full documentation:** [cline/agent.rb on GitHub](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/cline/agent.rb)
|
|
361
|
+
|
|
362
|
+
---
|
|
363
|
+
|
|
364
|
+
### Mixins
|
|
365
|
+
|
|
366
|
+
Mixins are `prepend`-ed into an agent class to add specific capabilities.
|
|
367
|
+
|
|
368
|
+
#### `ComposableAgents::Mixins::Logger`
|
|
369
|
+
|
|
370
|
+
- **Description:** Provides debug and info logging to agents. Debug mode is enabled by setting `COMPOSABLE_AGENTS_DEBUG=1` in the environment.
|
|
371
|
+
- **Public methods (class-level):**
|
|
372
|
+
- `self.debug?` โ Returns `true` if debug mode is enabled (`ENV['COMPOSABLE_AGENTS_DEBUG'] == '1'`).
|
|
373
|
+
- **Usage example:**
|
|
374
|
+
```ruby
|
|
375
|
+
# Enable debug logs
|
|
376
|
+
ENV['COMPOSABLE_AGENTS_DEBUG'] = '1'
|
|
377
|
+
puts ComposableAgents::Mixins::Logger.debug? # => true
|
|
378
|
+
```
|
|
379
|
+
- **Full documentation:** [Mixins::Logger on RubyDoc](https://www.rubydoc.info/gems/composable_agents/ComposableAgents/Mixins/Logger) | [logger.rb on GitHub](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/mixins/logger.rb)
|
|
380
|
+
|
|
381
|
+
#### `ComposableAgents::Mixins::Resumable`
|
|
382
|
+
|
|
383
|
+
- **Description:** Adds step-level persistence and resumption capabilities to agents. Steps and their artifacts are serialized to JSON on disk so that long-running workflows can be interrupted and resumed.
|
|
384
|
+
- **Public methods:**
|
|
385
|
+
- `#initialize(*args, run_id:, **kwargs)` โ The `run_id` identifies the persisted run.
|
|
386
|
+
- **Usage example:**
|
|
387
|
+
```ruby
|
|
388
|
+
class WorkflowAgent < ComposableAgents::Agent
|
|
389
|
+
prepend ComposableAgents::Mixins::Resumable
|
|
390
|
+
|
|
391
|
+
def run(**inputs)
|
|
392
|
+
@artifacts = inputs
|
|
393
|
+
step(:process_data) { @artifacts[:result] = @artifacts[:data].upcase }
|
|
394
|
+
step(:finalize) { @artifacts[:done] = true }
|
|
395
|
+
@artifacts
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# If interrupted between steps, re-running with the same run_id skips completed steps
|
|
400
|
+
WorkflowAgent.new(run_id: 'my_workflow').run(data: 'hello')
|
|
401
|
+
```
|
|
402
|
+
- **Full documentation:** [Mixins::Resumable on RubyDoc](https://www.rubydoc.info/gems/composable_agents/ComposableAgents/Mixins/Resumable) | [resumable.rb on GitHub](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/mixins/resumable.rb)
|
|
403
|
+
|
|
404
|
+
#### `ComposableAgents::Mixins::UserInteraction`
|
|
405
|
+
|
|
406
|
+
- **Description:** Adds a simple question-and-answer interface for agents. By default, questions are printed to the terminal and answers are read from `$stdin`. Override `#answer_to` for custom behavior.
|
|
407
|
+
- **Public methods:**
|
|
408
|
+
- `#ask(question)` โ Ask the user a question and return the answer.
|
|
409
|
+
- **Usage example:**
|
|
410
|
+
```ruby
|
|
411
|
+
class InteractiveAgent < ComposableAgents::Agent
|
|
412
|
+
include ComposableAgents::Mixins::UserInteraction
|
|
413
|
+
|
|
414
|
+
def run(**)
|
|
415
|
+
name = ask('What is your name?')
|
|
416
|
+
{ greeting: "Hello, #{name}!" }
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
```
|
|
420
|
+
- **Full documentation:** [Mixins::UserInteraction on RubyDoc](https://www.rubydoc.info/gems/composable_agents/ComposableAgents/Mixins/UserInteraction) | [user_interaction.rb on GitHub](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/mixins/user_interaction.rb)
|
|
421
|
+
|
|
422
|
+
#### `ComposableAgents::Mixins::ArtifactContract`
|
|
423
|
+
|
|
424
|
+
- **Description:** Validates input and output artifacts against declared contracts before and after running an agent. Contracts specify description, optionality, and expected type (`:text`, `:markdown`, `:json`).
|
|
425
|
+
- **Public error classes:**
|
|
426
|
+
- `MissingInputArtifactError < RuntimeError` โ Raised when required input artifacts are missing.
|
|
427
|
+
- `MissingOutputArtifactError < RuntimeError` โ Raised when expected output artifacts are missing after execution.
|
|
428
|
+
- `ArtifactTypeError < RuntimeError` โ Raised when an artifact's content does not match its declared type.
|
|
429
|
+
- **Public methods:**
|
|
430
|
+
- `#initialize(*args, input_artifacts_contracts:, output_artifacts_contracts:, **kwargs)` โ Contracts are `Hash{Symbol => String}` (simple description) or `Hash{Symbol => Hash{description:, optional:, type:}}`.
|
|
431
|
+
- **Usage example:**
|
|
432
|
+
```ruby
|
|
433
|
+
class ValidatedAgent < ComposableAgents::Agent
|
|
434
|
+
prepend ComposableAgents::Mixins::ArtifactContract
|
|
435
|
+
|
|
436
|
+
def input_artifacts_contracts
|
|
437
|
+
{ name: { description: 'User name', type: :text } }
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
def output_artifacts_contracts
|
|
441
|
+
{ greeting: { description: 'Greeting message', type: :text } }
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def run(**inputs)
|
|
445
|
+
{ greeting: "Hello, #{inputs[:name]}!" }
|
|
446
|
+
end
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
agent = ValidatedAgent.new
|
|
450
|
+
agent.run(name: 'World') # => { greeting: "Hello, World!" }
|
|
451
|
+
agent.run(foo: 'bar') # Raises MissingInputArtifactError
|
|
452
|
+
```
|
|
453
|
+
- **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)
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
### Additional notes
|
|
458
|
+
|
|
459
|
+
- **No executables / CLI** โ This gem is a library only; there are no scripts in `bin/`.
|
|
460
|
+
- **Prompt rendering strategies** (`PromptRenderingStrategy::Markdown` and `PromptRenderingStrategy::MarkdownHeavy`) are not part of the public API themselves โ they are included automatically by the agent's `strategy:` parameter.
|
|
461
|
+
- **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.
|
|
462
|
+
- See the [examples/](https://github.com/Muriel-Salvan/composable_agents/tree/main/examples) directory for complete, runnable scripts demonstrating all the above APIs.
|
|
463
|
+
|
|
464
|
+
## Documentation
|
|
465
|
+
|
|
466
|
+
- **๐ Main README** โ [README.md](https://github.com/Muriel-Salvan/composable_agents#readme) โ Overview, installation, usage, and development instructions.
|
|
467
|
+
- **๐ RubyDoc.info (API reference)** โ [composable_agents on RubyDoc](https://www.rubydoc.info/gems/composable_agents) โ Auto-generated YARD documentation for all public classes, modules, and methods. Covers the full API with 100% documented coverage.
|
|
468
|
+
- **๐ GitHub Repository** โ [github.com/Muriel-Salvan/composable_agents](https://github.com/Muriel-Salvan/composable_agents) โ Source code, issue tracker, and pull requests.
|
|
469
|
+
- **๐ License** โ [BSD-3-Clause License](https://github.com/Muriel-Salvan/composable_agents/blob/main/LICENSE) โ The gem is available as open source under the BSD 3-Clause License.
|
|
470
|
+
- **๐ก Examples** โ [`examples/` directory](https://github.com/Muriel-Salvan/composable_agents/tree/main/examples) โ Runnable Ruby scripts demonstrating simple pipelines, resumable workflows, and user interaction patterns.
|
|
471
|
+
- **โ๏ธ CI / Build** โ [Continuous Integration workflow](https://github.com/Muriel-Salvan/composable_agents/blob/main/.github/workflows/continuous_integration.yml) โ GitHub Actions configuration for running tests and publishing releases.
|
|
472
|
+
- **๐ Source Code** โ Browse the [`lib/` directory](https://github.com/Muriel-Salvan/composable_agents/tree/main/lib/composable_agents) for inline YARD-annotated source documentation of all agents, mixins, and rendering strategies:
|
|
473
|
+
- [`Agent`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/agent.rb) โ Abstract base class
|
|
474
|
+
- [`PromptDrivenAgent`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/prompt_driven_agent.rb) โ LLM-prompted agent
|
|
475
|
+
- [`RubyAgent`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/ruby_agent.rb) โ Plain Ruby logic agent
|
|
476
|
+
- [`AiAgents::Agent`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/ai_agents/agent.rb) โ ai-agents backend
|
|
477
|
+
- [`Cline::Agent`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/cline/agent.rb) โ cline-rb backend
|
|
478
|
+
- [`Instructions`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/instructions.rb) โ Instruction system
|
|
479
|
+
- [`Mixins::Resumable`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/mixins/resumable.rb) โ Resumable workflow mixin
|
|
480
|
+
- [`Mixins::ArtifactContract`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/mixins/artifact_contract.rb) โ Artifact validation mixin
|
|
481
|
+
- [`Mixins::UserInteraction`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/mixins/user_interaction.rb) โ User question-asking mixin
|
|
482
|
+
- [`Mixins::Logger`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/mixins/logger.rb) โ Debug logging mixin
|
|
483
|
+
- [`PromptRenderingStrategy::Markdown`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/prompt_rendering_strategy/markdown.rb) โ Markdown prompt strategy
|
|
484
|
+
- [`PromptRenderingStrategy::MarkdownHeavy`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/prompt_rendering_strategy/markdown_heavy.rb) โ Heavy Markdown prompt strategy
|
|
485
|
+
- [`Utils::Markdown`](https://github.com/Muriel-Salvan/composable_agents/blob/main/lib/composable_agents/utils/markdown.rb) โ Markdown header alignment utilities
|
|
486
|
+
|
|
487
|
+
## How it works
|
|
488
|
+
|
|
489
|
+
**composable_agents** is built on a simple principle: every agent is a **stateless function** that takes input artifacts (a `Hash{Symbol => Object}`) and returns output artifacts. Chain them โ one agent's outputs become the next agent's inputs.
|
|
490
|
+
|
|
491
|
+
### Architecture overview ๐๏ธ
|
|
492
|
+
|
|
493
|
+
```mermaid
|
|
494
|
+
classDiagram
|
|
495
|
+
class Agent {
|
|
496
|
+
+run(**input_artifacts)~Hash~
|
|
497
|
+
+full_name()~String~
|
|
498
|
+
}
|
|
499
|
+
class PromptDrivenAgent {
|
|
500
|
+
+String role
|
|
501
|
+
+String objective
|
|
502
|
+
+String system_instructions
|
|
503
|
+
+String constraints
|
|
504
|
+
+Array conversation
|
|
505
|
+
#prompt(user_prompt)~String~
|
|
506
|
+
}
|
|
507
|
+
class RubyAgent {
|
|
508
|
+
-Proc processor
|
|
509
|
+
+run(**input_artifacts)~Hash~
|
|
510
|
+
}
|
|
511
|
+
class AiAgents_Agent {
|
|
512
|
+
-AgentRunner agent_runner
|
|
513
|
+
#prompt(user_prompt)~String~
|
|
514
|
+
}
|
|
515
|
+
class Cline_Agent {
|
|
516
|
+
-Cline::Config cline_config
|
|
517
|
+
#prompt(user_prompt)~String~
|
|
518
|
+
}
|
|
519
|
+
Agent <|-- PromptDrivenAgent : extends
|
|
520
|
+
Agent <|-- RubyAgent : extends
|
|
521
|
+
PromptDrivenAgent <|-- AiAgents_Agent : extends
|
|
522
|
+
PromptDrivenAgent <|-- Cline_Agent : extends
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
The framework provides a clean **4-class hierarchy**:
|
|
526
|
+
|
|
527
|
+
- **`Agent`** โ Abstract base class. Defines the `run(**input_artifacts)` contract and includes the [`Mixins::Logger`](lib/composable_agents/mixins/logger.rb) for debug/info logging.
|
|
528
|
+
- **`RubyAgent`** โ Wraps any Ruby `Proc` as an agent. No LLM involved: call `proc.call(input_artifacts)` and return a hash. Ideal for deterministic logic.
|
|
529
|
+
- **`PromptDrivenAgent`** โ Base for LLM-powered agents. Holds a **role**, **objective**, **system_instructions**, and **constraints**. Renders them via a pluggable **prompt rendering strategy** and records every prompt/response in a `conversation` array.
|
|
530
|
+
- **`AiAgents::Agent` / `Cline::Agent`** โ Concrete LLM backends: one wraps the [`ai-agents`](https://github.com/nicbarker/ai-agents) gem, the other wraps [`cline-rb`](https://github.com/Muriel-Salvan/cline-rb). Both implement `#prompt(user_prompt)` to send the rendered prompt to the LLM.
|
|
531
|
+
|
|
532
|
+
### Composition: the pipeline pattern ๐
|
|
4
533
|
|
|
5
|
-
|
|
534
|
+
Agents communicate exclusively through **artifacts** โ named key/value pairs in a Ruby Hash:
|
|
6
535
|
|
|
7
|
-
|
|
536
|
+
```ruby
|
|
537
|
+
outputs = agent.run(**inputs)
|
|
538
|
+
# outputs[:city] can feed into next_agent.run(city: outputs[:city])
|
|
539
|
+
```
|
|
8
540
|
|
|
9
|
-
|
|
541
|
+
> ๐ก Agents are **stateless by design** โ no hidden mutable state. This makes them easy to test, debug, and reorder in pipelines.
|
|
10
542
|
|
|
11
|
-
|
|
543
|
+
### Prompt-driven execution flow ๐ง
|
|
12
544
|
|
|
13
|
-
|
|
545
|
+
Here's what happens when you call `run(**inputs)` on a `PromptDrivenAgent`:
|
|
14
546
|
|
|
15
|
-
|
|
547
|
+
```mermaid
|
|
548
|
+
flowchart TD
|
|
549
|
+
A[run] --> B[Render system prompt]
|
|
550
|
+
B --> C[Call #prompt with system prompt]
|
|
551
|
+
C --> D{Missing output\nartifacts?}
|
|
552
|
+
D -->|Yes| E[Render retry prompt]
|
|
553
|
+
E --> F[Call #prompt again]
|
|
554
|
+
F --> D
|
|
555
|
+
D -->|No| G[Return output artifacts]
|
|
556
|
+
```
|
|
16
557
|
|
|
17
|
-
|
|
558
|
+
1. **`render_system_prompt`** โ Assembles role, objective, instructions, and constraints into a structured Markdown document (via the chosen strategy).
|
|
559
|
+
2. **`#prompt(user_prompt)`** โ Sends the rendered prompt to the LLM backend. The backend (ai-agents or cline-rb) manages tool calls, context, and the LLM conversation.
|
|
560
|
+
3. **Retry loop** โ If expected output artifacts are missing, a retry prompt is generated and sent again.
|
|
561
|
+
4. Returns the collected `{ artifact_name => content }` hash.
|
|
562
|
+
|
|
563
|
+
### Prompt rendering strategies ๐
|
|
564
|
+
|
|
565
|
+
Two strategies are included, mixed into the agent at initialization via `singleton_class.include strategy`:
|
|
566
|
+
|
|
567
|
+
- **`PromptRenderingStrategy::Markdown`** โ Clean, minimal Markdown. Simple instructions and artifact references.
|
|
568
|
+
- **`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`).
|
|
569
|
+
|
|
570
|
+
### LLM backends: ai-agents vs cline-rb ๐ฏ
|
|
571
|
+
|
|
572
|
+
| Feature | [`AiAgents::Agent`](lib/composable_agents/ai_agents/agent.rb) | [`Cline::Agent`](lib/composable_agents/cline/agent.rb) |
|
|
573
|
+
|---|---|---|
|
|
574
|
+
| Underlying gem | [ai-agents](https://github.com/nicbarker/ai-agents) | [cline-rb](https://github.com/Muriel-Salvan/cline-rb) |
|
|
575
|
+
| Tools | Exposes `CreateArtifactTool`, `GetArtifactTool` to the LLM | Uses Cline's skill system |
|
|
576
|
+
| State persistence | Marshal + Base64 via `export_state`/`import_state` | Direct JSON serialization of context array |
|
|
577
|
+
| Default rendering | `Markdown` | `MarkdownHeavy` (with structured output parsing) |
|
|
578
|
+
| User interaction | Optional `AskUserTool` via [`AiAgentUserInteraction`](lib/composable_agents/mixins/ai_agent_user_interaction.rb) | N/A (uses Cline's own interaction) |
|
|
579
|
+
|
|
580
|
+
### Mixin system โ augment agents with capabilities ๐งฐ
|
|
581
|
+
|
|
582
|
+
Mixins are **prepended** (using `prepend`) or **included** (using `include`) to override `#run` or add new methods:
|
|
583
|
+
|
|
584
|
+
- **[`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.
|
|
585
|
+
- **[`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`.
|
|
586
|
+
- **[`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.
|
|
587
|
+
- **[`Mixins::Logger`](lib/composable_agents/mixins/logger.rb)** โ Provides `log_debug`/`log_info` methods. Debug output is toggled via the `COMPOSABLE_AGENTS_DEBUG=1` environment variable.
|
|
588
|
+
|
|
589
|
+
### Instruction system ๐
|
|
590
|
+
|
|
591
|
+
The [`Instructions`](lib/composable_agents/instructions.rb) class normalizes instructions into a standard list format. Each instruction can be:
|
|
592
|
+
- **`{ text: "..." }`** โ Free-form text
|
|
593
|
+
- **`{ ordered_list: ["Step 1", "Step 2"] }`** โ Sequential steps
|
|
594
|
+
|
|
595
|
+
The rendering strategy then renders each type appropriately (`#render_instruction_text`, `#render_instruction_ordered_list`).
|
|
596
|
+
|
|
597
|
+
### Code loading โก
|
|
598
|
+
|
|
599
|
+
Uses [`zeitwerk`](https://github.com/fxn/zeitwerk) for automatic, thread-safe code autoloading โ no manual `require` calls needed beyond the top-level entry point.
|
|
600
|
+
|
|
601
|
+
### State persistence for resumable workflows ๐พ
|
|
602
|
+
|
|
603
|
+
```mermaid
|
|
604
|
+
flowchart LR
|
|
605
|
+
A[Step: fetch_data] --> B{Step JSON\nexists?}
|
|
606
|
+
B -->|Yes| C[Deserialize state\nSkip execution]
|
|
607
|
+
B -->|No| D[Execute block]
|
|
608
|
+
D --> E[Serialize artifacts\n+ agent state to JSON]
|
|
609
|
+
C --> F[Continue with\nrestored artifacts]
|
|
610
|
+
E --> F
|
|
611
|
+
```
|
|
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.
|
|
18
614
|
|
|
19
615
|
## Development
|
|
20
616
|
|
|
21
|
-
|
|
617
|
+
### Prerequisites
|
|
618
|
+
|
|
619
|
+
- **Ruby** >= 3.1
|
|
620
|
+
- **Bundler** (comes with Ruby)
|
|
621
|
+
- **Node.js** โ required by the `cline-rb` backend for pseudo-terminal support (`node-pty`)
|
|
622
|
+
|
|
623
|
+
### Clone the repository
|
|
624
|
+
|
|
625
|
+
```bash
|
|
626
|
+
git clone https://github.com/Muriel-Salvan/composable_agents.git
|
|
627
|
+
cd composable_agents
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### Install dependencies
|
|
631
|
+
|
|
632
|
+
```bash
|
|
633
|
+
bundle install
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
Additionally, install the Node.js pseudo-terminal dependency that the `cline-rb` backend expects:
|
|
637
|
+
|
|
638
|
+
```bash
|
|
639
|
+
npm install node-pty
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
### Project structure (high-level)
|
|
643
|
+
|
|
644
|
+
```
|
|
645
|
+
.
|
|
646
|
+
โโโ lib/ # Source code (autoloaded via Zeitwerk)
|
|
647
|
+
โ โโโ composable_agents/ # Main library modules
|
|
648
|
+
โ โโโ agent.rb # Abstract base agent
|
|
649
|
+
โ โโโ prompt_driven_agent.rb # LLM-prompted agent
|
|
650
|
+
โ โโโ ruby_agent.rb # Plain Ruby logic agent
|
|
651
|
+
โ โโโ instructions.rb # Instruction system
|
|
652
|
+
โ โโโ ai_agents/ # ai-agents backend
|
|
653
|
+
โ โโโ cline/ # cline-rb backend
|
|
654
|
+
โ โโโ mixins/ # Resumable, ArtifactContract, UserInteraction, Logger
|
|
655
|
+
โ โโโ prompt_rendering_strategy/ # Markdown & MarkdownHeavy strategies
|
|
656
|
+
โ โโโ utils/ # Markdown utilities
|
|
657
|
+
โโโ spec/ # Test suite
|
|
658
|
+
โ โโโ scenarios/ # RSpec test cases
|
|
659
|
+
โ โโโ composable_agents_test/ # Test helpers, spies, stubs
|
|
660
|
+
โโโ examples/ # Runnable usage examples
|
|
661
|
+
โโโ Gemfile # Dependencies
|
|
662
|
+
โโโ composable_agents.gemspec # Gem specification
|
|
663
|
+
โโโ .rubocop.yml # RuboCop configuration
|
|
664
|
+
โโโ .github/workflows/ # CI pipeline
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
### Run tests
|
|
668
|
+
|
|
669
|
+
Run the full test suite with RSpec:
|
|
670
|
+
|
|
671
|
+
```bash
|
|
672
|
+
bundle exec rspec
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
Run a specific test file:
|
|
676
|
+
|
|
677
|
+
```bash
|
|
678
|
+
bundle exec rspec spec/scenarios/composable_agents/cline/agent_spec.rb
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
Run tests with verbose documentation output:
|
|
682
|
+
|
|
683
|
+
```bash
|
|
684
|
+
bundle exec rspec --format documentation
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
#### Test debugging
|
|
688
|
+
|
|
689
|
+
Set the `TEST_DEBUG=1` environment variable to enable verbose debug output during test execution:
|
|
690
|
+
|
|
691
|
+
```bash
|
|
692
|
+
TEST_DEBUG=1 bundle exec rspec
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
#### Code coverage
|
|
696
|
+
|
|
697
|
+
The test suite enforces **99% minimum code coverage** via SimpleCov. Coverage reports are generated in Cobertura format and automatically uploaded to Codecov in CI.
|
|
698
|
+
|
|
699
|
+
### Code linting
|
|
700
|
+
|
|
701
|
+
This project uses **RuboCop** with the `rubocop-rspec` and `rubocop-yard` plugins. Run the linter:
|
|
702
|
+
|
|
703
|
+
```bash
|
|
704
|
+
bundle exec rubocop
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
To auto-correct fixable offenses:
|
|
708
|
+
|
|
709
|
+
```bash
|
|
710
|
+
bundle exec rubocop -a
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
Linting is also verified as part of the test suite via the `Code Quality` spec (`spec/scenarios/code_quality_spec.rb`), which runs `rubocop` and asserts that no offenses are detected.
|
|
714
|
+
|
|
715
|
+
### Generate documentation
|
|
716
|
+
|
|
717
|
+
API documentation is generated with **YARD**. The project enforces **100% documented code**:
|
|
718
|
+
|
|
719
|
+
```bash
|
|
720
|
+
bundle exec yard doc --fail-on-warning
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
Check documentation coverage stats:
|
|
724
|
+
|
|
725
|
+
```bash
|
|
726
|
+
bundle exec yard stats --list-undoc --fail-on-warning
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
Documentation generation and coverage are also verified as part of the test suite via the `Documentation generation` spec.
|
|
730
|
+
|
|
731
|
+
### Package the gem
|
|
732
|
+
|
|
733
|
+
Build the gem locally:
|
|
734
|
+
|
|
735
|
+
```bash
|
|
736
|
+
gem build composable_agents.gemspec
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
This produces a `.gem` file (e.g., `composable_agents-0.1.0.gem`) in the current directory. The packaging process is also verified by the `Gem packaging` spec.
|
|
740
|
+
|
|
741
|
+
### Common development tasks
|
|
742
|
+
|
|
743
|
+
#### Adding a new feature
|
|
744
|
+
|
|
745
|
+
1. Write the feature code under `lib/composable_agents/` โ files are autoloaded by **Zeitwerk**, so name them according to the module/class namespace (e.g., `lib/composable_agents/my_feature.rb` for `ComposableAgents::MyFeature`).
|
|
746
|
+
2. Add RSpec tests under `spec/scenarios/composable_agents/` following the existing patterns.
|
|
747
|
+
3. Document all public methods with YARD annotations.
|
|
748
|
+
4. Run the full test suite and linting before committing:
|
|
749
|
+
|
|
750
|
+
```bash
|
|
751
|
+
bundle exec rspec && bundle exec rubocop
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
#### Adding a test helper
|
|
755
|
+
|
|
756
|
+
Place reusable test helpers, spies, or stubs under `spec/composable_agents_test/`. They are autoloaded via Zeitwerk under the `ComposableAgentsTest` namespace and included automatically via the spec helper.
|
|
757
|
+
|
|
758
|
+
#### Running examples
|
|
759
|
+
|
|
760
|
+
Example scripts are located in the `examples/` directory. Run any example directly with Ruby:
|
|
761
|
+
|
|
762
|
+
```bash
|
|
763
|
+
bundle exec ruby examples/compose_without_ai.rb
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
Some examples require an LLM provider API key. Set the appropriate environment variable before running:
|
|
767
|
+
|
|
768
|
+
```bash
|
|
769
|
+
OPENROUTER_API_KEY=your_key bundle exec ruby examples/compose_with_ai.rb
|
|
770
|
+
# or
|
|
771
|
+
CLINE_API_KEY=your_key bundle exec ruby examples/compose_with_cline.rb
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
#### CI pipeline
|
|
775
|
+
|
|
776
|
+
The project uses **GitHub Actions** (defined in `.github/workflows/continuous_integration.yml`):
|
|
777
|
+
|
|
778
|
+
- **`test` job** โ Runs the full RSpec suite on push (Ruby 3.4, with Bundler cache and `node-pty` installed). Coverage is uploaded to Codecov.
|
|
779
|
+
- **`package` job** โ Runs after tests pass, using **semantic-release** to publish the gem to RubyGems.org when a new version is tagged.
|
|
780
|
+
|
|
781
|
+
#### Release process
|
|
782
|
+
|
|
783
|
+
Releases are automated via **semantic-release**. Pushing a tag with a commit message following conventional commits format triggers the CI `package` job, which:
|
|
784
|
+
|
|
785
|
+
1. Builds the gem
|
|
786
|
+
2. Publishes it to RubyGems.org
|
|
787
|
+
3. Creates a GitHub release with changelog
|
|
788
|
+
|
|
789
|
+
Manual gem publishing can also be done with:
|
|
790
|
+
|
|
791
|
+
```bash
|
|
792
|
+
GEM_HOST_API_KEY=your_key gem push composable_agents-*.gem
|
|
793
|
+
```
|
|
22
794
|
|
|
23
795
|
## Contributing
|
|
24
796
|
|
|
25
|
-
Bug reports and pull requests are
|
|
797
|
+
Bug reports, feature suggestions, and pull requests are warmly welcomed on GitHub at [github.com/Muriel-Salvan/composable_agents](https://github.com/Muriel-Salvan/composable_agents). Please follow the guidelines below to keep things running smoothly.
|
|
798
|
+
|
|
799
|
+
### ๐ Issues
|
|
800
|
+
|
|
801
|
+
- **Before opening an issue**, search the [existing tracker](https://github.com/Muriel-Salvan/composable_agents/issues) to avoid duplicates.
|
|
802
|
+
- For a **bug report**, include:
|
|
803
|
+
- Ruby version (`ruby -v`)
|
|
804
|
+
- gem version
|
|
805
|
+
- a minimal code snippet that reproduces the problem
|
|
806
|
+
- the full error output or unexpected behaviour
|
|
807
|
+
- For a **feature request**, describe what you'd like to do and why, and if possible sketch how it could fit into the existing agent/mixin architecture.
|
|
808
|
+
|
|
809
|
+
### ๐ด Fork & Branch
|
|
810
|
+
|
|
811
|
+
1. Fork the repository on GitHub.
|
|
812
|
+
2. Clone your fork locally:
|
|
813
|
+
```bash
|
|
814
|
+
git clone https://github.com/<your-username>/composable_agents.git
|
|
815
|
+
cd composable_agents
|
|
816
|
+
```
|
|
817
|
+
3. Create a feature branch from `main`:
|
|
818
|
+
```bash
|
|
819
|
+
git checkout -b feat/my-awesome-feature
|
|
820
|
+
```
|
|
821
|
+
We follow a [semantic-release](https://semantic-release.gitbook.io/) workflow, so branch names like `feat/โฆ`, `fix/โฆ`, `chore/โฆ` help the CI determine the next version bump.
|
|
822
|
+
|
|
823
|
+
### ๐งช Setting up test dependencies & running tests
|
|
824
|
+
|
|
825
|
+
After checking out the repo, install all dependencies with `bundle install`. Then run the full test suite with `bundle exec rspec` (add `--format documentation` for verbose output). To run a single spec file, point to it directly, e.g. `bundle exec rspec spec/composable_agents_test/prompt_driven_agent_spies.rb`; to run a specific example, append `:line_number`, e.g. `bundle exec rspec spec/composable_agents_test/agent_spec.rb:42`. The CI (GitHub Actions, see [`.github/workflows/continuous_integration.yml`](https://github.com/Muriel-Salvan/composable_agents/blob/main/.github/workflows/continuous_integration.yml)) runs `bundle exec rspec --format documentation` on Ruby 3.4 โ your changes must pass all tests and should not drop code coverage below **99 %** (enforced via [SimpleCov](https://github.com/simplecov-ruby/simplecov)).
|
|
826
|
+
|
|
827
|
+
### โ
Linting & code style
|
|
828
|
+
|
|
829
|
+
The project uses [RuboCop](https://github.com/rubocop/rubocop) with the `rubocop-rspec` plugin. Run the linter before pushing:
|
|
830
|
+
```bash
|
|
831
|
+
bundle exec rubocop
|
|
832
|
+
```
|
|
833
|
+
Configuration lives in [`.rubocop.yml`](https://github.com/Muriel-Salvan/composable_agents/blob/main/.rubocop.yml). Key allowances (long methods, nested RSpec groups, etc.) are already tuned to the codebase โ please keep them as they are.
|
|
834
|
+
|
|
835
|
+
### ๐ CI / Build pipeline
|
|
836
|
+
|
|
837
|
+
| Job | When | What it does |
|
|
838
|
+
|-----|------|-------------|
|
|
839
|
+
| **test** | Every push | Installs Ruby 3.4 + Node (for `node-pty`), runs `bundle exec rspec`, uploads coverage to [Codecov](https://codecov.io/) |
|
|
840
|
+
| **package** | After tests pass | Runs `semantic-release` to auto-publish the gem to [RubyGems](https://rubygems.org/gems/composable_agents) and create a GitHub release with a generated changelog |
|
|
841
|
+
|
|
842
|
+
Pull requests must pass the **test** job before they can be merged.
|
|
843
|
+
|
|
844
|
+
### ๐ Pull request guidelines
|
|
845
|
+
|
|
846
|
+
- Keep PRs focused โ one feature or fix per pull request.
|
|
847
|
+
- Write a clear title and description. Reference any related issues (e.g. "Closes #42").
|
|
848
|
+
- Ensure all existing tests still pass (`bundle exec rspec`) and **add new specs** for your changes.
|
|
849
|
+
- Unit specs go under [`spec/composable_agents_test/`](https://github.com/Muriel-Salvan/composable_agents/tree/main/spec/composable_agents_test).
|
|
850
|
+
- Scenario specs (integration, documentation, packaging) go under [`spec/scenarios/`](https://github.com/Muriel-Salvan/composable_agents/tree/main/spec/scenarios).
|
|
851
|
+
- Run RuboCop and address any offenses.
|
|
852
|
+
- If your change adds a new public API method, document it with [YARD](https://yardoc.org/) โ the project enforces 100 % documented coverage.
|
|
853
|
+
- Commits should follow the [Conventional Commits](https://www.conventionalcommits.org/) style (e.g. `feat:`, `fix:`, `chore:`, `docs:`) so that `semantic-release` can determine the next version number automatically.
|
|
854
|
+
|
|
855
|
+
### ๐ License
|
|
856
|
+
|
|
857
|
+
By contributing, you agree that your contributions will be licensed under the [BSD-3-Clause License](https://github.com/Muriel-Salvan/composable_agents/blob/main/LICENSE) that covers the project.
|
|
858
|
+
|
|
859
|
+
---
|
|
860
|
+
|
|
861
|
+
*Questions? Open a [Discussion](https://github.com/Muriel-Salvan/composable_agents/discussions) or tag `@Muriel-Salvan` in an issue.* ๐ฌ
|
|
26
862
|
|
|
27
863
|
## License
|
|
28
864
|
|
|
29
|
-
The
|
|
865
|
+
The project is licensed under the **BSD 3-Clause License**.
|
|
866
|
+
|
|
867
|
+
See the [LICENSE](https://github.com/Muriel-Salvan/composable_agents/blob/main/LICENSE) file for the full terms.
|
|
868
|
+
|
|
869
|
+
Copyright ยฉ 2026, Muriel Salvan. All rights reserved.
|
data/TODO.md
CHANGED