<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: George Kosmopoulos</title>
    <description>The latest articles on DEV Community by George Kosmopoulos (@gkosmo).</description>
    <link>https://dev.to/gkosmo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F106316%2F516c6ac7-293a-44e7-8116-996c67aabd5b.jpeg</url>
      <title>DEV Community: George Kosmopoulos</title>
      <link>https://dev.to/gkosmo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gkosmo"/>
    <language>en</language>
    <item>
      <title>AI agents in Rails, for a business that actually has customers</title>
      <dc:creator>George Kosmopoulos</dc:creator>
      <pubDate>Thu, 10 Sep 2026 09:34:55 +0000</pubDate>
      <link>https://dev.to/gkosmo/ai-agents-in-rails-for-a-business-that-actually-has-customers-131e</link>
      <guid>https://dev.to/gkosmo/ai-agents-in-rails-for-a-business-that-actually-has-customers-131e</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.gkosmo.eu/blogs/ai-agents-in-rails-for-a-business-that-actually-has-customers" rel="noopener noreferrer"&gt;gkosmo.eu&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;An agent is a background job that gets to call a few methods&lt;/p&gt;

&lt;p&gt;I have been putting this post off for months. Partly because "AI agents" is a phrase that makes me tired, and partly because most of what I read about it is either a demo that writes haikus or an architecture diagram with eleven boxes.&lt;/p&gt;

&lt;p&gt;Here is what I actually run. It's small. You can paste it into a Rails app this afternoon.&lt;/p&gt;

&lt;p&gt;The gems are &lt;code&gt;rcrewai&lt;/code&gt; and &lt;code&gt;rcrewai-rails&lt;/code&gt;. I maintain both and I use them on &lt;a href="https://nakyma.io" rel="noopener noreferrer"&gt;nakyma.io&lt;/a&gt;, so this is not a neutral review. It is the thing I reach for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The example
&lt;/h2&gt;

&lt;p&gt;A shop. Customers open support tickets. Somebody in the shop reads each one, looks up the customer's orders, decides if it's a refund, a shipping question or something else, and writes a reply.&lt;/p&gt;

&lt;p&gt;That last person is the bottleneck. We are going to replace the reading and the drafting, and keep the sending.&lt;/p&gt;

&lt;p&gt;That line matters: agents draft, humans send. Every time I've skipped this rule I regretted it within a week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Gemfile&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"rcrewai"&lt;/span&gt;
&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"rcrewai-rails"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/initializers/rcrewai.rb&lt;/span&gt;
&lt;span class="no"&gt;RCrewAI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;llm_provider&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:anthropic&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;anthropic_api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ANTHROPIC_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;anthropic_model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ANTHROPIC_MODEL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"claude-sonnet-4-5"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;temperature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Temperature low. This is a support desk, not a poetry slam.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tool
&lt;/h2&gt;

&lt;p&gt;This is the part people get wrong, so it comes first.&lt;/p&gt;

&lt;p&gt;A tool is a Ruby object the model is allowed to call. Not "the database". Not "ActiveRecord". One method, scoped to one customer, read-only, returning a string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/tools/order_lookup_tool.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderLookupTool&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;RCrewAI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Tools&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;tool_name&lt;/span&gt; &lt;span class="s2"&gt;"order_lookup"&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="s2"&gt;"Recent orders for the customer who opened this ticket. "&lt;/span&gt; &lt;span class="p"&gt;\&lt;/span&gt;
              &lt;span class="s2"&gt;"Returns number, status, total and shipped_at."&lt;/span&gt;

  &lt;span class="n"&gt;param&lt;/span&gt; &lt;span class="ss"&gt;:limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;type: :integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;required: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;description: &lt;/span&gt;&lt;span class="s2"&gt;"How many recent orders to return (max 10)"&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;:)&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="vi"&gt;@customer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;limit: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orders&lt;/span&gt;
             &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;created_at: :desc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
             &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
             &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:shipped_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
             &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;param&lt;/code&gt; DSL turns into a JSON schema the model sees. The &lt;code&gt;customer:&lt;/code&gt; is injected by us, not chosen by the model. The model cannot ask for someone else's orders because there is no parameter for that. Security by not having the argument. It's the cheapest kind.&lt;/p&gt;

