Module: SolidAgent::Records::AgentTemplate

Extended by:
ActiveSupport::Concern
Defined in:
lib/solid_agent/records/agent_template.rb

Overview

Behavior for the AgentTemplate record: a named, reusable agent configuration that new agents are stamped out of.

A template holds the same configuration columns an agent holds — provider, model, instructions, tools, and so on — and knows how to copy them onto a fresh agent record for an owner. It is a prototype, not a parent: the agent gets a snapshot, and later edits to the template never reach agents already created from it.

What this concern deliberately leaves to the host:

  • Catalog copy. Seeded template libraries pin provider model IDs ("gpt-4o", "claude-sonnet-4-20250514"). Shipping those from a gem would hand its release cadence to vendor deprecation schedules, so seeds stay in the application that curates them.
  • Merchandising. Category, featured, popularity, public/free-tier — and the usage counter that ranks them — describe how one product sells templates. A persistence gem whose schema asserts a pricing model is wrong. Every reference here to such a column is guarded with has_attribute? so hosts that add them still work.

In place of a usage counter, #create_agent_for instruments USED_EVENT. Counting is a subscriber's job, which lets a dashboard increment a column, a metrics backend emit a gauge, and a plain host app do nothing at all — from the same code path.

Examples:

Stamping an agent out of a template

template = AgentTemplate.find_by!(slug: "code-assistant")
agent = template.create_agent_for(current_user, name: "My Reviewer")
agent.persisted? #=> true

Counting template usage from the host

ActiveSupport::Notifications.subscribe("template.used.solid_agent") do |*, payload|
  payload[:template].increment!(:usage_count)
end

Constant Summary collapse

USED_EVENT =

Emitted after an agent is successfully created from a template. Payload: :template, :agent, :owner, and :category when the host has added that column.

"template.used.solid_agent"
CONFIGURATION_ATTRIBUTES =

Columns copied verbatim onto the new agent. Identity (name, slug), ownership and lifecycle are handled separately — those are the agent's own, not the template's.

%w[
  description
  provider
  model
  instructions
  preset_type
  appearance
  instruction_sets
  tools
  mcp_servers
  model_config
].freeze

Instance Method Summary collapse

Instance Method Details

#create_agent_for(owner, name: nil) ⇒ Object

Builds and saves an agent for owner from this template.

Returns the agent whether or not it saved — unsaved with errors populated, the way create does — because callers render those errors. USED_EVENT fires only on a successful save.

Examples:

Overriding just the name

template.create_agent_for(, name: "Release Notes Writer")

Parameters:

  • owner (Object)

    the record the agent belongs to (user, account, …)

  • name (String, nil) (defaults to: nil)

    overrides the template's name

Returns:

  • (Object)

    an instance of SolidAgent.agent_model

Raises:



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/solid_agent/records/agent_template.rb', line 98

def create_agent_for(owner, name: nil)
  model = SolidAgent.agent_model!

  # Intersect with the agent's columns rather than assuming the two
  # schemas match: template and agent drift independently once a host
  # starts editing generated migrations.
  attributes = template_configuration.slice(*model.column_names)
  attributes["name"] = name.presence || self.name
  attributes["status"] = :draft if draft_status?(model)

  agent = model.new(attributes)
  # Ownable maps `owner` onto whichever column the host actually has.
  # A single-tenant install may have none, and creating an unowned agent
  # is a legitimate outcome there, not an error worth raising.
  agent.owner = owner if agent.respond_to?(:owner=)

  if agent.save
    ActiveSupport::Notifications.instrument(USED_EVENT, used_event_payload(agent, owner))
  end

  agent
end

#template_configurationHash{String => Object}

The configuration this template stamps onto an agent.

Only attributes the template actually has are included, so a host that trims columns it does not use — or adds them later — needs no change here.

Returns:

  • (Hash{String => Object})


79
80
81
82
83
# File 'lib/solid_agent/records/agent_template.rb', line 79

def template_configuration
  CONFIGURATION_ATTRIBUTES.each_with_object({}) do |attribute, config|
    config[attribute] = self[attribute] if has_attribute?(attribute)
  end
end