A Ruby framework for building composable, prompt-driven AI agent pipelines — mix, match, and orchestrate agents into reusable workflows.
composable_agents is a Ruby gem that lets you build modular AI agent pipelines 🧩 — compose simple agents together into complex, resumable workflows.
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:
- 🧠 Create prompt-driven agents with role, objective, instructions, and constraints
- 🔄 Chain agents together so the output of one becomes the input of another
- 📦 Define typed artifact contracts with validation for inputs/outputs
- 💾 Resume interrupted runs — long workflows keep their state between executions
- 🗣️ Let agents ask users questions when they need clarification
- 🎯 Integrate with multiple LLM backends via cline-rb or ai-agents
- 📝 Use flexible prompt rendering (Markdown, or heavy Markdown with structured outputs)
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.
- Quick start
- Requirements
- Features
- Public API
- Documentation
- How it works
- Development
- Contributing
- License
Add the gem to your application's Gemfile:
bundle add composable_agentsOr install it globally:
gem install composable_agentsRequires Ruby >= 3.1.
Here's a minimal example that chains three agents together to build a holiday planner.
require 'composable_agents'
# --- 1. Define the agents ---
# An LLM-powered agent (uses ai-agents gem, needs an API key)
class ItineraryAgent < ComposableAgents::AiAgents::Agent
def initialize
super(
role: 'You are a travel planner',
objective: 'Find cities matching the user preferences',
system_instructions: <<~EO_INSTRUCTIONS,
Get the user preferences from the artifact named `preferences`.
Find the best cities.
Create an artifact named `cities` as a JSON list of city names.
EO_INSTRUCTIONS
model: 'openai/gpt-4o-mini' # or any model supported by your provider
)
end
end
# A plain Ruby agent (no LLM needed)
class BudgetAgent < ComposableAgents::RubyAgent
def initialize
super(proc do |input_artifacts|
cities = JSON.parse(input_artifacts[:cities])
{ budget: cities.size * 1000 }
end)
end
end
# --- 2. Configure the LLM provider (for ai-agents backend) ---
require 'agents'
Agents.configure do |config|
config.openrouter_api_key = ENV.fetch('OPENROUTER_API_KEY', nil)
end
# --- 3. Compose and run them ---
preferences = { preferences: 'Cultural city trips in Europe' }
itinerary_outputs = ItineraryAgent.new.run(**preferences)
budget_outputs = BudgetAgent.new.run(**itinerary_outputs)
puts "Cities: #{itinerary_outputs[:cities]}"
puts "Budget: $#{budget_outputs[:budget]}"If you prefer the cline-rb backend, set the CLINE_API_KEY environment variable:
# Use Cline-powered agents instead
itinerary_agent = ComposableAgents::Cline::Agent.new(
role: 'You are a travel planner',
objective: 'Find cities matching the user preferences',
model: 'anthropic/claude-sonnet-4.6',
api_key: ENV.fetch('CLINE_API_KEY', nil),
input_artifacts_contracts: { preferences: 'User travel preferences' },
output_artifacts_contracts: { cities: 'List of best cities' }
)- Browse the examples/ directory for full working scripts.
- Use the
ArtifactContractmixin to validate inputs/outputs. - Use the
Resumablemixin to persist and resume long-running workflows. - Use the
AiAgentUserInteractionmixin to let agents ask the user questions.
- Ruby >= 3.1 — The gem requires Ruby 3.1 or newer.
- Bundler — Used to install the gem and manage its dependencies (comes with Ruby).
- Node.js — Required at runtime by the
cline-rbbackend for pseudo-terminal (PTY) support vianode-pty. - An LLM provider API key — One of the following (depending on the agent backend you use):
- OpenRouter API key — Set via the
OPENROUTER_API_KEYenvironment variable when using theAiAgentsbackend. - Cline API key — Set via the
CLINE_API_KEYenvironment variable when using theClinebackend.
- OpenRouter API key — Set via the
composable_agents is a Ruby framework for building modular, prompt-driven AI agent pipelines 🧩. Here are its key capabilities:
- 🧠 Three agent types — Create LLM-powered agents via
PromptDrivenAgent, wrap plain Ruby logic withRubyAgent, or compose complex multi-step workflows using theResumablemixin. - 🔄 Composable pipelines — Pass output artifacts from one agent directly as input to another, forming reusable, chainable workflows.
- 🎯 Multiple LLM backends — Plug into different AI providers via the
ai-agentsgem (OpenRouter) orcline-rb(Claude, GPT, and many more). - 📝 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.
- 📦 Typed artifact contracts — Define and validate input/output schemas with descriptions, optional flags, and types (
:text,:markdown,:json). The framework raises clearMissingInputArtifactError,MissingOutputArtifactError, orArtifactTypeErroron violations. - 💾 Resumable execution — Persist step-by-step state to disk (via the
Resumablemixin). Interrupted runs can be resumed seamlessly — previously completed steps are skipped, saving time and API costs. - 🗣️ User interaction — Agents can ask users clarifying questions mid-execution. Works out of the box via the terminal, or through an
ai-agentstool integration for LLM-controlled workflows. - 📋 Automatic conversation tracking — Every prompt and response is automatically recorded with timestamps in a structured
conversationstore, ready for debugging or replay. - ⚙️ Flexible instruction system — Use raw text, structured ordered-lists, or a mix of both to define agent instructions, rendered consistently by the chosen strategy.
- 🛠️ Cline skill support — Select and enable specific Cline skills per agent, with automatic dependency resolution.
- 🔐 State export/import — Agents can serialize and restore their internal state via
export_state/import_state, enabling deep integration with the resumable workflow system. - 🐛 Debug logging — Toggle verbose debug output with the
COMPOSABLE_AGENTS_DEBUG=1environment variable. - 📐 Markdown header alignment — A built-in utility normalizes Markdown header levels across composed prompts, maintaining a clean document hierarchy.
This section documents all public entry points of the composable_agents gem. The project is a Ruby library (not a CLI) — users install the gem and use its classes and mixins in their own Ruby code.
- Description: The current version of the gem (
'0.1.0'). - Full documentation: version.rb on GitHub
- 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.
- Public methods:
#initialize(name: nil, composable_agents_dir: '.composable_agents')— Create a new agent with an optional name and a working directory.#name— Return the agent's name (String, ornil).#full_name— Return a human-readable full name for logs and traces (can be overridden by subclasses).
- Usage example:
class MyCustomAgent < ComposableAgents::Agent def run(**input_artifacts) # Process input_artifacts and return output artifacts { result: input_artifacts[:data].upcase } end end agent = MyCustomAgent.new(name: 'uppercaser') output = agent.run(data: 'hello') puts output[:result] # => "HELLO"
- Full documentation: Agent on RubyDoc | agent.rb on GitHub
- Description: An agent that wraps arbitrary Ruby logic as a
Proc. No LLM is needed — ideal for deterministic or simple processing steps. - Public methods:
#initialize(processor, *args, **kwargs)— Theprocessoris a#call-able object (e.g. aProc) that receives input artifacts and returns output artifacts.#run(**input_artifacts)— Execute the proc with the given input artifacts.
- Usage example:
# A simple agent that doubles a number double_agent = ComposableAgents::RubyAgent.new( proc { |inputs| { double: inputs[:number] * 2 } } ) result = double_agent.run(number: 21) puts result[:double] # => 42
- Full documentation: RubyAgent on RubyDoc | ruby_agent.rb on GitHub
- Description: Normalizes instructions (system prompts, user prompts) into a canonical list format. Supports plain text strings and structured hashes (e.g. with
ordered_listkeys). IncludesEnumerable. - Public methods:
#initialize(instructions)— Accepts aString, anArray, or aHash{text:, ordered_list:}.#each(&)— Iterate over each instruction as(type, content)pairs.
- Usage example:
instructions = ComposableAgents::Instructions.new({ ordered_list: ['Step one', 'Step two'] }) instructions.each do |type, content| puts "#{type}: #{content}" end
- Full documentation: Instructions on RubyDoc | instructions.rb on GitHub
- 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.
- Public methods:
#role/#role=— Agent's role description.#objective/#objective=— Agent's objective.#system_instructions/#system_instructions=— Instructions for the agent.#constraints/#constraints=— Constraints the agent must respect.#conversation— Read the conversation history (array of message hashes).#initialize(*args, role:, objective:, system_instructions:, constraints:, strategy:, **kwargs)— Thestrategydefaults toPromptRenderingStrategy::Markdown.#full_name— Human-readable name for logs.#run(user_instructions: nil, **input_artifacts)— Execute the agent and produce output artifacts.
- Usage example:
agent = ComposableAgents::PromptDrivenAgent.new( role: 'A helpful assistant', objective: 'Answer user questions' ) # Subclass and implement #prompt(user_prompt) to provide the LLM backend.
- Full documentation: PromptDrivenAgent on RubyDoc | prompt_driven_agent.rb on GitHub
- Description: An agent that uses the
ai-agentsgem as its LLM backend. Requires an OpenRouter API key configured viaAgents.configure. - Public methods:
#initialize(*args, model:, params:, handoff_agents:, **kwargs)— Specify themodel(e.g.'openai/gpt-4o-mini'), optionalparamsfor model configuration, and a list ofhandoff_agents.#full_name— Returns"<name> (AiAgent <model>)".
- Usage example:
require 'agents' Agents.configure { |c| c.openrouter_api_key = ENV['OPENROUTER_API_KEY'] } agent = ComposableAgents::AiAgents::Agent.new( role: 'Travel planner', objective: 'Suggest destinations', system_instructions: 'Create an artifact named `cities` with city names.', model: 'openai/gpt-4o-mini' ) result = agent.run(preferences: 'beach holidays') puts result[:cities]
- Full documentation: AiAgents::Agent on RubyDoc | ai_agents/agent.rb on GitHub
- Description: An agent that uses the
cline-rbgem as its LLM backend. Requires aCLINE_API_KEYenvironment variable. Automatically prepends theArtifactContractmixin. - Public methods:
#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.#full_name— Returns"<name> (Cline <provider>/<model>)".
- Usage example:
agent = ComposableAgents::Cline::Agent.new( role: 'Travel planner', objective: 'Suggest destinations', model: 'deepseek/deepseek-v4-flash', api_key: ENV['CLINE_API_KEY'], input_artifacts_contracts: { preferences: 'User preferences' }, output_artifacts_contracts: { cities: 'City list' } ) result = agent.run(preferences: 'cultural trips in Italy') puts result[:cities]
- Full documentation: Cline::Agent on RubyDoc | cline/agent.rb on GitHub
- Description: Raised by
Cline::Agentwhen a referenced skill is not found in the global or project Cline configuration. - Full documentation: cline/agent.rb on GitHub
Mixins are prepend-ed into an agent class to add specific capabilities.
- Description: Provides debug and info logging to agents. Debug mode is enabled by setting
COMPOSABLE_AGENTS_DEBUG=1in the environment. - Public methods (class-level):
self.debug?— Returnstrueif debug mode is enabled (ENV['COMPOSABLE_AGENTS_DEBUG'] == '1').
- Usage example:
# Enable debug logs ENV['COMPOSABLE_AGENTS_DEBUG'] = '1' puts ComposableAgents::Mixins::Logger.debug? # => true
- Full documentation: Mixins::Logger on RubyDoc | logger.rb on GitHub
- 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.
- Public methods:
#initialize(*args, run_id:, **kwargs)— Therun_ididentifies the persisted run.
- Usage example:
class WorkflowAgent < ComposableAgents::Agent prepend ComposableAgents::Mixins::Resumable def run(**inputs) @artifacts = inputs step(:process_data) { @artifacts[:result] = @artifacts[:data].upcase } step(:finalize) { @artifacts[:done] = true } @artifacts end end # If interrupted between steps, re-running with the same run_id skips completed steps WorkflowAgent.new(run_id: 'my_workflow').run(data: 'hello')
- Full documentation: Mixins::Resumable on RubyDoc | resumable.rb on GitHub
- 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_tofor custom behavior. - Public methods:
#ask(question)— Ask the user a question and return the answer.
- Usage example:
class InteractiveAgent < ComposableAgents::Agent include ComposableAgents::Mixins::UserInteraction def run(**) name = ask('What is your name?') { greeting: "Hello, #{name}!" } end end
- Full documentation: Mixins::UserInteraction on RubyDoc | user_interaction.rb on GitHub
- 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). - Public error classes:
MissingInputArtifactError < RuntimeError— Raised when required input artifacts are missing.MissingOutputArtifactError < RuntimeError— Raised when expected output artifacts are missing after execution.ArtifactTypeError < RuntimeError— Raised when an artifact's content does not match its declared type.
- Public methods:
#initialize(*args, input_artifacts_contracts:, output_artifacts_contracts:, **kwargs)— Contracts areHash{Symbol => String}(simple description) orHash{Symbol => Hash{description:, optional:, type:}}.
- Usage example:
class ValidatedAgent < ComposableAgents::Agent prepend ComposableAgents::Mixins::ArtifactContract def input_artifacts_contracts { name: { description: 'User name', type: :text } } end def output_artifacts_contracts { greeting: { description: 'Greeting message', type: :text } } end def run(**inputs) { greeting: "Hello, #{inputs[:name]}!" } end end agent = ValidatedAgent.new agent.run(name: 'World') # => { greeting: "Hello, World!" } agent.run(foo: 'bar') # Raises MissingInputArtifactError
- Full documentation: Mixins::ArtifactContract on RubyDoc | artifact_contract.rb on GitHub
- No executables / CLI — This gem is a library only; there are no scripts in
bin/. - Prompt rendering strategies (
PromptRenderingStrategy::MarkdownandPromptRenderingStrategy::MarkdownHeavy) are not part of the public API themselves — they are included automatically by the agent'sstrategy:parameter. - The
AiAgentUserInteractionmixin (ComposableAgents::Mixins::AiAgentUserInteraction) is an internal bridge that combinesUserInteractionwithAiAgents::Agent; it is used by prepending it to anAiAgents::Agentsubclass. - See the examples/ directory for complete, runnable scripts demonstrating all the above APIs.
- 📖 Main README — README.md — Overview, installation, usage, and development instructions.
- 📚 RubyDoc.info (API reference) — composable_agents on RubyDoc — Auto-generated YARD documentation for all public classes, modules, and methods. Covers the full API with 100% documented coverage.
- 🏠 GitHub Repository — github.com/Muriel-Salvan/composable_agents — Source code, issue tracker, and pull requests.
- 📄 License — BSD-3-Clause License — The gem is available as open source under the BSD 3-Clause License.
- 💡 Examples —
examples/directory — Runnable Ruby scripts demonstrating simple pipelines, resumable workflows, and user interaction patterns. - ⚙️ CI / Build — Continuous Integration workflow — GitHub Actions configuration for running tests and publishing releases.
- 📁 Source Code — Browse the
lib/directory for inline YARD-annotated source documentation of all agents, mixins, and rendering strategies:Agent— Abstract base classPromptDrivenAgent— LLM-prompted agentRubyAgent— Plain Ruby logic agentAiAgents::Agent— ai-agents backendCline::Agent— cline-rb backendInstructions— Instruction systemMixins::Resumable— Resumable workflow mixinMixins::ArtifactContract— Artifact validation mixinMixins::UserInteraction— User question-asking mixinMixins::Logger— Debug logging mixinPromptRenderingStrategy::Markdown— Markdown prompt strategyPromptRenderingStrategy::MarkdownHeavy— Heavy Markdown prompt strategyUtils::Markdown— Markdown header alignment utilities
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.
classDiagram
class Agent {
+run(**input_artifacts)~Hash~
+full_name()~String~
}
class PromptDrivenAgent {
+String role
+String objective
+String system_instructions
+String constraints
+Array conversation
#prompt(user_prompt)~String~
}
class RubyAgent {
-Proc processor
+run(**input_artifacts)~Hash~
}
class AiAgents_Agent {
-AgentRunner agent_runner
#prompt(user_prompt)~String~
}
class Cline_Agent {
-Cline::Config cline_config
#prompt(user_prompt)~String~
}
Agent <|-- PromptDrivenAgent : extends
Agent <|-- RubyAgent : extends
PromptDrivenAgent <|-- AiAgents_Agent : extends
PromptDrivenAgent <|-- Cline_Agent : extends
The framework provides a clean 4-class hierarchy:
Agent— Abstract base class. Defines therun(**input_artifacts)contract and includes theMixins::Loggerfor debug/info logging.RubyAgent— Wraps any RubyProcas an agent. No LLM involved: callproc.call(input_artifacts)and return a hash. Ideal for deterministic logic.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 aconversationarray.AiAgents::Agent/Cline::Agent— Concrete LLM backends: one wraps theai-agentsgem, the other wrapscline-rb. Both implement#prompt(user_prompt)to send the rendered prompt to the LLM.
Agents communicate exclusively through artifacts — named key/value pairs in a Ruby Hash:
outputs = agent.run(**inputs)
# outputs[:city] can feed into next_agent.run(city: outputs[:city])💡 Agents are stateless by design — no hidden mutable state. This makes them easy to test, debug, and reorder in pipelines.
Here's what happens when you call run(**inputs) on a PromptDrivenAgent:
flowchart TD
A[run] --> B[Render system prompt]
B --> C[Call #prompt with system prompt]
C --> D{Missing output\nartifacts?}
D -->|Yes| E[Render retry prompt]
E --> F[Call #prompt again]
F --> D
D -->|No| G[Return output artifacts]
render_system_prompt— Assembles role, objective, instructions, and constraints into a structured Markdown document (via the chosen strategy).#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.- Retry loop — If expected output artifacts are missing, a retry prompt is generated and sent again.
- Returns the collected
{ artifact_name => content }hash.
Two strategies are included, mixed into the agent at initialization via singleton_class.include strategy:
PromptRenderingStrategy::Markdown— Clean, minimal Markdown. Simple instructions and artifact references.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 withoutput_artifact=NAME, which the strategy parses back into the output hash. Includes type-aware parsing (:text,:markdown,:json).
| Feature | AiAgents::Agent |
Cline::Agent |
|---|---|---|
| Underlying gem | ai-agents | cline-rb |
| Tools | Exposes CreateArtifactTool, GetArtifactTool to the LLM |
Uses Cline's skill system |
| State persistence | Marshal + Base64 via export_state/import_state |
Direct JSON serialization of context array |
| Default rendering | Markdown |
MarkdownHeavy (with structured output parsing) |
| User interaction | Optional AskUserTool via AiAgentUserInteraction |
N/A (uses Cline's own interaction) |
Mixins are prepended (using prepend) or included (using include) to override #run or add new methods:
Mixins::ArtifactContract— Wraps#runto validate inputs before and outputs after execution against declared contracts. RaisesMissingInputArtifactError,MissingOutputArtifactError, orArtifactTypeErroron violations.Mixins::Resumable— Overrides#runwith a step-based execution model. Eachstepblock 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 viaexport_state/import_state.Mixins::UserInteraction— Adds an#ask(question)method. By default prompts the terminal; override#answer_tofor custom behavior.Mixins::Logger— Provideslog_debug/log_infomethods. Debug output is toggled via theCOMPOSABLE_AGENTS_DEBUG=1environment variable.
The Instructions class normalizes instructions into a standard list format. Each instruction can be:
{ text: "..." }— Free-form text{ ordered_list: ["Step 1", "Step 2"] }— Sequential steps
The rendering strategy then renders each type appropriately (#render_instruction_text, #render_instruction_ordered_list).
Uses zeitwerk for automatic, thread-safe code autoloading — no manual require calls needed beyond the top-level entry point.
flowchart LR
A[Step: fetch_data] --> B{Step JSON\nexists?}
B -->|Yes| C[Deserialize state\nSkip execution]
B -->|No| D[Execute block]
D --> E[Serialize artifacts\n+ agent state to JSON]
C --> F[Continue with\nrestored artifacts]
E --> F
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.
- Ruby >= 3.1
- Bundler (comes with Ruby)
- Node.js — required by the
cline-rbbackend for pseudo-terminal support (node-pty)
git clone https://github.com/Muriel-Salvan/composable_agents.git
cd composable_agentsbundle installAdditionally, install the Node.js pseudo-terminal dependency that the cline-rb backend expects:
npm install node-pty.
├── lib/ # Source code (autoloaded via Zeitwerk)
│ └── composable_agents/ # Main library modules
│ ├── agent.rb # Abstract base agent
│ ├── prompt_driven_agent.rb # LLM-prompted agent
│ ├── ruby_agent.rb # Plain Ruby logic agent
│ ├── instructions.rb # Instruction system
│ ├── ai_agents/ # ai-agents backend
│ ├── cline/ # cline-rb backend
│ ├── mixins/ # Resumable, ArtifactContract, UserInteraction, Logger
│ ├── prompt_rendering_strategy/ # Markdown & MarkdownHeavy strategies
│ └── utils/ # Markdown utilities
├── spec/ # Test suite
│ ├── scenarios/ # RSpec test cases
│ └── composable_agents_test/ # Test helpers, spies, stubs
├── examples/ # Runnable usage examples
├── Gemfile # Dependencies
├── composable_agents.gemspec # Gem specification
├── .rubocop.yml # RuboCop configuration
└── .github/workflows/ # CI pipeline
Run the full test suite with RSpec:
bundle exec rspecRun a specific test file:
bundle exec rspec spec/scenarios/composable_agents/cline/agent_spec.rbRun tests with verbose documentation output:
bundle exec rspec --format documentationSet the TEST_DEBUG=1 environment variable to enable verbose debug output during test execution:
TEST_DEBUG=1 bundle exec rspecThe test suite enforces 99% minimum code coverage via SimpleCov. Coverage reports are generated in Cobertura format and automatically uploaded to Codecov in CI.
This project uses RuboCop with the rubocop-rspec and rubocop-yard plugins. Run the linter:
bundle exec rubocopTo auto-correct fixable offenses:
bundle exec rubocop -aLinting 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.
API documentation is generated with YARD. The project enforces 100% documented code:
bundle exec yard doc --fail-on-warningCheck documentation coverage stats:
bundle exec yard stats --list-undoc --fail-on-warningDocumentation generation and coverage are also verified as part of the test suite via the Documentation generation spec.
Build the gem locally:
gem build composable_agents.gemspecThis 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.
- 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.rbforComposableAgents::MyFeature). - Add RSpec tests under
spec/scenarios/composable_agents/following the existing patterns. - Document all public methods with YARD annotations.
- Run the full test suite and linting before committing:
bundle exec rspec && bundle exec rubocopPlace 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.
Example scripts are located in the examples/ directory. Run any example directly with Ruby:
bundle exec ruby examples/compose_without_ai.rbSome examples require an LLM provider API key. Set the appropriate environment variable before running:
OPENROUTER_API_KEY=your_key bundle exec ruby examples/compose_with_ai.rb
# or
CLINE_API_KEY=your_key bundle exec ruby examples/compose_with_cline.rbThe project uses GitHub Actions (defined in .github/workflows/continuous_integration.yml):
testjob — Runs the full RSpec suite on push (Ruby 3.4, with Bundler cache andnode-ptyinstalled). Coverage is uploaded to Codecov.packagejob — Runs after tests pass, using semantic-release to publish the gem to RubyGems.org when a new version is tagged.
Releases are automated via semantic-release. Pushing a tag with a commit message following conventional commits format triggers the CI package job, which:
- Builds the gem
- Publishes it to RubyGems.org
- Creates a GitHub release with changelog
Manual gem publishing can also be done with:
GEM_HOST_API_KEY=your_key gem push composable_agents-*.gemBug reports, feature suggestions, and pull requests are warmly welcomed on GitHub at github.com/Muriel-Salvan/composable_agents. Please follow the guidelines below to keep things running smoothly.
- Before opening an issue, search the existing tracker to avoid duplicates.
- For a bug report, include:
- Ruby version (
ruby -v) - gem version
- a minimal code snippet that reproduces the problem
- the full error output or unexpected behaviour
- Ruby version (
- 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.
- Fork the repository on GitHub.
- Clone your fork locally:
git clone https://github.com/<your-username>/composable_agents.git cd composable_agents
- Create a feature branch from
main:We follow a semantic-release workflow, so branch names likegit checkout -b feat/my-awesome-feature
feat/…,fix/…,chore/…help the CI determine the next version bump.
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) 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).
The project uses RuboCop with the rubocop-rspec plugin. Run the linter before pushing:
bundle exec rubocopConfiguration lives in .rubocop.yml. Key allowances (long methods, nested RSpec groups, etc.) are already tuned to the codebase — please keep them as they are.
| Job | When | What it does |
|---|---|---|
| test | Every push | Installs Ruby 3.4 + Node (for node-pty), runs bundle exec rspec, uploads coverage to Codecov |
| package | After tests pass | Runs semantic-release to auto-publish the gem to RubyGems and create a GitHub release with a generated changelog |
Pull requests must pass the test job before they can be merged.
- Keep PRs focused — one feature or fix per pull request.
- Write a clear title and description. Reference any related issues (e.g. "Closes #42").
- Ensure all existing tests still pass (
bundle exec rspec) and add new specs for your changes.- Unit specs go under
spec/composable_agents_test/. - Scenario specs (integration, documentation, packaging) go under
spec/scenarios/.
- Unit specs go under
- Run RuboCop and address any offenses.
- If your change adds a new public API method, document it with YARD — the project enforces 100 % documented coverage.
- Commits should follow the Conventional Commits style (e.g.
feat:,fix:,chore:,docs:) so thatsemantic-releasecan determine the next version number automatically.
By contributing, you agree that your contributions will be licensed under the BSD-3-Clause License that covers the project.
Questions? Open a Discussion or tag @Muriel-Salvan in an issue. 💬
The project is licensed under the BSD 3-Clause License.
See the LICENSE file for the full terms.
Copyright © 2026, Muriel Salvan. All rights reserved.