&lt;p&gt;The gem ships with &lt;code&gt;WebSearch&lt;/code&gt;, &lt;code&gt;SqlDatabase&lt;/code&gt;, &lt;code&gt;FileWriter&lt;/code&gt; and friends, and the Rails engine adds an &lt;code&gt;ActiveRecordTool&lt;/code&gt;. I don't use the generic ones in production. A tool that can run any query is a tool I have to explain to a customer later.&lt;/p&gt;

&lt;h2&gt;
  
  
  The crew
&lt;/h2&gt;

&lt;p&gt;Two agents, two tasks, run in order. The first drafts, the second checks and formats.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/services/ticket_triage.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TicketTriage&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Failed&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;StandardError&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="no"&gt;OUTPUT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;properties: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="ss"&gt;category:         &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;  &lt;span class="c1"&gt;# refund | shipping | other&lt;/span&gt;
      &lt;span class="ss"&gt;refund_requested: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s2"&gt;"boolean"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="ss"&gt;reply:            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;type: &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="ss"&gt;required: &lt;/span&gt;&lt;span class="sx"&gt;%w[category refund_requested reply]&lt;/span&gt;
  &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;freeze&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@ticket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ticket&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;
    &lt;span class="n"&gt;support&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RCrewAI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"support"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;role: &lt;/span&gt;&lt;span class="s2"&gt;"Support agent at a small online shop"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;goal: &lt;/span&gt;&lt;span class="s2"&gt;"Answer the customer accurately using their real order data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;backstory: &lt;/span&gt;&lt;span class="s2"&gt;"You never invent order details. If the tool has no "&lt;/span&gt; &lt;span class="p"&gt;\&lt;/span&gt;
                 &lt;span class="s2"&gt;"matching order, say so and ask for the order number."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;tools: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="no"&gt;OrderLookupTool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;customer: &lt;/span&gt;&lt;span class="vi"&gt;@ticket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
      &lt;span class="ss"&gt;max_iterations: &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;reviewer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RCrewAI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"reviewer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;role: &lt;/span&gt;&lt;span class="s2"&gt;"Support lead"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;goal: &lt;/span&gt;&lt;span class="s2"&gt;"Make sure replies follow policy and are short"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;backstory: &lt;/span&gt;&lt;span class="s2"&gt;"Policy: refunds within 30 days of delivery, no questions. "&lt;/span&gt; &lt;span class="p"&gt;\&lt;/span&gt;
                 &lt;span class="s2"&gt;"After 30 days, offer store credit. Never promise a date "&lt;/span&gt; &lt;span class="p"&gt;\&lt;/span&gt;
                 &lt;span class="s2"&gt;"we did not get from the order data."&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RCrewAI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"draft"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;agent: &lt;/span&gt;&lt;span class="n"&gt;support&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;description: &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;TXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;
        Customer &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@ticket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt; wrote:

        &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@ticket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;

        Look up their orders, then write a reply.
&lt;/span&gt;&lt;span class="no"&gt;      TXT&lt;/span&gt;
      &lt;span class="ss"&gt;expected_output: &lt;/span&gt;&lt;span class="s2"&gt;"A plain-text reply of at most 120 words."&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;review&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RCrewAI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"review"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;agent: &lt;/span&gt;&lt;span class="n"&gt;reviewer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;context: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;description: &lt;/span&gt;&lt;span class="s2"&gt;"Check the draft against policy. Fix it if needed. "&lt;/span&gt; &lt;span class="p"&gt;\&lt;/span&gt;
                   &lt;span class="s2"&gt;"Classify the ticket."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;expected_output: &lt;/span&gt;&lt;span class="s2"&gt;"JSON with category, refund_requested and reply."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;output_schema: &lt;/span&gt;&lt;span class="no"&gt;OUTPUT&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;crew&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RCrewAI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Crew&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ticket_triage"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;crew&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;support&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;crew&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reviewer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;crew&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;crew&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;review&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;crew&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;Failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:results&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:result&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:failed_tasks&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="n"&gt;review&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;structured_output&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Things to notice.&lt;/p&gt;

