solid_agent 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +68 -0
  3. data/LICENSE +21 -0
  4. data/README.md +209 -18
  5. data/Rakefile +22 -2
  6. data/docs/agent-md-spec.md +803 -0
  7. data/docs/parser-design.md +1369 -0
  8. data/docs/registry-api.md +882 -0
  9. data/examples/README.md +60 -0
  10. data/examples/manifests/changelog_writer.agent.md +81 -0
  11. data/examples/manifests/usage.rb +96 -0
  12. data/examples/memory_handoff/app/agents/researcher_agent.rb +36 -0
  13. data/examples/memory_handoff/app/agents/writer_agent.rb +41 -0
  14. data/examples/memory_handoff/usage.rb +45 -0
  15. data/examples/persistent_conversation/app/agents/support_agent.rb +59 -0
  16. data/examples/persistent_conversation/app/controllers/support_conversations_controller.rb +24 -0
  17. data/examples/persistent_conversation/app/views/agents/support/instructions.md.erb +8 -0
  18. data/examples/persistent_conversation/usage.rb +51 -0
  19. data/examples/reasoning/app/agents/analysis_agent.rb +52 -0
  20. data/examples/reasoning/usage.rb +52 -0
  21. data/examples/run_tracking/app/agents/report_agent.rb +30 -0
  22. data/examples/run_tracking/app/controllers/agent_runs_controller.rb +43 -0
  23. data/examples/run_tracking/app/jobs/document_analysis_job.rb +17 -0
  24. data/examples/run_tracking/app/services/document_analysis_run.rb +68 -0
  25. data/examples/run_tracking/usage.rb +85 -0
  26. data/examples/tool_streaming/app/agents/browser_agent.rb +65 -0
  27. data/examples/tool_streaming/app/channels/tool_status_channel.rb +24 -0
  28. data/examples/tool_streaming/app/views/browser_agent/tools/fetch_url.json.erb +15 -0
  29. data/examples/tool_streaming/usage.rb +47 -0
  30. data/lib/generators/solid_agent/agent/agent_generator.rb +2 -2
  31. data/lib/generators/solid_agent/agent/templates/agent.rb.erb +3 -3
  32. data/lib/generators/solid_agent/context/templates/context_model.rb.erb +50 -16
  33. data/lib/generators/solid_agent/context/templates/create_generations.rb.erb +8 -0
  34. data/lib/generators/solid_agent/context/templates/create_messages.rb.erb +4 -0
  35. data/lib/generators/solid_agent/context/templates/generation_model.rb.erb +11 -0
  36. data/lib/generators/solid_agent/install/install_generator.rb +9 -0
  37. data/lib/generators/solid_agent/install/templates/agent_context.rb.erb +60 -17
  38. data/lib/generators/solid_agent/install/templates/agent_generation.rb.erb +23 -6
  39. data/lib/generators/solid_agent/install/templates/agent_memory.rb.erb +51 -0
  40. data/lib/generators/solid_agent/install/templates/agent_memory_entry.rb.erb +12 -0
  41. data/lib/generators/solid_agent/install/templates/agent_run.rb.erb +122 -0
  42. data/lib/generators/solid_agent/install/templates/create_agent_generations.rb.erb +13 -0
  43. data/lib/generators/solid_agent/install/templates/create_agent_memories.rb.erb +35 -0
  44. data/lib/generators/solid_agent/install/templates/create_agent_messages.rb.erb +5 -0
  45. data/lib/generators/solid_agent/install/templates/create_agent_runs.rb.erb +46 -0
  46. data/lib/generators/solid_agent/manifest/manifest_generator.rb +209 -0
  47. data/lib/generators/solid_agent/manifest/templates/agent.md.erb +39 -0
  48. data/lib/generators/solid_agent/manifest/templates/prompt.erb +13 -0
  49. data/lib/generators/solid_agent/reasons/reasons_generator.rb +83 -0
  50. data/lib/generators/solid_agent/reasons/templates/add_reasoning_columns.rb.erb +12 -0
  51. data/lib/solid_agent/agent_manifest/agent_builder.rb +323 -0
  52. data/lib/solid_agent/agent_manifest/errors.rb +26 -0
  53. data/lib/solid_agent/agent_manifest/exporter_registry.rb +117 -0
  54. data/lib/solid_agent/agent_manifest/exporters/agent_md_exporter.rb +115 -0
  55. data/lib/solid_agent/agent_manifest/exporters/base_exporter.rb +152 -0
  56. data/lib/solid_agent/agent_manifest/exporters/crewai_exporter.rb +125 -0
  57. data/lib/solid_agent/agent_manifest/exporters/dotprompt_exporter.rb +92 -0
  58. data/lib/solid_agent/agent_manifest/input_schema.rb +154 -0
  59. data/lib/solid_agent/agent_manifest/manifest.rb +306 -0
  60. data/lib/solid_agent/agent_manifest/parser_registry.rb +185 -0
  61. data/lib/solid_agent/agent_manifest/parsers/agent_md_parser.rb +87 -0
  62. data/lib/solid_agent/agent_manifest/parsers/base_parser.rb +223 -0
  63. data/lib/solid_agent/agent_manifest/parsers/crewai_parser.rb +201 -0
  64. data/lib/solid_agent/agent_manifest/parsers/dotprompt_parser.rb +122 -0
  65. data/lib/solid_agent/agent_manifest/parsers/github_prompt_parser.rb +143 -0
  66. data/lib/solid_agent/agent_manifest/picoschema.rb +254 -0
  67. data/lib/solid_agent/agent_manifest/registry/auth.rb +103 -0
  68. data/lib/solid_agent/agent_manifest/registry/client.rb +384 -0
  69. data/lib/solid_agent/agent_manifest/resource.rb +103 -0
  70. data/lib/solid_agent/agent_manifest/tool.rb +160 -0
  71. data/lib/solid_agent/agent_manifest/validator.rb +368 -0
  72. data/lib/solid_agent/agent_manifest.rb +381 -0
  73. data/lib/solid_agent/has_context.rb +251 -30
  74. data/lib/solid_agent/has_memory.rb +136 -0
  75. data/lib/solid_agent/has_reasons.rb +230 -0
  76. data/lib/solid_agent/model_naming.rb +42 -0
  77. data/lib/solid_agent/model_pricing.rb +93 -0
  78. data/lib/solid_agent/reasonable/reason.rb +205 -0
  79. data/lib/solid_agent/reasonable.rb +181 -0
  80. data/lib/solid_agent/records/agent.rb +520 -0
  81. data/lib/solid_agent/records/agent_run.rb +520 -0
  82. data/lib/solid_agent/records/agent_template.rb +142 -0
  83. data/lib/solid_agent/records/agent_version.rb +141 -0
  84. data/lib/solid_agent/records/ownable.rb +130 -0
  85. data/lib/solid_agent/records.rb +152 -0
  86. data/lib/solid_agent/run_fingerprint.rb +51 -0
  87. data/lib/solid_agent/tool_cache.rb +91 -0
  88. data/lib/solid_agent/version.rb +1 -1
  89. data/lib/solid_agent.rb +70 -3
  90. metadata +87 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52f173d6c45fc416105fb4a93760dc7aacc81061b75bada9820fce835e9652dd
