Module: SolidAgent::Records::AgentVersion

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

Overview

Version history for an agent's configuration.

Every time an agent's versioned attributes change, the host writes a new row holding a full snapshot of the configuration at that moment. Snapshots are whole, not deltas: a version has to be restorable on its own, long after the rows around it were pruned, and diffing is cheap enough to do in Ruby.

The agent association is named with the configured class string (SolidAgent.agent_class) rather than a constant, so the host can point the records layer at Ai::Assistant without the gem ever touching an autoloadable constant during load.

Nothing here uses jsonb operators, so the concern works the same on Postgres, MySQL and sqlite; comparison happens in Ruby.

Examples:

Diffing two versions

v2.diff(v1)
#=> { "model" => { from: "gpt-4o-mini", to: "gpt-4o" },
#     "tools" => { from: nil, to: ["search"] } }

Walking the history

version.previous      #=> the next-lower version, or nil at v1
version.next_version  #=> the next-higher version, or nil at the tip

Instance Method Summary collapse

Instance Method Details

#diff(other_version) ⇒ Hash{String => Hash}

Compares this version's snapshot against another's.

The result is keyed by configuration key and reads from the other version to this one, so newer.diff(older) describes what the newer version changed.

Both key sets are unioned, which means a key dropped in this version is reported as { from: <old value>, to: nil } rather than silently skipped. Keys are compared as strings, because a snapshot built in memory carries symbol keys while one loaded from a json column carries strings, and the two must not read as a wholesale rewrite.

Examples:

A removed key

v1.update!(configuration_snapshot: { "model" => "gpt-4o", "tools" => ["search"] })
v2.update!(configuration_snapshot: { "model" => "gpt-4o" })
v2.diff(v1) #=> { "tools" => { from: ["search"], to: nil } }

Parameters:

  • other_version (#configuration_snapshot, nil)

Returns:

  • (Hash{String => Hash})

    changed keys to { from:, to: }



73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/solid_agent/records/agent_version.rb', line 73

def diff(other_version)
  return {} unless other_version

  mine = normalized_snapshot(configuration_snapshot)
  theirs = normalized_snapshot(other_version.configuration_snapshot)

  (mine.keys | theirs.keys).each_with_object({}) do |key, changes|
    before = theirs[key]
    after = mine[key]
    changes[key] = { from: before, to: after } unless before == after
  end
end

#initial?Boolean

Whether this is version 1.

Deliberately a property of the numbering, not of the surviving rows — after old versions are pruned the oldest remaining row is not the initial configuration and should not claim to be.

Returns:

  • (Boolean)


120
121
122
# File 'lib/solid_agent/records/agent_version.rb', line 120

def initial?
  version_number == 1
end

#latest?Boolean

Whether no higher-numbered version exists for the same agent.

Answered from the version table alone rather than by asking the agent for its latest version: the gem owns no part of the host's Agent model and must not require it to expose a latest_version reader.

Returns:

  • (Boolean)


107
108
109
110
111
# File 'lib/solid_agent/records/agent_version.rb', line 107

def latest?
  return false if version_number.nil?

  !sibling_versions.where("version_number > ?", version_number).exists?
end

#next_versionActiveRecord::Base?

The nearest version above this one, or nil when this is the tip.

Returns:

  • (ActiveRecord::Base, nil)


96
97
98
# File 'lib/solid_agent/records/agent_version.rb', line 96

def next_version
  sibling_versions.where("version_number > ?", version_number).order(version_number: :asc).first
end

#previousActiveRecord::Base?

The nearest version below this one, or nil when this is the first.

Returns:

  • (ActiveRecord::Base, nil)


89
90
91
# File 'lib/solid_agent/records/agent_version.rb', line 89

def previous
  sibling_versions.where("version_number < ?", version_number).order(version_number: :desc).first
end