&lt;p&gt;The ticket text goes straight into the task description. There is no template engine, no &lt;code&gt;{placeholder}&lt;/code&gt; interpolation. It's a Ruby heredoc. The crew is built per ticket and thrown away. Objects are cheap; I stopped trying to make crews reusable and my code got shorter.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;context: [draft]&lt;/code&gt; is how the reviewer sees the support agent's output. That's the whole "multi-agent" story. One task's result becomes the next task's input. Nothing more mystical than a pipeline.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;output_schema&lt;/code&gt; makes the last task return parsed JSON, validated, with retries when the model rambles. &lt;code&gt;review.structured_output&lt;/code&gt; is a Hash with string keys. No regex on model output. If you are writing &lt;code&gt;JSON.parse(response[/\{.*\}/m])&lt;/code&gt; somewhere, this is the fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  The job
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/jobs/triage_ticket_job.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TriageTicketJob&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationJob&lt;/span&gt;
  &lt;span class="n"&gt;queue_as&lt;/span&gt; &lt;span class="ss"&gt;:agents&lt;/span&gt;
  &lt;span class="n"&gt;retry_on&lt;/span&gt; &lt;span class="no"&gt;RCrewAI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;TicketTriage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Failed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
           &lt;span class="ss"&gt;wait: :polynomially_longer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;attempts: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;TicketTriage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;

    &lt;span class="n"&gt;ticket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;category:         &lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;refund_requested: &lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"refund_requested"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;draft_reply:      &lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"reply"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="ss"&gt;triaged_at:       &lt;/span&gt;&lt;span class="no"&gt;Time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/support_ticket.rb&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SupportTicket&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:customer&lt;/span&gt;
  &lt;span class="n"&gt;after_create_commit&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;TriageTicketJob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform_later&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. A ticket arrives, thirty seconds later it has a category and a draft reply sitting in the admin, and a human clicks send or rewrites it. Give the &lt;code&gt;agents&lt;/code&gt; queue its own worker so a slow model call doesn't sit in front of your password reset emails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where rcrewai-rails comes in
&lt;/h2&gt;

&lt;p&gt;Everything above is the core gem. It's plain Ruby, it runs in a rake task, in a test, anywhere.&lt;/p&gt;

&lt;p&gt;The Rails engine is for the day after it works, when someone asks "why did it tell Mrs Dupont she'd get a refund?".&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rails generate rcrewai:rails:install
rails db:migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/routes.rb&lt;/span&gt;
&lt;span class="n"&gt;mount&lt;/span&gt; &lt;span class="no"&gt;RcrewAI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Engine&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"/rcrewai"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get tables for crews, agents, tasks and executions, an ActiveJob that records every run with its inputs, output, duration and error, and a span tree of every prompt and tool call underneath. There is a dashboard at &lt;code&gt;/rcrewai&lt;/code&gt;. On nakyma.io that is how I find out what a run cost and where it looped.&lt;/p&gt;

&lt;p&gt;If you want your crews to live in the database instead of in a service object, the engine has a &lt;code&gt;CrewBuilder&lt;/code&gt; mixin and &lt;code&gt;RcrewAI::Rails::Crew#execute_async&lt;/code&gt;. I use it for the crews that don't change per request. For the per-ticket kind, the PORO above is simpler and I'd start there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Opinions, since you asked
&lt;/h2&gt;

&lt;p&gt;Two agents is plenty. I've never needed a third that a better task description couldn't replace.&lt;/p&gt;

&lt;p&gt;Put the policy in the backstory, not in a vector database. Thirty days, store credit after, don't promise dates. That fits in a sentence. Retrieval is for when it doesn't fit in a page.&lt;/p&gt;

&lt;p&gt;Make the tool the security boundary. The model gets a method with the arguments you choose. Nothing else.&lt;/p&gt;

&lt;p&gt;Never let the agent's output reach a customer without a &lt;code&gt;draft_&lt;/code&gt; prefix on the column. The day you remove the prefix should be a decision, not an accident.&lt;/p&gt;

