Skip to content

Repository files navigation

Ragents

A pure Ruby, Ractor-based AI agent framework for concurrent, isolated agent execution.

Ragents provides a clean DSL for defining AI agents with tools, uses RubyLLM for access to 500+ models across all major providers, and enables true parallel execution using Ruby's Ractor primitive.

Features

  • Ractor-First Concurrency: Agents run in isolated Ractors for true parallelism
  • Pure Ruby: No Rails dependencies (optional Rails integration available)
  • Message-Passing Architecture: Immutable messages and contexts for safe concurrency
  • Tool-Based Actions: Declarative tool definitions with JSON Schema support
  • 500+ Models: Uses RubyLLM for OpenAI, Anthropic, Gemini, Ollama, and more
  • Multi-Agent Orchestration: Sequential workflows, parallel execution, and supervision
  • Composable: Agents can coordinate with other agents for complex workflows

Installation

Add to your Gemfile:

gem"ragents"

Then run:

bundle install

Quick Start

Configure RubyLLM

First, configure RubyLLM with your API keys:

# config/initializers/ruby_llm.rb (Rails)# or at application startupRubyLLM.configuredo |config|
config.openai_api_key=ENV["OPENAI_API_KEY"]config.anthropic_api_key=ENV["ANTHROPIC_API_KEY"]# Add other providers as neededend

Define an Agent

classResearchAgent < Ragents::Agentsystem_prompt"You are a research assistant. Use tools to find information."tool:search_webdodescription"Search the web for information"parameter:query,type: :string,required: true,description: "Search query"parameter:limit,type: :integer,default: 5executedo |query:,limit:|
SearchService.search(query,limit: limit)endendtool:summarizedodescription"Summarize a piece of text"parameter:text,type: :string,required: trueexecutedo |text:|
text.split.first(50).join(" ") + "..."endendend

Run the Agent

# Create a provider with any RubyLLM-supported modelprovider=Ragents::Providers::RubyLLM.new(model: "gpt-4o")# Or: "claude-sonnet-4-20250514", "gemini-2.0-flash", "llama3.2", etc.# Create and run the agentagent=ResearchAgent.new(provider: provider)result=agent.run(input: "Research the latest developments in Ruby 3.4")putsresult.last_message.content

Core Concepts

Messages

Immutable, Ractor-shareable message objects:

user_msg=Ragents::Message.user("Hello!")assistant_msg=Ragents::Message.assistant("Hi there!")system_msg=Ragents::Message.system("You are helpful.")tool_result=Ragents::Message.tool("result data",tool_call_id: "call_123")# Messages are frozen for Ractor safetyuser_msg.frozen?# => true

Context

Immutable conversation state:

context=Ragents::Context.newcontext=context.add_message(Ragents::Message.user("Hello"))context=context.add_message(Ragents::Message.assistant("Hi!"))# Contexts are immutable - operations return new contextscontext.size# => 2context.last_message.content# => "Hi!"# Truncate long conversationsshort_context=context.truncate(max_messages: 10,keep_system: true)# Fork for branching conversationsbranch=context.fork(experiment: "new-prompt")

Tools

Declarative tool definitions:

tool=Ragents::Tool.new(:calculate)dodescription"Perform a calculation"parameter:expression,type: :string,required: trueparameter:precision,type: :integer,default: 2executedo |expression:,precision:|
result=eval(expression)# Be careful with eval in production!result.round(precision)endend# Tools generate JSON Schema for LLM function callingtool.to_json_schema# => { type: "function", function: { name: "calculate", ... } }

Provider (RubyLLM)

Access 500+ models through RubyLLM:

# OpenAI modelsprovider=Ragents::Providers::RubyLLM.new(model: "gpt-4o")provider=Ragents::Providers::RubyLLM.new(model: "gpt-4o-mini")# Anthropic Claudeprovider=Ragents::Providers::RubyLLM.new(model: "claude-sonnet-4-20250514")provider=Ragents::Providers::RubyLLM.new(model: "claude-3-5-haiku-latest")# Google Geminiprovider=Ragents::Providers::RubyLLM.new(model: "gemini-2.0-flash")# Local Ollamaprovider=Ragents::Providers::RubyLLM.new(model: "llama3.2")# Test provider for unit teststest=Ragents::Providers::Test.newtest.stub_response(content: "Mocked response")

Multi-Agent Orchestration

Orchestrator

Coordinate multiple agents:

orchestrator=Ragents::Orchestrator.new(provider: provider)# Register agentsorchestrator.register(:researcher,ResearchAgent)orchestrator.register(:writer,WriterAgent)orchestrator.register(:editor,EditorAgent)# Run a single agentresult=orchestrator.run(:researcher,input: "Research topic X")# Run agents in parallel (uses Ractors)results=orchestrator.parallel([:researcher,{input: "Topic A"}],[:researcher,{input: "Topic B"}],[:researcher,{input: "Topic C"}])# Sequential workflowfinal=orchestrator.workflowdo |w|
w.step(:researcher,input: "Research the topic")w.step(:writer){ |ctx| {input: "Write about: #{ctx.last_response}"}}w.step(:editor)end# Supervised execution with automatic restartsresult=orchestrator.supervised(:researcher,input: "Important task",max_restarts: 3,restart_delay: 1)

Ractor-Based Parallelism

Run agents in isolated Ractors using Ruby 4.x APIs:

# Async executionractor=ResearchAgent.run_async(provider: provider,input: "Research task")# Do other work while agent runs...# Get result when ready (Ruby 4.x uses #value instead of #take)result=ractor.value# Or use the synchronous helperresult=ResearchAgent.run_in_ractor(provider: provider,input: "Research task")

Configuration

Global Configuration

Ragents.configuredo |config|
config.max_iterations=10# Max tool call loops per runconfig.timeout=120# Request timeout in secondsconfig.default_model="gpt-4o"end

Rails Integration

When Rails is detected, Ragents automatically:

  • Adds app/agents to autoload paths
  • Provides configuration via config.ragents
  • Instruments agent runs with ActiveSupport::Notifications
# config/initializers/ragents.rbRails.application.config.ragents.max_iterations=15Rails.application.config.ragents.timeout=180

Testing

Use the test provider for unit tests:

classMyAgentTest < Minitest::Testdefsetup@provider=Ragents::Providers::Test.newenddeftest_agent_responds@provider.stub_response(content: "Hello!")agent=MyAgent.new(provider: @provider)result=agent.run(input: "Hi")assert_equal"Hello!",result.last_message.contentenddeftest_agent_uses_tool@provider.stub_tool_call(name: :search,arguments: {query: "test"})@provider.stub_response(content: "Found results")agent=MyAgent.new(provider: @provider)result=agent.run(input: "Search for test")# Verify requests were madeassert_equal2,@provider.request_countendend

Comparison with Active Agent

FeatureRagentsActive Agent
FrameworkPure RubyRails-dependent
ConcurrencyRactor-basedThread/Fiber
MessagesImmutableMutable
FocusMulti-agent orchestrationRails integration
LLM BackendRubyLLM (500+ models)Multiple adapters

Ragents is designed to complement Active Agent - use Ragents for complex multi-agent workflows and integrate results back into Active Agent actions.

Requirements

  • Ruby 4.0+ (4.1 recommended for latest Ractor improvements)
  • ruby_llm gem (automatically installed)

Contributing

Bug reports and pull requests are welcome on GitHub.

License

The gem is available as open source under the terms of the MIT License.

About

Ractor based Agent Framework for RubyLLM

Resources

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages