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.
- 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
Add to your Gemfile:
gem"ragents"Then run:
bundle installFirst, 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 neededendclassResearchAgent < 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# 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.contentImmutable, 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?# => trueImmutable 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")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", ... } }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")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)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")Ragents.configuredo |config|
config.max_iterations=10# Max tool call loops per runconfig.timeout=120# Request timeout in secondsconfig.default_model="gpt-4o"endWhen Rails is detected, Ragents automatically:
- Adds
app/agentsto 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=180Use 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| Feature | Ragents | Active Agent |
|---|---|---|
| Framework | Pure Ruby | Rails-dependent |
| Concurrency | Ractor-based | Thread/Fiber |
| Messages | Immutable | Mutable |
| Focus | Multi-agent orchestration | Rails integration |
| LLM Backend | RubyLLM (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.
- Ruby 4.0+ (4.1 recommended for latest Ractor improvements)
- ruby_llm gem (automatically installed)
Bug reports and pull requests are welcome on GitHub.
The gem is available as open source under the terms of the MIT License.