&lt;p&gt;And run it as a job. An agent is a background job that gets to call a few methods. Once I stopped thinking of it as anything more, it fit into Rails the way everything else does.&lt;/p&gt;

&lt;p&gt;Code for both gems: &lt;a href="https://github.com/gkosmo/rcrewAI" rel="noopener noreferrer"&gt;rcrewAI&lt;/a&gt; and &lt;a href="https://github.com/gkosmo/rcrewai-rails" rel="noopener noreferrer"&gt;rcrewai-rails&lt;/a&gt;. Issues welcome, especially the ones that say I'm wrong.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>Check the code health of your rails app</title>
      <dc:creator>George Kosmopoulos</dc:creator>
      <pubDate>Tue, 17 Jun 2025 14:55:00 +0000</pubDate>
      <link>https://dev.to/gkosmo/check-the-code-health-of-your-rails-app-ag5</link>
      <guid>https://dev.to/gkosmo/check-the-code-health-of-your-rails-app-ag5</guid>
      <description>&lt;h2&gt;
  
  
  Rails Code Health: Turn Technical Debt Into Hard Numbers
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;What if your "quick fix" could predict next quarter's delivery delays? What if messy code came with a price tag? What if your codebase could literally tell you it needs therapy?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Meet &lt;strong&gt;Rails Code Health&lt;/strong&gt; - the first gem to put a scientific health score on your Rails codebase, because apparently we needed peer-reviewed research to confirm that spaghetti code is, in fact, terrible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The $85 Billion "I Told You So" Moment
&lt;/h2&gt;

&lt;p&gt;Here's what researchers found when they studied real production codebases (shocking absolutely no one who's ever worked with legacy code):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;💥 Low quality code has 15x more bugs&lt;/strong&gt; &lt;em&gt;(surprised Pikachu face)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⏰ Takes 124% longer to ship features&lt;/strong&gt; &lt;em&gt;(somewhere, a PM cries)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🎲 Creates 9x more unpredictable delivery times&lt;/strong&gt; &lt;em&gt;(your estimates were doomed from the start)&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The kicker? &lt;strong&gt;42% of developer time is wasted on technical debt&lt;/strong&gt;, costing the industry $85 billion annually. That's enough money to buy Twitter... wait, scratch that, bad example. That's enough money to buy a lot of coffee for very tired developers.&lt;/p&gt;

&lt;p&gt;Yet only 10% of companies actively manage it. The other 90% are too busy putting out fires they accidentally started.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Research That Changes Everything (Finally!)
&lt;/h2&gt;

&lt;p&gt;The peer-reviewed paper &lt;a href="https://arxiv.org/pdf/2203.04374" rel="noopener noreferrer"&gt;"Code Red: The Business Impact of Code Quality"&lt;/a&gt; analyzed 30,737 files across diverse industries. Scientists literally counted bugs so you don't have to argue with your PM about why that "simple" feature took three sprints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Translation:&lt;/strong&gt; That controller you've been avoiding since 2019 isn't just scary—it's statistically proven to be a productivity vampire. 🧛‍♂️&lt;/p&gt;

&lt;h2&gt;
  
  
  Rails Code Health: Because Your Code Deserves Better Than "It Works™"
&lt;/h2&gt;

&lt;p&gt;This gem transforms academic research into actionable insights for Rails applications. Think of it as a fitness tracker for your codebase, except instead of counting steps, it counts tears shed during debugging sessions.&lt;/p&gt;

&lt;h3&gt;
  
  
  🎯 Smart Analysis Beyond "It Has Too Many Lines"
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# This looks fine to humans who haven't lost all hope...&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UsersController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="c1"&gt;# 50 lines of business logic, validations, email sending,&lt;/span&gt;
    &lt;span class="c1"&gt;# PDF generation, coffee brewing, existential dread...&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;# But Rails Code Health knows this is a war crime&lt;/span&gt;
