Uh oh!
There was an error while loading. Please reload this page.
Extract the agent configuration records into SolidAgent::Records - #6
Merged
Conversation
Groundwork for moving the agent / version / template / run models out of
activeagent's dashboard engine and into solid_agent. Two prerequisites, both
verifiable on their own.
A real database for record tests. test/test_helper.rb mocks ActiveRecord::Base
so the unit suite runs with no database and no Rails, which is worth keeping —
but it means an ActiveRecord concern tested under it only tests the mock. The
record concerns this refactor introduces are validations, enums, callbacks,
scopes and associations, so test/records/ boots a real ActiveRecord on sqlite
:memory: with the schema the generator emits. It needs its own process for the
same reason test/integration/ does: the unit harness defines a mock Rails
constant, and ActiveAgent requires its railtie whenever Rails is defined.
Postgres-only behavior (jsonb containment, partial unique indexes) is out of
scope here; sqlite is the portable floor, not the target.
Fix the model-name configuration seam. SolidAgent.context_class,
message_class and generation_class had no consumers, while the shipped
initializer template told hosts to set them — so uncommenting it did nothing.
HasContext#infer_class_names hardcoded the three names; it now reads the
config, which is what makes the documented knob real.
Fix sibling-name derivation. The chained
delete_suffix("Context").delete_suffix("Session") reduced "SessionContext" to
"", yielding a bare "Message"/"Generation" pair that collides across every
context in an app. Extracted to SolidAgent::ModelNaming, which strips at most
one suffix and is now the single place that knows the rule — the context
generator derives the same names independently today, which is how they drifted.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RbmULyN7NyPwv62s7eWRmDThe platform app has run Agent, AgentVersion, AgentTemplate and AgentRun as host-owned models for a while, and activeagent's dashboard ships an older, poorer copy of the same four with no controllers or routes pointing at them. docs/framework/v2-extraction-roadmap.md in activeagent tracks folding the platform's drifted copies back onto solid_agent's templates; this is that. The gem ships behavior only. Agent, AgentVersion, AgentTemplate, AgentRun and Ownable are ActiveSupport::Concerns under SolidAgent::Records; the model classes stay host-owned in app/models. Three reasons the models are not gem-owned: - activeagent's dashboard cannot depend on solid_agent, because solid_agent already depends on activeagent and the reverse edge is a cycle. Naming the models with configurable strings and resolving them at call time is what lets the dashboard and a plain host app read the same tables with neither gem requiring the other. - Engine-namespacing them as SolidAgent::Agent would make isolate_namespace resolve the table to solid_agent_agents, and would invalidate the contextable_type: "Agent" strings already in production agent_contexts rows. - Agent configuration is what applications most want to extend. A host-owned model can be edited; a gem-owned one can only be monkey-patched. Every cross-model reference goes through SolidAgent.agent_class and its siblings, resolved with safe_constantize at call time — never at load, which would deadlock the Rails autoloader or pin a stale class across a reload. SolidAgent.records_installed? answers false both when the constant is missing and when the migration has not run, so consumers can degrade rather than fail late. SolidAgent.run_executor is the seam for actually running an agent: building a class from stored provider/model/instructions is execution, which belongs to activeagent and the host, so the gem defines the contract and its default raises with instructions rather than returning mock data the way the dashboard's stub does. What deliberately did not come across, because a persistence gem should not carry it: the 135 lines of seed_defaults! marketing copy with model IDs pinned to gpt-4o and claude-sonnet-4; the merchandising columns and scopes (featured, free_tier, public, usage_count, by_category, popular) — a gem whose default schema asserts a pricing model is wrong, so template usage emits a notification and a dashboard counts; the closed vocabularies PRESET_TYPES, INSTRUCTION_SETS, AVAILABLE_TOOLS and PROVIDERS, defined by a React component that does not ship in this repo and already drifted once; to_agent_class_code, since code generation is not persistence; and ActionCable broadcasting, which becomes a notification a dashboard subscribes to. preset_type and appearance are the deliberate exception. They are cosmetic, but they already live inside configuration_snapshot in production agent_versions rows, so dropping them would make existing version rows lossy on restore. Tests run against a real ActiveRecord on sqlite :memory: in test/records/, which needs its own process for the same reason test/integration/ does. Along the way the unit harness lost its hand-rolled ActiveSupport shims: its String inflections were defined after require "solid_agent" and had been shadowing ActiveSupport's, so the suite was exercising a toy camelize while production ran the real one. The gem now requires the ActiveSupport pieces it calls, so it loads from a plain Ruby process. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RbmULyN7NyPwv62s7eWRmD
⏳ Superconductor is working — View implementation I'll get back to you soon! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Folds the ActiveAgents platform's drifted model copies back onto the gem, as tracked in activeagent's
docs/framework/v2-extraction-roadmap.md:The platform app has run
Agent,AgentVersion,AgentTemplateandAgentRunas host-owned models for a while. activeagent's dashboard ships an older, poorer copy of the same four — which the roadmap already flags as orphaned: "ships orphaned platform-shaped models with no controllers or routes. Decide: wire them or drop them from the gem."The shape
The gem ships behavior only.
Agent,AgentVersion,AgentTemplate,AgentRunandOwnableareActiveSupport::Concerns underSolidAgent::Records; the model classes stay host-owned inapp/models.Three reasons the models are not gem-owned:
SolidAgent::Agentwould break production data.isolate_namespacewould resolve the table tosolid_agent_agents, and would invalidate thecontextable_type: "Agent"strings already in productionagent_contextsrows.Every cross-model reference goes through
SolidAgent.agent_classand its siblings, resolved withsafe_constantizeat call time — never at load, which would deadlock the Rails autoloader or pin a stale class across a reload.records_installed?answers false in both failure modes so a consumer that must degrade — the dashboard being the motivating one — can check once instead of failing late.run_executoris the seam for actually running an agent. Building a class from stored provider/model/instructions is execution, which belongs to activeagent and the host, so the gem defines the contract and its default raises with instructions rather than returning mock data the way the dashboard's stub does (agent.rb,build_and_execute_agent, a TODO returning"Mock response for: ...").What deliberately did not come across
A persistence gem should not carry:
seed_defaults!— 135 lines of marketing copy and emoji with model IDs pinned togpt-4oandclaude-sonnet-4-20250514. A gem shipping that has its release cadence set by vendor deprecations.featured,free_tier,public,usage_count,by_category,popular. A gem whose default schema asserts a pricing model on every consumer is wrong. Template usage now emitstemplate.used.solid_agentand a dashboard counts.PRESET_TYPES,INSTRUCTION_SETS,AVAILABLE_TOOLS,PROVIDERS. No inclusion validation uses them, they are defined by a React component that does not ship in this repo, andPROVIDERShas already drifted once (Requesty).to_agent_class_code— code generation is not persistence.preset_typeandappearanceare the deliberate exception. They are cosmetic, but they already live insideconfiguration_snapshotin productionagent_versionsrows, so dropping them would make existing version rows lossy on restore.Decisions I made — please sanity-check these
:userwith aclass_attributeescape hatch, matching production. Polymorphic would force the platform to migrate a liveuser_idcarrying three indexes for no present benefit.agent_runsadopts the platform's representation (integer status enum,agent_id). solid_agent's polymorphicrunnableandinstructions_digestsurvive as additions, and the events column name is aclass_attributeso neither side renames a live column.subjectreturnsrunnable || agent.AgentVersion#diffwas fixed, not ported. The original iterates only its own snapshot keys, so a key removed in the newer version was silently missed.modelattribute from ingest, which would otherwise churn a version per trace.with_toolis guarded. It uses Postgres containment and raises on every other adapter today; now it uses containment on Postgres with a portable fallback elsewhere.Verification
require "solid_agent"in a process with no ActiveRecord and no Rails loads clean — verified. The concerns are required eagerly, which is safe because nothing in them touches an ActiveRecord API until a host model includes one.ActiveAgent::Dashboardreference anywhere inlib/.constantizesits inside adefine_methodblock, so nothing resolves at load time.Also fixed along the way:
SolidAgent.context_class/message_class/generation_classhad zero consumers while the shipped initializer told hosts to set them, so uncommenting it did nothing. And the chaineddelete_suffix("Context").delete_suffix("Session")reducedSessionContextto"", yielding a bareMessage/Generationpair that collided across every context in an app.The unit harness also lost its hand-rolled String inflections — they were defined after
require "solid_agent"and had been shadowing ActiveSupport's, so the suite was exercising a toycamelizewhile production ran the real one.Not in this PR
The
solid_agent:agentsgenerator (migrations + model templates) and thesolid_agent:upgradepath for apps that already own anagent_runstable. The concerns and their schema are the reviewable unit; wiring the generator on top is mechanical once this shape is agreed.Generated by Claude Code