About

This post documents how to build a platform of agents that can be specialized and associated with a user. This pattern or technique is powerful because a user can have their own personal agents, and those agents can be specialized in different domains, such as day trading, investing, and so on.

The idea is simple but empowering: instead of treating agents as something outside the application, we can treat them as ordinary models with state, tools, skills, and associations.

An optimized version of this pattern might use polymorphism, but the intent of this post is to communicate the idea and not a perfect implementation. The pattern is enabled by and really made possible thanks to llm.rb and in fact it drives the design of the runtime's agent system.

Migration

The first step is to define a user table, and then two tables for our specialized agents. In this example the user has many agents, but we keep the schema simple by storing each concrete agent type in its own table.

Each agent belongs to a user through user_id, and each agent table stores the serialized agent state used by llm.rb:

class CreatePlatformOfAgents < ActiveRecord::Migration[8.0]
  def change
    create_table :users do |t|
      t.string :email, null: false
      t.timestamps
    end

    add_index :users, :email, unique: true

    create_table :daytraders do |t|
      t.references :user, null: false, foreign_key: true
      t.string :provider, null: false
      t.string :model
      t.text :data
      t.integer :input_tokens
      t.integer :output_tokens
      t.integer :total_tokens
      t.timestamps
    end

    create_table :stock_investors do |t|
      t.references :user, null: false, foreign_key: true
      t.string :provider, null: false
      t.string :model
      t.text :data
      t.integer :input_tokens
      t.integer :output_tokens
      t.integer :total_tokens
      t.timestamps
    end
  end
end

Explanation

Daytrader

The first agent in our platform is a day trader. The idea is simple: this agent can trade on behalf of a user.

That is the part I want to emphasize. This is just an ActiveRecord model, but it is also an agent with real capabilities. It can belong to a user, it can fit into the rest of the application's data model, and it can still have tools and skills that let it take meaningful action. In other words, the agent does not have to live in some separate service or outside runtime. It can live right alongside the rest of the models that already describe the application.

require "llm"
require "net/http/persistent"
require "active_record"
require "llm/active_record"

class User < ApplicationRecord
  has_many :daytraders, dependent: :destroy
  has_many :stock_investors, dependent: :destroy

  def agents
    [*daytraders, *stock_investors]
  end
end

class Daytrader < ApplicationRecord
  acts_as_agent provider: :set_provider
  belongs_to :user

  model "gpt-5.4-mini"
  instructions "You are a day trader. You can watch the market and place trades for the user."
  tools QuoteFeed, OrderEntry, PositionBook
  skills "skills/daytrader/find_trade",
         "skills/daytrader/enter_trade",
         "skills/daytrader/exit_trade"
  concurrency :thread

  private

  def set_provider
    {key: ENV["#{provider.upcase}_SECRET"], persistent: true}
  end
end

Explanation

Stock Investor

The second agent in our platform is a stock investor. The idea here is just as simple: this agent can invest on behalf of a user.

The same pattern applies here too. This is still just an ActiveRecord model, but now it represents a different kind of agent with a different job. It can still belong to a user, still participate in the existing associations of the application, and still be given tools and skills that let it do real work. Once that idea clicks, it becomes easier to see how almost any model in an application could be turned into an agent when that makes sense for the domain.

require "llm"
require "net/http/persistent"
require "active_record"
require "llm/active_record"

class StockInvestor < ApplicationRecord
  acts_as_agent provider: :set_provider
  belongs_to :user

  model "gpt-5.4-mini"
  instructions "You are a stock investor. You can review companies and invest for the user over time."
  tools FundamentalsFeed, PortfolioBook, OrderEntry
  skills "skills/stock_investor/find_investment",
         "skills/stock_investor/start_position",
         "skills/stock_investor/rebalance"
  concurrency :thread

  private

  def set_provider
    {key: ENV["#{provider.upcase}_SECRET"], persistent: true}
  end
end

Explanation

Conclusion

The interesting part of this pattern is that the agents can be modeled as part of the application itself. A user can have their own agents, those agents can be specialized by domain, and their behavior can be composed with tools and skills.

That makes it easier to build multi-agent systems, personalize behavior per user, and keep the design close to the rest of the domain model. With llm.rb, the runtime pieces stay small enough that the main work becomes application design.