&lt;span class="c1"&gt;# Score: 4.2/10.0 - "This method needs an intervention"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  📊 One Number to Rule Them All
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Health Score: 1-10 scale (like rating your pain at the doctor, but for code)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;🟢 8.0-10.0&lt;/strong&gt;: &lt;em&gt;"Chef's kiss"&lt;/em&gt; - Ship fast, sleep well, maybe even have a social life&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🟡 4.0-7.9&lt;/strong&gt;: Manageable technical debt (like a small kitchen fire)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🔴 1.0-3.9&lt;/strong&gt;: Your code is screaming internally (and so are you)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;⚫ &amp;lt;1.0&lt;/strong&gt;: Abandon all hope, ye who enter here&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔍 Rails-Specific Intelligence (It Speaks Rails!)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Controllers&lt;/strong&gt;: Counts actions like a bouncer counts troublemakers&lt;br&gt;
&lt;strong&gt;Models&lt;/strong&gt;: Detects fat models before they need bariatric surgery&lt;br&gt;
&lt;strong&gt;Views&lt;/strong&gt;: Spots business logic hiding in templates like a very boring game of hide-and-seek&lt;br&gt;
&lt;strong&gt;Migrations&lt;/strong&gt;: Judges your schema changes harder than Gordon Ramsay judges amateur chefs&lt;/p&gt;
&lt;h2&gt;
  
  
  See Your Code's Therapy Session
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;rails-health

Rails Code Health Report
&lt;span class="o"&gt;========================&lt;/span&gt;
🏥 Code Triage Results

📊 Overall Health: 6.8/10.0 &lt;span class="o"&gt;(&lt;/span&gt;Could be worse! Could be WordPress!&lt;span class="o"&gt;)&lt;/span&gt;
🟢 Healthy: 89 files &lt;span class="o"&gt;(&lt;/span&gt;Living their best life&lt;span class="o"&gt;)&lt;/span&gt;
🔴 Alert: 6 files &lt;span class="o"&gt;(&lt;/span&gt;Currently having existential crisis&lt;span class="o"&gt;)&lt;/span&gt;

🚨 Patients Needing Immediate Care:
1. 🔴 app/models/user.rb &lt;span class="o"&gt;(&lt;/span&gt;2.8/10.0&lt;span class="o"&gt;)&lt;/span&gt; - &lt;span class="k"&gt;*&lt;/span&gt;Terminal &lt;span class="k"&gt;case*&lt;/span&gt;
   • 847 lines - This isn&lt;span class="s1"&gt;'t a model, it'&lt;/span&gt;s a novella
   • 12 callbacks - More callbacks than a reality TV show

2. 🔴 app/controllers/admin_controller.rb &lt;span class="o"&gt;(&lt;/span&gt;3.1/10.0&lt;span class="p"&gt;)&lt;/span&gt; - &lt;span class="k"&gt;*&lt;/span&gt;Critically obese&lt;span class="k"&gt;*&lt;/span&gt;
   • 18 actions - REST &lt;span class="k"&gt;in &lt;/span&gt;peace, RESTful conventions
   • No strong parameters - Security? We don&lt;span class="s1"&gt;'t know her