4
- data.tar.gz: '07348df42b87220dea84691d61a11cac6e6576cebbcbe2272c702a0bed6f539c'
3
+ metadata.gz: 2b204dd708358a41a24ed81cb3e27a76c83b97bd6b69fb098eeaf9a3c54d4ddd
4
+ data.tar.gz: 3ee800cb2936615ee7ac57072da8a26c6152bf73e92088c8853a8cbcdd3b4a6b
5
5
  SHA512:
6
- metadata.gz: ffd6386b0d71a95df1ac198c9590bbf337bf7e1a37ccc19e17eb57afbbcf544260fe7e2e552ba8470d4091d636a5b7b074aa5e7745d9d17021af839704b81f46
7
- data.tar.gz: bcb89c355432829006b38157c727c271b4b63c86aa09a67d14611b16896f515406dfbf5d84f45c1b2b53a9e4adae6d4cfa250397271f3bb0f8e029f15417d5b4
6
+ metadata.gz: 4a9d1c5133162f5ebd32fe1cf90ac999cd9b0f0f9c64396eb4e59ef9b60ca3d871e79c5d6e83fe50a830bfbbea6677837ff5ad0e0ca8e32277b885c577d63e9c
7
+ data.tar.gz: 6e3d799d6cb5321f10455d2a954cb24d2c41a0768c88489a86eb24f6bfe84f5b4ae25fffc614e60f0edfd05c517dc6dfef66c79c078e125d9ed275fa581e27a9
data/CHANGELOG.md ADDED
@@ -0,0 +1,68 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## Unreleased
9
+
10
+ ### Added
11
+
12
+ - **`SolidAgent::Records::*` — behavior for the agent-configuration records.**
13
+ `Agent`, `AgentVersion`, `AgentTemplate`, `AgentRun` and `Ownable` ship as
14
+ concerns; the model classes stay host-owned in `app/models`. Every
15
+ cross-model reference resolves through `SolidAgent.agent_class` and its
16
+ siblings at call time, so the gem never names `Agent` as a constant and never
17
+ constantizes during load. The concerns are required eagerly, which is safe
18
+ because nothing in them touches an ActiveRecord API until a host model
19
+ includes one — `require "solid_agent"` in a process with no ActiveRecord
20
+ defines the modules and loads nothing else.
21
+
22
+ This folds the ActiveAgents platform's drifted model copies back onto the
23
+ gem, as tracked in activeagent's `docs/framework/v2-extraction-roadmap.md`.
24
+
25
+ - **`SolidAgent.run_executor`** — the seam for executing an agent record.
26
+ Building an agent class from stored provider/model/instructions is
27
+ execution, which belongs to activeagent and the host, so the gem defines the
28
+ contract and the host fills it. The default raises with instructions rather
29
+ than returning mock data.
30
+
31
+ - **`SolidAgent.records_installed?`** — answers false both when the model
32
+ constant is missing and when its migration has not run, so a consumer that
33
+ must degrade (activeagent's dashboard being the motivating one) can check
34
+ once instead of failing late.
35
+
36
+ - **Record test harness** (`test/records/`, `rake test:records`) running
37
+ against a real ActiveRecord on sqlite `:memory:`. The unit harness mocks
38
+ `ActiveRecord::Base`, which would put a fake under the real one, so the two
39
+ cannot share a process. `rake` runs both.
40
+
41
+ - **`CHANGELOG.md`**, which the gemspec already advertised.
42
+
43
+ ### Fixed
44
+
45
+ - `SolidAgent.context_class`, `message_class` and `generation_class` had no
46
+ consumers while the shipped initializer template told hosts to set them, so
47
+ uncommenting it did nothing. `HasContext#infer_class_names` now reads them.
48
+
49
+ - Sibling class-name derivation chained
50
+ `delete_suffix("Context").delete_suffix("Session")`, reducing
51
+ `SessionContext` to `""` and yielding a bare `Message`/`Generation` pair that
52
+ collided across every context in an app. Extracted to
53
+ `SolidAgent::ModelNaming`, which strips at most one suffix and is now the
54
+ single place that knows the rule — the context generator derived the same
55
+ names independently, which is how they drifted.
56
+
57
+ - `require "solid_agent"` outside Rails raised `NameError` on
58
+ `ActiveSupport::Concern`; the gem assumed a host had already loaded
59
+ ActiveSupport for it. It now requires the pieces it calls. The unit
60
+ harness's hand-rolled String inflections went with it — they were defined
61
+ *after* `require "solid_agent"` and had been shadowing ActiveSupport's, so
62
+ the suite was exercising a toy `camelize` while production ran the real one.
63
+
64
+ ### Changed
65
+
66
+ - `activemodel` is now a declared dependency. `AgentManifest` has always been
67
+ an ActiveModel; it arrived transitively through `activerecord`, and a require
68
+ deserves a declaration.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Active Agents AI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -1,13 +1,41 @@
1
+ <p align="center">
2
+ <img src="assets/solid_agent.png" alt="SolidAgent" width="200">
3
+ </p>
4
+
1
5
  # SolidAgent
2
6
 
3
- SolidAgent extends the [ActiveAgent](https://github.com/activeagents/activeagent) framework with enterprise-grade features for building robust AI agents in Rails applications. It provides three core concerns that add database-backed persistence, declarative tool schemas, and real-time streaming capabilities to your agents.
7
+ [![Gem Version](https://img.shields.io/gem/v/solid_agent?logo=rubygems&color=CC342D)](https://rubygems.org/gems/solid_agent)
8
+ [![Downloads](https://img.shields.io/gem/dt/solid_agent?label=downloads)](https://rubygems.org/gems/solid_agent)
9
+ [![CI](https://github.com/activeagents/solid_agent/actions/workflows/ci.yml/badge.svg)](https://github.com/activeagents/solid_agent/actions/workflows/ci.yml)
10
+ [![Docs](https://img.shields.io/badge/docs-docs.activeagents.ai%2Fsolid__agent-2563eb)](https://docs.activeagents.ai/solid_agent)
11
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.0-CC342D)](https://www.ruby-lang.org)
12
+ [![ActiveAgent](https://img.shields.io/badge/activeagent-%3E%3D%201.0-D30001)](https://github.com/activeagents/activeagent)
13
+ [![License](https://img.shields.io/github/license/activeagents/solid_agent)](LICENSE)
14
+
15
+ SolidAgent extends the [ActiveAgent](https://github.com/activeagents/activeagent) framework with database-backed persistence for everything an agent does in a Rails application: conversations, generations, tool/MCP interactions, reasoning, and long-term memory.
16
+
17
+ **[Documentation](https://docs.activeagents.ai/solid_agent)** ·
18
+ **[Examples](examples)** ·
19
+ **[`.agent.md` spec](docs/agent-md-spec.md)**
4
20
 
5
21
  ## Features
6
22
 
7
- - **HasContext** - Database-backed prompt context management for maintaining conversation history and agent state
23
+ Agent-side concerns:
24
+
25
+ - **HasContext** - Database-backed prompt context management for maintaining conversation history and agent state, including the full tool/MCP interaction stream
26
+ - **HasMemory** - An agent-curated summary list the model reads/writes via `save_memory`/`recall_memory` function-calling tools; scoped to a subject record so agents hand off to each other through shared memory
8
27
  - **HasTools** - Declarative, schema-based tool definitions compatible with LLM function-calling APIs
28
+ - **HasReasons** - Capture and inspect extended-thinking/reasoning output across a generation
9
29
  - **StreamsToolUpdates** - Real-time UI feedback during tool execution via ActionCable
10
30
 
31
+ Model-side and standalone:
32
+
33
+ - **Reasonable** - Persist reasoning content/tokens/metadata on your generation records
34
+ - **AgentRun** - Durable run records (installed by the generator): lifecycle status, append-only progress events for live UIs, token/duration accounting, and instruction-fingerprint cohorts for comparing configuration changes
35
+ - **ToolCache** - Cache tool/MCP/service results by `(tool, normalized args)` with TTL, backed by `Rails.cache`; error results are never cached and replays are tagged `cached: true`
36
+ - **ModelPricing** - Token-count → estimated USD cost, using RubyLLM's model registry when available with a static pattern-table fallback
37
+ - **AgentManifest** - Load, validate, export, and build agent classes from portable manifests (`.agent.md`, dotprompt, CrewAI)
38
+
11
39
  ## Installation
12
40
 
13
41
  Add this line to your application's Gemfile:
@@ -22,20 +50,16 @@ And then execute:
22
50
  $ bundle install
23
51
  ```
24
52
 
25
- Or install it yourself as:
26
-
27
- ```bash
28
- $ gem install solid_agent
29
- ```
30
-
31
53
  ## Usage
32
54
 
33
55
  ### Quick Start
34
56
 
35
- Generate a new agent with context support:
57
+ Install the persistence tables and models (`AgentContext`, `AgentMessage`, `AgentGeneration`, `AgentMemory`, `AgentMemoryEntry`, `AgentRun`), then generate an agent with context support:
36
58
 
37
59
  ```bash
38
- $ rails generate solid_agent:agent WritingAssistant --context --context_name conversation --contextable user
60
+ $ rails generate solid_agent:install
61
+ $ rails db:migrate
62
+ $ rails generate solid_agent:agent WritingAssistant --context --context_name conversation --contextual user
39
63
  ```
40
64
 
41
65
  ### HasContext - Persistent Conversation History
@@ -46,12 +70,14 @@ Add database-backed context management to your agents:
46
70
  class WritingAssistantAgent < ApplicationAgent
47
71
  include SolidAgent::HasContext
48
72
 
49
- has_context :conversation, contextable: :user
73
+ has_context :conversation, class_name: "AgentContext", contextual: :user
50
74
 
51
75
  def improve
52
- load_conversation(contextable: current_user)
53
- add_conversation_user_message(params[:message])
54
- prompt messages: conversation_messages
76
+ load_conversation(contextable: params[:user]) # contextable is the polymorphic association
77
+
78
+ prompt messages: conversation_messages + [
79
+ { role: "user", content: params[:message] }
80
+ ]
55
81
  end
56
82
  end
57
83
  ```
@@ -63,6 +89,42 @@ This generates helper methods like:
63
89
  - `add_conversation_assistant_message(content)` - Add an AI response
64
90
  - `conversation_result` - Get the last assistant message
65
91
 
92
+ With `auto_save` on (the default), the last prompt message is persisted as
93
+ the user turn and the response as the assistant turn, both after the
94
+ provider call — so reach for `add_conversation_user_message` only with
95
+ `auto_save: false`, or the turn is stored twice.
96
+
97
+ > **Naming a context also names its models.** `has_context :conversation`
98
+ > infers `Conversation`, `ConversationMessage` and `ConversationGeneration`,
99
+ > not the `AgentContext` family the installer wrote — hence `class_name:`
100
+ > above, which infers `AgentMessage` and `AgentGeneration` alongside it.
101
+ > Unnamed `has_context` resolves to those models directly; for genuinely
102
+ > separate tables per context, run
103
+ > `rails generate solid_agent:context conversation`.
104
+
105
+ > **Note:** contexts are persisted under `self.class.name` — agents built
106
+ > with anonymous `Class.new(...)` must define a class name or context
107
+ > creation will fail the `agent_name` presence validation.
108
+
109
+ #### Telemetry trace correlation
110
+
111
+ Every persisted generation records a `trace_id` and a provenance snapshot
112
+ (agent/prompt/context checksums). Thread a distributed trace id — for
113
+ example an `ActiveAgent::Telemetry` trace — through prompt options and it
114
+ lands on the `agent_generations` row, joining conversation records to
115
+ telemetry traces:
116
+
117
+ ```ruby
118
+ def improve
119
+ prompt_options[:trace_id] = my_telemetry_trace_id
120
+ load_conversation(contextable: current_user)
121
+ prompt messages: conversation_messages
122
+ end
123
+ ```
124
+
125
+ Query with `AgentGeneration.with_trace(trace_id)` or
126
+ `AgentContext.with_trace(trace_id)`.
127
+
66
128
  ### HasTools - Declarative Tool Schemas
67
129
 
68
130
  Define tools inline with a clean DSL:
@@ -103,28 +165,157 @@ class BrowserAgent < ApplicationAgent
103
165
  end
104
166
  ```
105
167
 
168
+ ### HasMemory - Agent-Curated Long-Term Memory
169
+
170
+ Give an agent a durable summary list it decides when to read and write, scoped to a subject record rather than the agent class — so different agents operating on the same subject share memory, with `source_agent` provenance on every entry:
171
+
172
+ ```ruby
173
+ class SupportAgent < ApplicationAgent
174
+ include SolidAgent::HasContext
175
+ include SolidAgent::HasMemory
176
+
177
+ has_context contextual: :user
178
+ has_memory # scope: "default", class_name: "AgentMemory"
179
+
180
+ def assist
181
+ load_context(contextable: params[:user])
182
+ prompt messages: context_messages, tools: memory_tool_definitions
183
+ end
184
+ end
185
+ ```
186
+
187
+ The model calls `save_memory(content:, category:)` and `recall_memory(category:, limit:)` as ordinary function-calling tools. `SolidAgent::HasMemory.tool_definitions` exposes the same schemas module-level for non-agent executors (platform services, MCP servers). Inject `agent.memory.to_prompt` into instructions to prime a handoff.
188
+
189
+ ### ToolCache - Cached Tool Results
190
+
191
+ ```ruby
192
+ result = SolidAgent::ToolCache.fetch(tool: "fetch_url", args: { url: url }, ttl: 300) do
193
+ expensive_call(url)
194
+ end
195
+ result[:cached] # => true on a replay
196
+ ```
197
+
198
+ Error-shaped results (`{ error: ... }`) are never cached, so transient failures don't stick; cache keys are stable across argument ordering and symbol/string keys.
199
+
200
+ ### AgentRun - Durable Run Records
201
+
202
+ Executors record each agent execution as an `AgentRun`: lifecycle (`start!`/`complete!`/`fail!`/`cancel!`), correlation with contexts, generations, and telemetry via `trace_id`, and an append-only progress-event stream a UI can poll mid-run:
203
+
204
+ ```ruby
205
+ run = AgentRun.create!(runnable: document, agent_name: "SupportAgent", input_prompt: message)
206
+ run.record_instructions(agent.instructions) # cohort fingerprint ("calm-heron")
207
+ run.start!
208
+ run.append_event(kind: "tool", label: "fetch_url", eid: "e1", status: "started")
209
+ # ... execute ...
210
+ run.append_event(kind: "tool", label: "fetch_url", eid: "e1", status: "done", duration_ms: 120)
211
+ run.complete!(output: response.message.content, input_tokens: usage.input_tokens, output_tokens: usage.output_tokens)
212
+ ```
213
+
214
+ `AgentRun#instructions_codename` names each instruction cohort deterministically (`SolidAgent::RunFingerprint`), so comparing "what changed between these two batches of runs" reads as `calm-heron` vs `misty-atoll` instead of hex digests.
215
+
216
+ ### ModelPricing - Estimated Spend
217
+
218
+ ```ruby
219
+ SolidAgent::ModelPricing.estimate(model: "claude-sonnet-5", input_tokens: 12_000, output_tokens: 800)
220
+ # => 0.048 (USD, estimated)
221
+ ```
222
+
223
+ The generated `AgentGeneration#estimated_cost` uses this automatically. Rates come from RubyLLM's registry when that gem is present, else a static pattern table.
224
+
106
225
  ### Generators
107
226
 
108
227
  ```bash
228
+ # Install persistence tables + models (contexts, messages, generations, memories)
229
+ $ rails generate solid_agent:install
230
+
109
231
  # Generate a new agent
110
232
  $ rails generate solid_agent:agent MyAgent
111
233
 
112
- # Generate with context support
234
+ # Generate with context support. --context_name emits
235
+ # `has_context :session`, which resolves Session/SessionMessage/
236
+ # SessionGeneration — pair it with the context generator below, or drop the
237
+ # option to use the installed AgentContext models.
113
238
  $ rails generate solid_agent:agent MyAgent --context --context_name session
114
239
 
115
240
  # Generate a tool template
116
241
  $ rails generate solid_agent:tool search MyAgent --parameters query:string:required
117
242
 
118
- # Generate context models
243
+ # Generate custom-named context models
119
244
  $ rails generate solid_agent:context conversation
245
+
246
+ # Add reasoning columns to a generation model
247
+ $ rails generate solid_agent:reasons AgentGeneration
248
+
249
+ # Scaffold an agent manifest (.agent.md)
250
+ $ rails generate solid_agent:manifest research
120
251
  ```
121
252
 
253
+ ## Examples
254
+
255
+ The [`examples/`](examples) directory has a worked example per concern —
256
+ agent classes, views, controllers and console walkthroughs laid out the way
257
+ they'd sit in a Rails app:
258
+
259
+ | Example | Concerns |
260
+ |---------|----------|
261
+ | [persistent_conversation](examples/persistent_conversation) | `HasContext` |
262
+ | [memory_handoff](examples/memory_handoff) | `HasMemory` |
263
+ | [tool_streaming](examples/tool_streaming) | `HasTools`, `StreamsToolUpdates`, `ToolCache` |
264
+ | [reasoning](examples/reasoning) | `HasReasons`, `Reasonable` |
265
+ | [run_tracking](examples/run_tracking) | `AgentRun`, `RunFingerprint`, `ModelPricing` |
266
+ | [manifests](examples/manifests) | `AgentManifest` |
267
+
268
+ The narrated versions live at
269
+ [docs.activeagents.ai/solid_agent](https://docs.activeagents.ai/solid_agent).
270
+
271
+ ## Example Apps
272
+
273
+ See SolidAgent in action:
274
+
275
+ - [Fizzy](https://github.com/tonsoffun/fizzy) - AI-enhanced Kanban tracking tool with writing, research, and file analysis agents
276
+ - [Writebook](https://github.com/tonsoffun/writebook) - Collaborative writing platform with integrated AI writing assistance, research, and document analysis
277
+
122
278
  ## Development
123
279
 
124
280
  After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
125
281
 
126
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
282
+ ```bash
283
+ bundle exec rake test
284
+ ```
285
+
286
+ ### Testing against ActiveAgent
287
+
288
+ This suite runs against mocks — deliberately, so it stays fast and
289
+ dependency-free — which means it can pass while these concerns no longer
290
+ compose with the framework they extend. ActiveAgent carries a dummy Rails
291
+ app and a cross-repo suite for exactly that. Point it at your working tree:
292
+
293
+ ```bash
294
+ git clone https://github.com/activeagents/activeagent ../activeagent
295
+ cd ../activeagent
296
+
297
+ SOLID_AGENT_PATH=../solid_agent \
298
+ BUNDLE_GEMFILE=gemfiles/solid_agent_main.gemfile \
299
+ SOLID_AGENT_STRICT=1 \
300
+ bin/test test/integration/solid_agent/*_test.rb \
301
+ actionagent/test/agent_execution_service_test.rb
302
+ ```
303
+
304
+ `SOLID_AGENT_STRICT=1` fails on anything the suite would otherwise skip for
305
+ a missing API — here the resolved gem *is* your checkout, so a skip means
306
+ something was removed. CI runs this on every pull request against
307
+ ActiveAgent's main branch and its latest release, and again nightly. See
308
+ [Releasing & Cross-Repo Testing](https://docs.activeagents.ai/contributing/releasing).
309
+
310
+ ### Releasing
311
+
312
+ Releases publish from a `v*` tag through
313
+ [.github/workflows/release.yml](.github/workflows/release.yml) using RubyGems
314
+ trusted publishing, gated on CI including the cross-repo suite. Bump
315
+ `SolidAgent::VERSION`, tag, push. This gem depends on `activeagent` and
316
+ `actionagent` depends on this gem, so anything requiring a new framework API
317
+ waits for that release to land on RubyGems first.
127
318
 
128
319
  ## Contributing
129
320
 
130
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/solid_agent.
321
+ Bug reports and pull requests are welcome on GitHub at https://github.com/activeagents/solid_agent.
data/Rakefile CHANGED
@@ -3,10 +3,30 @@
3
3
  require "bundler/gem_tasks"
4
4
  require "rake/testtask"
5
5
 
6
+ # Two suites, two processes. Rake::TestTask forks one Ruby per task, which is
7
+ # the point: these harnesses cannot share a process.
8
+ #
9
+ # * test/ (unit) hand-rolls stand-ins for Rails and ActiveModel so the concerns
10
+ # run with no Rails and no database.
11
+ # * test/records/ boots a real ActiveRecord on sqlite :memory:. Loading it into
12
+ # the unit process would put a genuine ActiveRecord::Base underneath the mock
13
+ # one, and the two disagree about everything.
14
+ #
15
+ # Splitting them is what keeps both harnesses honest — and what makes a failure
16
+ # name the harness it happened in.
6
17
  Rake::TestTask.new(:test) do |t|
7
18
  t.libs << "test"
8
19
  t.libs << "lib"
9
- t.test_files = FileList["test/**/*_test.rb"]
20
+ t.test_files = FileList["test/**/*_test.rb"].exclude("test/records/**/*_test.rb")
10
21
  end
11
22
 
12
- task default: :test
23
+ namespace :test do
24
+ desc "Run the record concerns against a real ActiveRecord on sqlite"
25
+ Rake::TestTask.new(:records) do |t|
26
+ t.libs << "test"
27
+ t.libs << "lib"
28
+ t.test_files = FileList["test/records/**/*_test.rb"]
29
+ end
30
+ end
31
+
32
+ task default: [ :test, "test:records" ]