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
:users
Stores the owner of the agents. In this example a user can have many agents.:daytradersand:stock_investors
These are two different kinds of agents, each stored in its own table.:user_id
Associates each agent with a user.:provider,:model, and:data
These are the main columns used by llm.rb's ActiveRecord persistence layer.:input_tokens,:output_tokens, and:total_tokens
Keeps token usage on the record.:timestamps
Gives us the usual ActiveRecord created and updated timestamps.
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
has_many :daytradersandhas_many :stock_investors
Gives the user a normal association to each specialized agent type.def agents
Provides one place where both kinds of agents can be treated as a single collection.belongs_to :user
Associates each day trader agent with its owner.tools QuoteFeed, OrderEntry, PositionBook
These are the local tools the agent can call to check prices, place trades, and inspect positions.skills "skills/daytrader/..."
These are reusable trading behaviors that can be shared across day trader agents.concurrency :thread
Lets the agent run tool work with threads.
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
belongs_to :user
Associates each stock investor agent with its owner.tools FundamentalsFeed, PortfolioBook, OrderEntry
These are the local tools the agent can call to review companies, inspect the portfolio, and place orders.skills "skills/stock_investor/..."
These are reusable investing behaviors that can be shared across stock investor agents.concurrency :thread
Lets the agent run tool work with threads.
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.