💰 Business Impact (AKA "Why You Should Care"):
   Fixing alert files could speed development by 55%
   (That'&lt;/span&gt;s like getting a whole extra developer, but cheaper and less pizza required&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  The ROI Calculator (Or: How to Justify Your Refactoring to The Suits)
&lt;/h2&gt;

&lt;p&gt;Based on actual science (not just developer complaints), improving health from 4.0 → 8.0:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For a 10-person team:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;55% faster feature delivery&lt;/strong&gt; = $550k saved annually &lt;em&gt;(enough for a really nice coffee machine)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;93% fewer production bugs&lt;/strong&gt; = Fewer 3 AM phone calls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;89% more predictable timelines&lt;/strong&gt; = Your estimates might actually be... accurate? &lt;em&gt;gasps&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Drop-In Integration (Easier Than Your Ex)
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install (like npm install, but for grown-ups)&lt;/span&gt;
gem &lt;span class="nb"&gt;install &lt;/span&gt;rails_code_health

&lt;span class="c"&gt;# Analyze (brace yourself)&lt;/span&gt;
rails-health

&lt;span class="c"&gt;# CI Integration (because humans can't be trusted)&lt;/span&gt;
rails-health &lt;span class="nt"&gt;--format&lt;/span&gt; json &lt;span class="nt"&gt;--output&lt;/span&gt; report.json
&lt;span class="c"&gt;# Fail build if health drops below 6.0 (harsh but fair)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;GitHub Actions Example:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Code Health Reality Check&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;rails-health --format json | \&lt;/span&gt;
    &lt;span class="s"&gt;ruby -e "exit 1 if JSON.parse(STDIN.read)['average_score'] &amp;lt; 6.0"&lt;/span&gt;
    &lt;span class="s"&gt;echo "Your code has been judged... and found wanting"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Beyond Just Metrics (It's Got Personality!)
&lt;/h2&gt;

&lt;p&gt;This isn't another linter that screams about semicolons like a grammar Nazi. It's a &lt;strong&gt;business intelligence tool&lt;/strong&gt; that:&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Predicts delivery delays&lt;/strong&gt; before your PM has to explain them to stakeholders&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Prioritizes refactoring&lt;/strong&gt; with data instead of "this code gives me anxiety"&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Connects code quality to money&lt;/strong&gt; (finally, a language executives understand!)&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Builds quality culture&lt;/strong&gt; without the usual developer smugness  &lt;/p&gt;

&lt;h2&gt;
  
  
  Real Teams, Real Results (Not Fake Testimonials)
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;"We identified our 3 biggest bottlenecks in 5 minutes. Our velocity increased 40% after addressing them. Also, I can sleep again."&lt;/em&gt; - Senior Rails Developer Who Used To Cry&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Finally, concrete numbers to justify refactoring time to management. No more 'trust me, this code is bad' arguments."&lt;/em&gt; - Engineering Manager (Reformed Excel Enthusiast)&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"The health score became our team's most watched metric. We have a leaderboard now. It's gotten competitive."&lt;/em&gt; - Tech Lead (Slightly Concerning Competitive)&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters Now (Spoiler: Everything Is On Fire)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Developer shortage + 42% wasted time + infinite technical debt = 🔥🔥🔥&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can't hire your way out of technical debt. Trust us, we've tried. But you can &lt;strong&gt;measure it, shame it into submission, and eliminate it&lt;/strong&gt; with the power of science and mild public embarrassment.&lt;/p&gt;

&lt;p&gt;Rails Code Health gives you the ammunition to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Stop playing technical debt whack-a-mole&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Justify refactoring&lt;/strong&gt; without sounding like a perfectionist
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prevent future disasters&lt;/strong&gt; before they require therapy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ship faster&lt;/strong&gt; without sacrificing your sanity (or your weekend)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Common Developer Reactions
&lt;/h2&gt;

&lt;p&gt;😱 &lt;strong&gt;"Oh no, my code has a score?"&lt;/strong&gt; - Yes, and it's probably lower than you think&lt;br&gt;
🤔 &lt;strong&gt;"But my code works fine!"&lt;/strong&gt; - So does a 1994 Honda Civic, doesn't mean you should drive it to a client meeting&lt;br&gt;
😤 &lt;strong&gt;"This is just opinion!"&lt;/strong&gt; - It's literally peer-reviewed research, Karen&lt;br&gt;
🥺 &lt;strong&gt;"Can I improve my score?"&lt;/strong&gt; - That's... that's the whole point&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started Today (Your Future Self Will Thank You)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gem &lt;span class="nb"&gt;install &lt;/span&gt;rails_code_health
&lt;span class="nb"&gt;cd &lt;/span&gt;your_rails_project
rails-health
&lt;span class="c"&gt;# Prepare for emotional damage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Join the support group&lt;/strong&gt; of developers building better Rails applications through measurable code quality and shared trauma.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Open source&lt;/strong&gt; • &lt;strong&gt;Research-backed&lt;/strong&gt; • &lt;strong&gt;Rails-optimized&lt;/strong&gt; • &lt;strong&gt;Therapist-approved&lt;/strong&gt;*&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/gkosmo/rails_code_health" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Because your code quality should be as measurable as your therapy bills.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;*Not actually approved by therapists, but several have expressed concern&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;PS: I do go to therapy to talk about my emotions and my life, and it is super beneficial. I have never managed to make my therapist laugh and it is not a goal ( just a side quest ). Do take therapy seriously   &lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>gem</category>
      <category>health</category>
    </item>
    <item>
      <title>Intro to Rsmolagent</title>
      <dc:creator>George Kosmopoulos</dc:creator>
      <pubDate>Tue, 11 Mar 2025 07:59:34 +0000</pubDate>
      <link>https://dev.to/gkosmo/intro-to-rsmolagent-1gf7</link>
      <guid>https://dev.to/gkosmo/intro-to-rsmolagent-1gf7</guid>
      <description>&lt;h2&gt;
  
  
  So I Made a Thing: RSmolagent for Ruby Devs Who Just Want Stuff to Work
&lt;/h2&gt;

&lt;p&gt;Hey folks! gkosmo here. I wanted to chat about this little library I put together called RSmolagent. It's basically just me wanting to have the cool AI agent stuff that Python devs have, but in Ruby, and without all the fuss.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I Built This Thing
&lt;/h2&gt;

&lt;p&gt;Look, I'm a Ruby dev. I like Ruby. It's elegant, it's fun to write, and it just makes me happy. But lately, all the cool AI agent frameworks are in Python. You've got LangChain (which is awesome but huge), and you've got smolagent (which is nice and simple).&lt;/p&gt;

&lt;p&gt;I kept thinking: "Why can't we have something like smolagent but for Ruby?" I didn't want to switch to Python just to build AI agents, and I definitely didn't want to deal with some complex Ruby-Python bridge setup that would break in weird ways.&lt;/p&gt;

&lt;p&gt;So I did what any slightly stubborn developer would do - I built my own version.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's an "Agent" Anyway?
&lt;/h2&gt;

&lt;p&gt;Before I dive in, let's talk about what an "agent" is in this context. It's pretty simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You've got an LLM (like GPT-4)&lt;/li&gt;
&lt;li&gt;You give it access to some tools (like web search, calculators, APIs)&lt;/li&gt;
&lt;li&gt;It figures out which tools to use to complete a task&lt;/li&gt;
&lt;li&gt;It uses the results from those tools to give you a final answer&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. Nothing magical. The LLM is the brain, the tools are the hands, and RSmolagent is the nervous system connecting them.&lt;/p&gt;

&lt;h2&gt;
  
  
  How RSmolagent Works
&lt;/h2&gt;

&lt;p&gt;The core idea is dead simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# You create an LLM provider&lt;/span&gt;
&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RSmolagent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OpenAIProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;model_id: &lt;/span&gt;&lt;span class="s2"&gt;"gpt-4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;client: &lt;/span&gt;&lt;span class="n"&gt;your_openai_client&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# You create some tools&lt;/span&gt;
&lt;span class="n"&gt;web_search&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RSmolagent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Tools&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WebSearchTool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;calculator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;MyCalculatorTool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

&lt;span class="c1"&gt;# You create an agent with those tools&lt;/span&gt;
&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RSmolagent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;llm_provider: &lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;tools: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;web_search&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;calculator&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# You run the agent with a task&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"What's the population of Tokyo multiplied by 3?"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind the scenes, the agent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Asks the LLM what to do&lt;/li&gt;
&lt;li&gt;The LLM says "I should search for Tokyo's population, then multiply it by 3"&lt;/li&gt;
&lt;li&gt;The agent executes the web search tool&lt;/li&gt;
&lt;li&gt;The agent sends the results back to the LLM&lt;/li&gt;
&lt;li&gt;The LLM says "now I need to calculate this"&lt;/li&gt;
&lt;li&gt;The agent runs the calculator tool&lt;/li&gt;
&lt;li&gt;The LLM gets the final answer&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It's a loop, but a pretty straightforward one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making Your Own Tools
&lt;/h2&gt;

&lt;p&gt;The real fun comes when you make your own tools. Here's a dead simple example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DadJokeTool&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;RSmolagent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Tool&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="ss"&gt;name: &lt;/span&gt;&lt;span class="s2"&gt;"dad_joke"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;description: &lt;/span&gt;&lt;span class="s2"&gt;"Get a random dad joke"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;input_schema: &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt; &lt;span class="c1"&gt;# No inputs needed&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;jokes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"Why don't scientists trust atoms? Because they make up everything!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"I told my wife she was drawing her eyebrows too high. She looked surprised."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"What do you call a fake noodle? An impasta!"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;jokes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sample&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your agent can tell dad jokes. Revolutionary, I know.&lt;/p&gt;

&lt;p&gt;But seriously, you can build tools that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Query your database&lt;/li&gt;
&lt;li&gt;Call your company's internal APIs&lt;/li&gt;
&lt;li&gt;Control IoT devices&lt;/li&gt;
&lt;li&gt;Generate images&lt;/li&gt;
&lt;li&gt;Whatever else you can code in Ruby&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Code Is Stupidly Simple (By Design)
&lt;/h2&gt;

&lt;p&gt;I intentionally kept the codebase tiny. There are basically four main parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;Agent&lt;/code&gt; - Manages the conversation loop&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Tool&lt;/code&gt; - Base class for all tools&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;LLMProvider&lt;/code&gt; - Talks to language models&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Memory&lt;/code&gt; - Keeps track of conversation history&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. The entire thing is a few hundred lines of code. You could read the entire library in 15 minutes.&lt;/p&gt;

&lt;p&gt;Why so simple? Because I wanted something I could understand entirely, modify easily, and not worry about mysterious bugs in some deep dependency chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Real Example
&lt;/h2&gt;

&lt;p&gt;Let's build something slightly more useful - a research assistant that can search the web and summarize what it finds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rsmolagent'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'ruby/openai'&lt;/span&gt;

&lt;span class="c1"&gt;# Set up OpenAI client&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;OpenAI&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;access_token: &lt;/span&gt;&lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"OPENAI_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; 
&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RSmolagent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OpenAIProvider&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;model_id: &lt;/span&gt;&lt;span class="s2"&gt;"gpt-3.5-turbo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;client: &lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Create web search tool&lt;/span&gt;
&lt;span class="n"&gt;web_search&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RSmolagent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Tools&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;WebSearchTool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

&lt;span class="c1"&gt;# Create the agent&lt;/span&gt;
&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RSmolagent&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;llm_provider: &lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;tools: &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;web_search&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="ss"&gt;max_steps: &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Ask a research question&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"What do you want to research?"&lt;/span&gt;
&lt;span class="n"&gt;question&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;gets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chomp&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Researching... (this might take a bit)"&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Here's what I found:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just like that, you've got a research assistant. No complex setup, no weird Python dependencies, just plain Ruby.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's Not Perfect (And That's Okay)
&lt;/h2&gt;

&lt;p&gt;Look, RSmolagent isn't trying to compete with LangChain or even smolagent in terms of features. It's a minimal implementation that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Works with Ruby&lt;/li&gt;
&lt;li&gt;Is easy to understand&lt;/li&gt;
&lt;li&gt;Gets the job done&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It might not have every bell and whistle, but it's a solid foundation you can build on. And since the code is so simple, extending it is straightforward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Give It a Try
&lt;/h2&gt;

&lt;p&gt;If you're a Ruby dev curious about AI agents but don't want to learn a whole new language or framework, give RSmolagent a shot. It's open source, it's simple, and it works.&lt;/p&gt;

&lt;p&gt;Clone it, hack on it, build something cool with it. And if you improve it, send a PR! This is just the beginning.&lt;/p&gt;

&lt;p&gt;Because at the end of the day, programming should be fun. And nothing's more fun than building a little AI assistant that can actually do useful stuff.&lt;/p&gt;

&lt;p&gt;Catch you later,&lt;br&gt;
gkosmo&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>agents</category>
      <category>ai</category>
      <category>llm</category>
    </item>
  </channel>
</rss>
