solid_agents 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +18 -0
- data/LICENSE.txt +21 -0
- data/README.md +111 -0
- data/Rakefile +12 -0
- data/app/controllers/solid_agents/agents_controller.rb +33 -0
- data/app/controllers/solid_agents/application_controller.rb +8 -0
- data/app/controllers/solid_agents/runs_controller.rb +38 -0
- data/app/helpers/solid_agents/runs_helper.rb +14 -0
- data/app/jobs/solid_agents/execute_run_job.rb +12 -0
- data/app/models/solid_agents/agent.rb +26 -0
- data/app/models/solid_agents/artifact.rb +11 -0
- data/app/models/solid_agents/config.rb +14 -0
- data/app/models/solid_agents/record.rb +11 -0
- data/app/models/solid_agents/run.rb +41 -0
- data/app/models/solid_agents/run_event.rb +12 -0
- data/app/services/solid_agents/runs/context_builder.rb +15 -0
- data/app/services/solid_agents/runs/dispatch.rb +31 -0
- data/app/services/solid_agents/runs/executor.rb +29 -0
- data/app/services/solid_agents/runs/prompt_builder.rb +33 -0
- data/app/views/layouts/solid_agents/_style.html.erb +128 -0
- data/app/views/layouts/solid_agents/application.html.erb +26 -0
- data/app/views/solid_agents/agents/edit.html.erb +17 -0
- data/app/views/solid_agents/agents/index.html.erb +27 -0
- data/app/views/solid_agents/agents/show.html.erb +11 -0
- data/app/views/solid_agents/runs/index.html.erb +27 -0
- data/app/views/solid_agents/runs/show.html.erb +48 -0
- data/config/locales/en.yml +6 -0
- data/config/routes.rb +9 -0
- data/lib/generators/solid_agents/install/USAGE +4 -0
- data/lib/generators/solid_agents/install/install_generator.rb +34 -0
- data/lib/generators/solid_agents/install/templates/config/initializers/solid_agents.rb +11 -0
- data/lib/generators/solid_agents/install/templates/db/agent_schema.rb +89 -0
- data/lib/solid_agents/engine.rb +16 -0
- data/lib/solid_agents/runtime/adapter.rb +22 -0
- data/lib/solid_agents/runtime/openclaw_adapter.rb +12 -0
- data/lib/solid_agents/runtime/tinyclaw_adapter.rb +12 -0
- data/lib/solid_agents/version.rb +5 -0
- data/lib/solid_agents.rb +32 -0
- metadata +179 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f47eb3e38b46a127496f51a08a4a9613becb2abc109922b1ad4f5f2b203c89a2
|
|
4
|
+
data.tar.gz: ad064cee60b057a8eb9dc3baccf5f06c48411550f91f984a35ff8a32fca51d5e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: fd4fe012fb44f5f2707152fae422cbc0a4a63b2342e3fac2e485231808d46c460c8465642fa747f37e7aa14820763f8a1bc9a722c65e1734b76db14c1153892f
|
|
7
|
+
data.tar.gz: 31a7f4a1ecc53a08710c656b7c7009d118e01de305f273d112984d460e1a826a0f5b4c14edbe5833bcb4589ffd1b6fa620346b8b23f61b52f265c3afd49eaf4b
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `solid_agents` will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [v0.1.0] - 2026-03-22
|
|
6
|
+
|
|
7
|
+
Initial public release (WIP).
|
|
8
|
+
|
|
9
|
+
- Introduced `solid_agents` Rails engine for database-backed agent run orchestration.
|
|
10
|
+
- Added run lifecycle models and persistence for runs, events, artifacts, agents, and config.
|
|
11
|
+
- Added dispatch and execution flow with runtime adapters for TinyClaw and OpenClaw.
|
|
12
|
+
- Added built-in UI/controllers for managing agents and inspecting runs.
|
|
13
|
+
- Added installer generator, schema template, and base configuration defaults.
|
|
14
|
+
|
|
15
|
+
Status notes:
|
|
16
|
+
|
|
17
|
+
- This release is still WIP and not production-ready.
|
|
18
|
+
- Breaking changes are expected before `1.0`.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Solid Agent
|
|
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
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# SolidAgents
|
|
2
|
+
|
|
3
|
+
**Automation workflows for Rails apps, powered by agent runtimes.**
|
|
4
|
+
|
|
5
|
+
> [!WARNING]
|
|
6
|
+
> `solid_agents` is an early release and still a work in progress.
|
|
7
|
+
> APIs, schema, runtime behavior, and configuration may change between minor releases.
|
|
8
|
+
> Expect breaking changes before `1.0`.
|
|
9
|
+
> Not production-ready yet.
|
|
10
|
+
|
|
11
|
+
`solid_agents` is a Rails engine that consumes observability/incident APIs (for example from `solid_events`), stores automation run state, dispatches work to runtime executors (TinyClaw in development, OpenClaw in production), and provides a built-in dashboard for operations.
|
|
12
|
+
|
|
13
|
+
## Scope
|
|
14
|
+
|
|
15
|
+
`solid_agents` is strictly for automation and execution:
|
|
16
|
+
|
|
17
|
+
- Consume incident/trace APIs and operator instructions
|
|
18
|
+
- Plan/execute workflows (triage, patch, test, PR, QA, review loops)
|
|
19
|
+
- Track run lifecycle, artifacts, and runtime outputs
|
|
20
|
+
- Route tasks to runtime adapters (TinyClaw/OpenClaw)
|
|
21
|
+
|
|
22
|
+
It does **not** own observability storage or incident detection/state as a source of truth. That belongs in `solid_events`.
|
|
23
|
+
|
|
24
|
+
## Features
|
|
25
|
+
|
|
26
|
+
- DB-backed run lifecycle (`queued`, `running`, `succeeded`, `failed`)
|
|
27
|
+
- Separate agent profiles per environment
|
|
28
|
+
- Runtime adapters for TinyClaw and OpenClaw
|
|
29
|
+
- Built-in UI for runs, events, artifacts, and agent configuration
|
|
30
|
+
- Install generator and schema template aligned with Solid gem conventions
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
Add this to your Gemfile:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
gem "solid_agents"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Run installer:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
rails generate solid_agents:install
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Add a dedicated database in `config/database.yml` (recommended):
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
production:
|
|
50
|
+
primary: &primary
|
|
51
|
+
<<: *default
|
|
52
|
+
database: app_production
|
|
53
|
+
solid_agents:
|
|
54
|
+
<<: *primary
|
|
55
|
+
database: app_production_solid_agents
|
|
56
|
+
migrations_paths: db/agent_migrate
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Configure engine DB connection:
|
|
60
|
+
|
|
61
|
+
```ruby
|
|
62
|
+
# config/environments/production.rb
|
|
63
|
+
config.solid_agents.connects_to = { database: { writing: :solid_agents } }
|
|
64
|
+
config.solid_agents.default_runtime = :openclaw
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Mount the UI:
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
# config/routes.rb
|
|
71
|
+
authenticate :user, ->(u) { u.admin? } do
|
|
72
|
+
mount SolidAgents::Engine, at: "/solid_agents"
|
|
73
|
+
end
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Usage
|
|
77
|
+
|
|
78
|
+
Create an agent profile:
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
SolidAgents::Agent.create!(
|
|
82
|
+
key: "fixer",
|
|
83
|
+
name: "Bug Fixer",
|
|
84
|
+
role: "fixer",
|
|
85
|
+
runtime: Rails.env.production? ? "openclaw" : "tinyclaw",
|
|
86
|
+
working_directory: Rails.root.to_s,
|
|
87
|
+
capabilities_json: {"allow_pr" => Rails.env.production?}
|
|
88
|
+
)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Dispatch from a job:
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
SolidAgents.dispatch_error(source: solid_error_record, agent_key: "fixer")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Configuration
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
config.solid_agents.tinyclaw_command = "tinyclaw"
|
|
101
|
+
config.solid_agents.openclaw_command = "openclaw"
|
|
102
|
+
config.solid_agents.default_test_command = "bin/rails test"
|
|
103
|
+
config.solid_agents.max_iterations = 8
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Development
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
bundle install
|
|
110
|
+
bundle exec rake test
|
|
111
|
+
```
|
data/Rakefile
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidAgents
|
|
4
|
+
class AgentsController < ApplicationController
|
|
5
|
+
before_action :set_agent, only: %i[show edit update]
|
|
6
|
+
|
|
7
|
+
def index
|
|
8
|
+
@agents = SolidAgents::Agent.order(:key)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def show; end
|
|
12
|
+
|
|
13
|
+
def edit; end
|
|
14
|
+
|
|
15
|
+
def update
|
|
16
|
+
if @agent.update(agent_params)
|
|
17
|
+
redirect_to agent_path(@agent), notice: "Agent updated."
|
|
18
|
+
else
|
|
19
|
+
render :edit, status: :unprocessable_entity
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def set_agent
|
|
26
|
+
@agent = SolidAgents::Agent.find(params[:id])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def agent_params
|
|
30
|
+
params.require(:agent).permit(:name, :role, :runtime, :enabled, :model, :working_directory, :timeout_seconds, :max_iterations, :environment, :system_prompt)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidAgents
|
|
4
|
+
class RunsController < ApplicationController
|
|
5
|
+
helper SolidAgents::RunsHelper
|
|
6
|
+
before_action :set_run, only: %i[show retry]
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
@runs = SolidAgents::Run.includes(:agent).order(created_at: :desc).limit(100)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def show
|
|
13
|
+
@events = @run.events.order(sequence: :asc)
|
|
14
|
+
@artifacts = @run.artifacts.order(created_at: :desc)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def retry
|
|
18
|
+
duplicated = @run.dup
|
|
19
|
+
duplicated.status = :queued
|
|
20
|
+
duplicated.started_at = nil
|
|
21
|
+
duplicated.finished_at = nil
|
|
22
|
+
duplicated.error_payload = nil
|
|
23
|
+
duplicated.result_payload = nil
|
|
24
|
+
duplicated.external_key = "retry-#{@run.id}-#{Time.current.to_i}"
|
|
25
|
+
duplicated.save!
|
|
26
|
+
duplicated.append_event!("retried", message: "Run created from retry", payload: {original_run_id: @run.id})
|
|
27
|
+
SolidAgents::ExecuteRunJob.perform_later(duplicated.id)
|
|
28
|
+
|
|
29
|
+
redirect_to run_path(duplicated), notice: "Run retried."
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def set_run
|
|
35
|
+
@run = SolidAgents::Run.find(params[:id])
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidAgents
|
|
4
|
+
class Agent < Record
|
|
5
|
+
self.table_name = "solid_agents_agents"
|
|
6
|
+
|
|
7
|
+
ROLES = %w[fixer reviewer qa custom].freeze
|
|
8
|
+
|
|
9
|
+
has_many :runs, class_name: "SolidAgents::Run", dependent: :nullify
|
|
10
|
+
|
|
11
|
+
validates :key, :name, :runtime, presence: true
|
|
12
|
+
validates :key, uniqueness: {scope: :environment}
|
|
13
|
+
validates :role, inclusion: {in: ROLES}
|
|
14
|
+
|
|
15
|
+
scope :enabled, -> { where(enabled: true) }
|
|
16
|
+
scope :for_environment, ->(env) { where(environment: [nil, env.to_s]) }
|
|
17
|
+
|
|
18
|
+
def runtime_sym
|
|
19
|
+
runtime.to_sym
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def capability?(name)
|
|
23
|
+
capabilities_json.to_h.fetch(name.to_s, false)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidAgents
|
|
4
|
+
class Config < Record
|
|
5
|
+
self.table_name = "solid_agents_configs"
|
|
6
|
+
|
|
7
|
+
validates :key, presence: true, uniqueness: {scope: :environment}
|
|
8
|
+
|
|
9
|
+
def self.get(key, environment: Rails.env)
|
|
10
|
+
row = where(key: key, environment: [environment, nil]).order(Arel.sql("environment IS NULL")).first
|
|
11
|
+
row&.value_json
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidAgents
|
|
4
|
+
class Record < ActiveRecord::Base
|
|
5
|
+
self.abstract_class = true
|
|
6
|
+
|
|
7
|
+
connects_to(**SolidAgents.connects_to) if SolidAgents.connects_to
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
ActiveSupport.run_load_hooks :solid_agents_record, SolidAgents::Record
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidAgents
|
|
4
|
+
class Run < Record
|
|
5
|
+
self.table_name = "solid_agents_runs"
|
|
6
|
+
|
|
7
|
+
STATUSES = {
|
|
8
|
+
queued: "queued",
|
|
9
|
+
running: "running",
|
|
10
|
+
pr_opened: "pr_opened",
|
|
11
|
+
succeeded: "succeeded",
|
|
12
|
+
failed: "failed",
|
|
13
|
+
canceled: "canceled"
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
belongs_to :agent, class_name: "SolidAgents::Agent"
|
|
17
|
+
has_many :events, class_name: "SolidAgents::RunEvent", foreign_key: :run_id, dependent: :delete_all
|
|
18
|
+
has_many :artifacts, class_name: "SolidAgents::Artifact", foreign_key: :run_id, dependent: :delete_all
|
|
19
|
+
|
|
20
|
+
enum :status, STATUSES
|
|
21
|
+
|
|
22
|
+
validates :runtime, :environment, :status, :source_type, presence: true
|
|
23
|
+
validates :external_key, uniqueness: true, allow_nil: true
|
|
24
|
+
|
|
25
|
+
before_validation :set_defaults, on: :create
|
|
26
|
+
|
|
27
|
+
def append_event!(event_type, message:, payload: nil)
|
|
28
|
+
next_sequence = (events.maximum(:sequence) || 0) + 1
|
|
29
|
+
events.create!(event_type: event_type, message: message, payload: payload || {}, event_time: Time.current, sequence: next_sequence)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def set_defaults
|
|
35
|
+
self.status ||= :queued
|
|
36
|
+
self.runtime ||= agent&.runtime || SolidAgents.default_runtime.to_s
|
|
37
|
+
self.environment ||= Rails.env
|
|
38
|
+
self.test_command ||= SolidAgents.default_test_command
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidAgents
|
|
4
|
+
class RunEvent < Record
|
|
5
|
+
self.table_name = "solid_agents_run_events"
|
|
6
|
+
|
|
7
|
+
belongs_to :run, class_name: "SolidAgents::Run"
|
|
8
|
+
|
|
9
|
+
validates :event_type, :message, :sequence, :event_time, presence: true
|
|
10
|
+
validates :sequence, uniqueness: {scope: :run_id}
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidAgents
|
|
4
|
+
module Runs
|
|
5
|
+
class ContextBuilder
|
|
6
|
+
def self.call(source:)
|
|
7
|
+
if source.respond_to?(:attributes)
|
|
8
|
+
source.attributes.slice("id", "message", "class_name", "backtrace", "context")
|
|
9
|
+
else
|
|
10
|
+
{value: source.to_s}
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidAgents
|
|
4
|
+
module Runs
|
|
5
|
+
class Dispatch
|
|
6
|
+
def self.call(source:, agent_key:)
|
|
7
|
+
environment = Rails.env
|
|
8
|
+
agent = SolidAgents::Agent.enabled.for_environment(environment).find_by!(key: agent_key)
|
|
9
|
+
|
|
10
|
+
run = SolidAgents::Run.create!(
|
|
11
|
+
agent: agent,
|
|
12
|
+
source_type: source.class.name,
|
|
13
|
+
source_id: source.respond_to?(:id) ? source.id : nil,
|
|
14
|
+
runtime: agent.runtime,
|
|
15
|
+
environment: environment,
|
|
16
|
+
external_key: "#{source.class.name}-#{source.respond_to?(:id) ? source.id : source.object_id}-#{Time.current.to_i}",
|
|
17
|
+
repo_path: agent.working_directory,
|
|
18
|
+
base_branch: "main",
|
|
19
|
+
max_iterations: agent.max_iterations
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
context = ContextBuilder.call(source: source)
|
|
23
|
+
run.update!(prompt_payload: context)
|
|
24
|
+
run.append_event!("dispatched", message: "Run dispatched from source", payload: {agent_key: agent.key})
|
|
25
|
+
|
|
26
|
+
SolidAgents::ExecuteRunJob.perform_later(run.id)
|
|
27
|
+
run
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidAgents
|
|
4
|
+
module Runs
|
|
5
|
+
class Executor
|
|
6
|
+
def self.call(run)
|
|
7
|
+
run.update!(status: :running, started_at: Time.current)
|
|
8
|
+
run.append_event!("runtime_started", message: "Runtime execution started")
|
|
9
|
+
|
|
10
|
+
prompt = PromptBuilder.call(run: run, context: run.prompt_payload || {})
|
|
11
|
+
adapter = SolidAgents.runtime_adapter(run.runtime)
|
|
12
|
+
result = adapter.execute(run: run, prompt: prompt)
|
|
13
|
+
|
|
14
|
+
run.artifacts.create!(kind: "log", label: "runtime_output", storage_type: "inline", content_text: result.output.to_s)
|
|
15
|
+
|
|
16
|
+
if result.ok
|
|
17
|
+
metadata = result.metadata.to_h
|
|
18
|
+
run.update!(status: :succeeded, finished_at: Time.current, result_payload: {output: result.output, metadata: metadata})
|
|
19
|
+
run.append_event!("runtime_succeeded", message: "Runtime execution finished", payload: metadata)
|
|
20
|
+
else
|
|
21
|
+
run.update!(status: :failed, finished_at: Time.current, error_payload: {stderr: result.error, metadata: result.metadata})
|
|
22
|
+
run.append_event!("runtime_failed", message: "Runtime execution failed", payload: result.metadata.to_h.merge("stderr" => result.error.to_s))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
run
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module SolidAgents
|
|
6
|
+
module Runs
|
|
7
|
+
class PromptBuilder
|
|
8
|
+
def self.call(run:, context:)
|
|
9
|
+
<<~PROMPT
|
|
10
|
+
You are #{run.agent.name} (#{run.agent.role}).
|
|
11
|
+
Goal: fix the reported Rails issue end-to-end.
|
|
12
|
+
|
|
13
|
+
Constraints:
|
|
14
|
+
- Work in repository: #{run.repo_path}
|
|
15
|
+
- Base branch: #{run.base_branch}
|
|
16
|
+
- Test command: #{run.test_command}
|
|
17
|
+
- Max iterations: #{run.max_iterations || SolidAgents.max_iterations}
|
|
18
|
+
- Open PR allowed: #{run.agent.capability?("allow_pr")}
|
|
19
|
+
|
|
20
|
+
Context JSON:
|
|
21
|
+
#{JSON.pretty_generate(context)}
|
|
22
|
+
|
|
23
|
+
Definition of done:
|
|
24
|
+
1) Root cause identified.
|
|
25
|
+
2) Code fix applied.
|
|
26
|
+
3) Tests added or updated.
|
|
27
|
+
4) Tests executed and passing.
|
|
28
|
+
5) Return final JSON with keys: status, branch_name, test_summary, pr_url, notes.
|
|
29
|
+
PROMPT
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
<style>
|
|
2
|
+
:root {
|
|
3
|
+
--bg: #f4f7fb;
|
|
4
|
+
--surface: #ffffff;
|
|
5
|
+
--ink: #17202a;
|
|
6
|
+
--muted: #5e6b78;
|
|
7
|
+
--line: #dbe4ee;
|
|
8
|
+
--accent: #0f766e;
|
|
9
|
+
--good: #166534;
|
|
10
|
+
--bad: #b91c1c;
|
|
11
|
+
--run: #7c3aed;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
* { box-sizing: border-box; }
|
|
15
|
+
|
|
16
|
+
body {
|
|
17
|
+
margin: 0;
|
|
18
|
+
font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
19
|
+
background: radial-gradient(circle at 5% 0%, #d7f7f0 0%, transparent 35%), var(--bg);
|
|
20
|
+
color: var(--ink);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.wrap { width: min(1100px, 94vw); margin: 0 auto; }
|
|
24
|
+
|
|
25
|
+
.topbar {
|
|
26
|
+
background: var(--surface);
|
|
27
|
+
border-bottom: 1px solid var(--line);
|
|
28
|
+
margin-bottom: 1.25rem;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.topbar .wrap {
|
|
32
|
+
display: flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
justify-content: space-between;
|
|
35
|
+
padding: .8rem 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.topbar h1 {
|
|
39
|
+
font-size: 1.1rem;
|
|
40
|
+
margin: 0;
|
|
41
|
+
letter-spacing: .02em;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.topbar nav { display: flex; gap: .75rem; }
|
|
45
|
+
|
|
46
|
+
.topbar a {
|
|
47
|
+
color: var(--ink);
|
|
48
|
+
text-decoration: none;
|
|
49
|
+
padding: .35rem .65rem;
|
|
50
|
+
border-radius: .5rem;
|
|
51
|
+
border: 1px solid transparent;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.topbar a:hover { border-color: var(--line); background: #f8fbff; }
|
|
55
|
+
|
|
56
|
+
.card {
|
|
57
|
+
background: var(--surface);
|
|
58
|
+
border: 1px solid var(--line);
|
|
59
|
+
border-radius: .9rem;
|
|
60
|
+
padding: 1rem;
|
|
61
|
+
margin-bottom: 1rem;
|
|
62
|
+
box-shadow: 0 1px 0 rgba(16,24,40,.02);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
table { width: 100%; border-collapse: collapse; }
|
|
66
|
+
|
|
67
|
+
th, td {
|
|
68
|
+
text-align: left;
|
|
69
|
+
padding: .65rem .5rem;
|
|
70
|
+
border-bottom: 1px solid var(--line);
|
|
71
|
+
font-size: .93rem;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
th { color: var(--muted); font-size: .8rem; text-transform: uppercase; letter-spacing: .05em; }
|
|
75
|
+
|
|
76
|
+
.badge {
|
|
77
|
+
display: inline-block;
|
|
78
|
+
padding: .2rem .55rem;
|
|
79
|
+
border-radius: .55rem;
|
|
80
|
+
font-size: .75rem;
|
|
81
|
+
font-weight: 700;
|
|
82
|
+
text-transform: uppercase;
|
|
83
|
+
letter-spacing: .04em;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.badge.ok { background: #dcfce7; color: var(--good); }
|
|
87
|
+
.badge.err { background: #fee2e2; color: var(--bad); }
|
|
88
|
+
.badge.run { background: #ede9fe; color: var(--run); }
|
|
89
|
+
.badge.muted { background: #eef2f7; color: var(--muted); }
|
|
90
|
+
|
|
91
|
+
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
|
|
92
|
+
|
|
93
|
+
.flash {
|
|
94
|
+
padding: .6rem .8rem;
|
|
95
|
+
border-radius: .65rem;
|
|
96
|
+
border: 1px solid var(--line);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.notice { background: #ecfeff; }
|
|
100
|
+
.alert { background: #fff7ed; }
|
|
101
|
+
|
|
102
|
+
.btn {
|
|
103
|
+
display: inline-block;
|
|
104
|
+
border: 1px solid var(--line);
|
|
105
|
+
background: #fff;
|
|
106
|
+
color: var(--ink);
|
|
107
|
+
border-radius: .55rem;
|
|
108
|
+
padding: .45rem .7rem;
|
|
109
|
+
text-decoration: none;
|
|
110
|
+
cursor: pointer;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
114
|
+
|
|
115
|
+
pre {
|
|
116
|
+
background: #0b1220;
|
|
117
|
+
color: #d1e0ff;
|
|
118
|
+
border-radius: .8rem;
|
|
119
|
+
padding: .85rem;
|
|
120
|
+
overflow: auto;
|
|
121
|
+
font-size: .82rem;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
@media (max-width: 800px) {
|
|
125
|
+
.grid { grid-template-columns: 1fr; }
|
|
126
|
+
th:nth-child(4), td:nth-child(4) { display: none; }
|
|
127
|
+
}
|
|
128
|
+
</style>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
5
|
+
<title>SolidAgents</title>
|
|
6
|
+
<%= csrf_meta_tags %>
|
|
7
|
+
<%= csp_meta_tag %>
|
|
8
|
+
<%= render "layouts/solid_agents/style" %>
|
|
9
|
+
</head>
|
|
10
|
+
<body>
|
|
11
|
+
<header class="topbar">
|
|
12
|
+
<div class="wrap">
|
|
13
|
+
<h1>SolidAgents</h1>
|
|
14
|
+
<nav>
|
|
15
|
+
<%= link_to "Runs", runs_path %>
|
|
16
|
+
<%= link_to "Agents", agents_path %>
|
|
17
|
+
</nav>
|
|
18
|
+
</div>
|
|
19
|
+
</header>
|
|
20
|
+
<main class="wrap">
|
|
21
|
+
<% if notice.present? %><p class="flash notice"><%= notice %></p><% end %>
|
|
22
|
+
<% if alert.present? %><p class="flash alert"><%= alert %></p><% end %>
|
|
23
|
+
<%= yield %>
|
|
24
|
+
</main>
|
|
25
|
+
</body>
|
|
26
|
+
</html>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<div class="card">
|
|
2
|
+
<h2>Edit Agent <%= @agent.key %></h2>
|
|
3
|
+
|
|
4
|
+
<%= form_with model: @agent, url: agent_path(@agent), method: :patch do |f| %>
|
|
5
|
+
<p><%= f.label :name %><br><%= f.text_field :name %></p>
|
|
6
|
+
<p><%= f.label :role %><br><%= f.select :role, SolidAgents::Agent::ROLES %></p>
|
|
7
|
+
<p><%= f.label :runtime %><br><%= f.select :runtime, %w[tinyclaw openclaw] %></p>
|
|
8
|
+
<p><%= f.label :environment %><br><%= f.text_field :environment %></p>
|
|
9
|
+
<p><%= f.label :model %><br><%= f.text_field :model %></p>
|
|
10
|
+
<p><%= f.label :working_directory %><br><%= f.text_field :working_directory %></p>
|
|
11
|
+
<p><%= f.label :timeout_seconds %><br><%= f.number_field :timeout_seconds %></p>
|
|
12
|
+
<p><%= f.label :max_iterations %><br><%= f.number_field :max_iterations %></p>
|
|
13
|
+
<p><%= f.label :system_prompt %><br><%= f.text_area :system_prompt, rows: 8, cols: 80 %></p>
|
|
14
|
+
<p><%= f.label :enabled %> <%= f.check_box :enabled %></p>
|
|
15
|
+
<p><%= f.submit "Save", class: "btn primary" %></p>
|
|
16
|
+
<% end %>
|
|
17
|
+
</div>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<div class="card">
|
|
2
|
+
<h2>Agents</h2>
|
|
3
|
+
<table>
|
|
4
|
+
<thead>
|
|
5
|
+
<tr>
|
|
6
|
+
<th>Key</th>
|
|
7
|
+
<th>Name</th>
|
|
8
|
+
<th>Role</th>
|
|
9
|
+
<th>Runtime</th>
|
|
10
|
+
<th>Env</th>
|
|
11
|
+
<th>Enabled</th>
|
|
12
|
+
</tr>
|
|
13
|
+
</thead>
|
|
14
|
+
<tbody>
|
|
15
|
+
<% @agents.each do |agent| %>
|
|
16
|
+
<tr>
|
|
17
|
+
<td><%= link_to agent.key, agent_path(agent) %></td>
|
|
18
|
+
<td><%= agent.name %></td>
|
|
19
|
+
<td><%= agent.role %></td>
|
|
20
|
+
<td><%= agent.runtime %></td>
|
|
21
|
+
<td><%= agent.environment || "all" %></td>
|
|
22
|
+
<td><%= agent.enabled? ? "yes" : "no" %></td>
|
|
23
|
+
</tr>
|
|
24
|
+
<% end %>
|
|
25
|
+
</tbody>
|
|
26
|
+
</table>
|
|
27
|
+
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<div class="card">
|
|
2
|
+
<h2>Agent: <%= @agent.key %></h2>
|
|
3
|
+
<p>Name: <strong><%= @agent.name %></strong></p>
|
|
4
|
+
<p>Role: <strong><%= @agent.role %></strong></p>
|
|
5
|
+
<p>Runtime: <strong><%= @agent.runtime %></strong></p>
|
|
6
|
+
<p>Environment: <strong><%= @agent.environment || "all" %></strong></p>
|
|
7
|
+
<p>Working directory: <code><%= @agent.working_directory %></code></p>
|
|
8
|
+
<p>Model: <code><%= @agent.model %></code></p>
|
|
9
|
+
<p>Enabled: <strong><%= @agent.enabled? ? "yes" : "no" %></strong></p>
|
|
10
|
+
<%= link_to "Edit", edit_agent_path(@agent), class: "btn" %>
|
|
11
|
+
</div>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<div class="card">
|
|
2
|
+
<h2>Runs</h2>
|
|
3
|
+
<table>
|
|
4
|
+
<thead>
|
|
5
|
+
<tr>
|
|
6
|
+
<th>ID</th>
|
|
7
|
+
<th>Agent</th>
|
|
8
|
+
<th>Status</th>
|
|
9
|
+
<th>Runtime</th>
|
|
10
|
+
<th>Source</th>
|
|
11
|
+
<th>Started</th>
|
|
12
|
+
</tr>
|
|
13
|
+
</thead>
|
|
14
|
+
<tbody>
|
|
15
|
+
<% @runs.each do |run| %>
|
|
16
|
+
<tr>
|
|
17
|
+
<td><%= link_to "##{run.id}", run_path(run) %></td>
|
|
18
|
+
<td><%= run.agent&.key %></td>
|
|
19
|
+
<td><span class="badge <%= status_badge_class(run.status) %>"><%= run.status %></span></td>
|
|
20
|
+
<td><%= run.runtime %></td>
|
|
21
|
+
<td><%= [run.source_type, run.source_id].compact.join("#") %></td>
|
|
22
|
+
<td><%= run.started_at || run.created_at %></td>
|
|
23
|
+
</tr>
|
|
24
|
+
<% end %>
|
|
25
|
+
</tbody>
|
|
26
|
+
</table>
|
|
27
|
+
</div>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<div class="card">
|
|
2
|
+
<h2>Run #<%= @run.id %></h2>
|
|
3
|
+
<p>
|
|
4
|
+
Agent: <strong><%= @run.agent.key %></strong> |
|
|
5
|
+
Runtime: <strong><%= @run.runtime %></strong> |
|
|
6
|
+
Status: <span class="badge <%= status_badge_class(@run.status) %>"><%= @run.status %></span>
|
|
7
|
+
</p>
|
|
8
|
+
<p>
|
|
9
|
+
Source: <%= [@run.source_type, @run.source_id].compact.join("#") %>
|
|
10
|
+
</p>
|
|
11
|
+
<%= button_to "Retry", retry_run_path(@run), method: :post, class: "btn primary" %>
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class="grid">
|
|
15
|
+
<div class="card">
|
|
16
|
+
<h3>Events</h3>
|
|
17
|
+
<table>
|
|
18
|
+
<thead>
|
|
19
|
+
<tr>
|
|
20
|
+
<th>#</th>
|
|
21
|
+
<th>Type</th>
|
|
22
|
+
<th>Message</th>
|
|
23
|
+
</tr>
|
|
24
|
+
</thead>
|
|
25
|
+
<tbody>
|
|
26
|
+
<% @events.each do |event| %>
|
|
27
|
+
<tr>
|
|
28
|
+
<td><%= event.sequence %></td>
|
|
29
|
+
<td><%= event.event_type %></td>
|
|
30
|
+
<td><%= event.message %></td>
|
|
31
|
+
</tr>
|
|
32
|
+
<% end %>
|
|
33
|
+
</tbody>
|
|
34
|
+
</table>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div class="card">
|
|
38
|
+
<h3>Artifacts</h3>
|
|
39
|
+
<% @artifacts.each do |artifact| %>
|
|
40
|
+
<h4><%= artifact.kind %> - <%= artifact.label %></h4>
|
|
41
|
+
<% if artifact.content_text.present? %>
|
|
42
|
+
<pre><%= artifact.content_text %></pre>
|
|
43
|
+
<% else %>
|
|
44
|
+
<pre><%= artifact.content_json %></pre>
|
|
45
|
+
<% end %>
|
|
46
|
+
<% end %>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidAgents
|
|
4
|
+
class InstallGenerator < Rails::Generators::Base
|
|
5
|
+
source_root File.expand_path("templates", __dir__)
|
|
6
|
+
|
|
7
|
+
def add_schema
|
|
8
|
+
template "db/agent_schema.rb"
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def add_initializer
|
|
12
|
+
template "config/initializers/solid_agents.rb"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def configure_environment
|
|
16
|
+
insert_into_file Pathname(destination_root).join("config/environments/production.rb"), after: /^([ \t]*).*?(?=\nend)$/ do
|
|
17
|
+
[
|
|
18
|
+
"",
|
|
19
|
+
'\\1# Configure Solid Agent',
|
|
20
|
+
'\\1config.solid_agents.connects_to = { database: { writing: :solid_agents } }',
|
|
21
|
+
'\\1config.solid_agents.default_runtime = :openclaw'
|
|
22
|
+
].join("\n")
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def ensure_migrations_directory
|
|
27
|
+
empty_directory "db/agent_migrate"
|
|
28
|
+
keep_path = Pathname(destination_root).join("db/agent_migrate/.gitkeep")
|
|
29
|
+
return if keep_path.exist?
|
|
30
|
+
|
|
31
|
+
create_file "db/agent_migrate/.gitkeep", "# Keep the SolidAgents migrations directory tracked by Git.\n"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Rails.application.configure do
|
|
4
|
+
# Optional: override in specific environments.
|
|
5
|
+
# config.solid_agents.connects_to = { database: { writing: :solid_agents } }
|
|
6
|
+
|
|
7
|
+
config.solid_agents.tinyclaw_command = ENV.fetch("SOLID_AGENTS_TINYCLAW_COMMAND", "tinyclaw")
|
|
8
|
+
config.solid_agents.openclaw_command = ENV.fetch("SOLID_AGENTS_OPENCLAW_COMMAND", "openclaw")
|
|
9
|
+
config.solid_agents.default_test_command = ENV.fetch("SOLID_AGENTS_TEST_COMMAND", "bin/rails test")
|
|
10
|
+
config.solid_agents.max_iterations = ENV.fetch("SOLID_AGENTS_MAX_ITERATIONS", 8).to_i
|
|
11
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
ActiveRecord::Schema[6.1].define do
|
|
4
|
+
create_table :solid_agents_agents, force: :cascade do |t|
|
|
5
|
+
t.string :key, null: false
|
|
6
|
+
t.string :name, null: false
|
|
7
|
+
t.string :role, null: false, default: "fixer"
|
|
8
|
+
t.string :runtime, null: false, default: "tinyclaw"
|
|
9
|
+
t.boolean :enabled, null: false, default: true
|
|
10
|
+
t.string :environment
|
|
11
|
+
t.string :model
|
|
12
|
+
t.text :system_prompt
|
|
13
|
+
t.json :capabilities_json, default: {}
|
|
14
|
+
t.string :working_directory
|
|
15
|
+
t.integer :timeout_seconds, null: false, default: 900
|
|
16
|
+
t.integer :max_iterations, null: false, default: 8
|
|
17
|
+
t.timestamps
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
add_index :solid_agents_agents, [:key, :environment], unique: true
|
|
21
|
+
|
|
22
|
+
create_table :solid_agents_runs, force: :cascade do |t|
|
|
23
|
+
t.references :agent, null: false, foreign_key: {to_table: :solid_agents_agents}
|
|
24
|
+
t.string :external_key
|
|
25
|
+
t.string :source_type, null: false
|
|
26
|
+
t.bigint :source_id
|
|
27
|
+
t.string :error_fingerprint
|
|
28
|
+
t.string :status, null: false, default: "queued"
|
|
29
|
+
t.string :runtime, null: false
|
|
30
|
+
t.string :environment, null: false
|
|
31
|
+
t.string :repo_path
|
|
32
|
+
t.string :base_branch
|
|
33
|
+
t.string :work_branch
|
|
34
|
+
t.string :commit_sha
|
|
35
|
+
t.string :pr_url
|
|
36
|
+
t.integer :pr_number
|
|
37
|
+
t.string :test_command
|
|
38
|
+
t.integer :attempt_count, null: false, default: 0
|
|
39
|
+
t.integer :max_iterations
|
|
40
|
+
t.datetime :started_at
|
|
41
|
+
t.datetime :finished_at
|
|
42
|
+
t.json :prompt_payload, default: {}
|
|
43
|
+
t.json :result_payload, default: {}
|
|
44
|
+
t.json :error_payload, default: {}
|
|
45
|
+
t.timestamps
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
add_index :solid_agents_runs, :external_key, unique: true
|
|
49
|
+
add_index :solid_agents_runs, :status
|
|
50
|
+
add_index :solid_agents_runs, :error_fingerprint
|
|
51
|
+
add_index :solid_agents_runs, [:source_type, :source_id]
|
|
52
|
+
|
|
53
|
+
create_table :solid_agents_run_events, force: :cascade do |t|
|
|
54
|
+
t.references :run, null: false, foreign_key: {to_table: :solid_agents_runs}
|
|
55
|
+
t.string :event_type, null: false
|
|
56
|
+
t.datetime :event_time, null: false
|
|
57
|
+
t.text :message, null: false
|
|
58
|
+
t.json :payload, default: {}
|
|
59
|
+
t.integer :sequence, null: false
|
|
60
|
+
t.timestamps
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
add_index :solid_agents_run_events, [:run_id, :sequence], unique: true
|
|
64
|
+
add_index :solid_agents_run_events, :event_type
|
|
65
|
+
|
|
66
|
+
create_table :solid_agents_artifacts, force: :cascade do |t|
|
|
67
|
+
t.references :run, null: false, foreign_key: {to_table: :solid_agents_runs}
|
|
68
|
+
t.string :kind, null: false
|
|
69
|
+
t.string :label
|
|
70
|
+
t.string :storage_type, null: false
|
|
71
|
+
t.string :storage_ref
|
|
72
|
+
t.text :content_text
|
|
73
|
+
t.json :content_json, default: {}
|
|
74
|
+
t.string :sha256
|
|
75
|
+
t.bigint :byte_size
|
|
76
|
+
t.timestamps
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
add_index :solid_agents_artifacts, :kind
|
|
80
|
+
|
|
81
|
+
create_table :solid_agents_configs, force: :cascade do |t|
|
|
82
|
+
t.string :key, null: false
|
|
83
|
+
t.string :environment
|
|
84
|
+
t.json :value_json, default: {}
|
|
85
|
+
t.timestamps
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
add_index :solid_agents_configs, [:key, :environment], unique: true
|
|
89
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidAgents
|
|
4
|
+
class Engine < ::Rails::Engine
|
|
5
|
+
config.root = File.expand_path("../..", __dir__)
|
|
6
|
+
isolate_namespace SolidAgents
|
|
7
|
+
|
|
8
|
+
config.solid_agents = ActiveSupport::OrderedOptions.new
|
|
9
|
+
|
|
10
|
+
initializer "solid_agents.configure" do
|
|
11
|
+
config.solid_agents.each do |name, value|
|
|
12
|
+
SolidAgents.public_send(:"#{name}=", value)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
|
|
5
|
+
module SolidAgents
|
|
6
|
+
module Runtime
|
|
7
|
+
class Adapter
|
|
8
|
+
Result = Struct.new(:ok, :output, :error, :metadata, keyword_init: true)
|
|
9
|
+
|
|
10
|
+
def execute(_run:, _prompt:)
|
|
11
|
+
raise NotImplementedError
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def run_command(*argv)
|
|
17
|
+
stdout, stderr, status = Open3.capture3(*argv)
|
|
18
|
+
Result.new(ok: status.success?, output: stdout, error: stderr, metadata: {exit_status: status.exitstatus, command: argv})
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidAgents
|
|
4
|
+
module Runtime
|
|
5
|
+
class OpenclawAdapter < Adapter
|
|
6
|
+
def execute(run:, prompt:)
|
|
7
|
+
command = [SolidAgents.openclaw_command.to_s, "agent", "--message", prompt, "--thinking", "high"]
|
|
8
|
+
run_command(*command)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SolidAgents
|
|
4
|
+
module Runtime
|
|
5
|
+
class TinyclawAdapter < Adapter
|
|
6
|
+
def execute(run:, prompt:)
|
|
7
|
+
command = [SolidAgents.tinyclaw_command.to_s, "send", "@#{run.agent.key} #{prompt}"]
|
|
8
|
+
run_command(*command)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
data/lib/solid_agents.rb
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "solid_agents/version"
|
|
4
|
+
require_relative "solid_agents/runtime/adapter"
|
|
5
|
+
require_relative "solid_agents/runtime/tinyclaw_adapter"
|
|
6
|
+
require_relative "solid_agents/runtime/openclaw_adapter"
|
|
7
|
+
require_relative "solid_agents/engine"
|
|
8
|
+
|
|
9
|
+
module SolidAgents
|
|
10
|
+
mattr_accessor :connects_to
|
|
11
|
+
mattr_accessor :base_controller_class, default: "::ActionController::Base"
|
|
12
|
+
mattr_accessor :default_runtime, default: :tinyclaw
|
|
13
|
+
mattr_accessor :tinyclaw_command, default: "tinyclaw"
|
|
14
|
+
mattr_accessor :openclaw_command, default: "openclaw"
|
|
15
|
+
mattr_accessor :default_test_command, default: "bin/rails test"
|
|
16
|
+
mattr_accessor :max_iterations, default: 8
|
|
17
|
+
|
|
18
|
+
class << self
|
|
19
|
+
def runtime_adapter(runtime)
|
|
20
|
+
case runtime.to_sym
|
|
21
|
+
when :tinyclaw then SolidAgents::Runtime::TinyclawAdapter.new
|
|
22
|
+
when :openclaw then SolidAgents::Runtime::OpenclawAdapter.new
|
|
23
|
+
else
|
|
24
|
+
raise ArgumentError, "Unsupported runtime: #{runtime.inspect}"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def dispatch_error(source:, agent_key: "fixer")
|
|
29
|
+
SolidAgents::Runs::Dispatch.call(source: source, agent_key: agent_key)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: solid_agents
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Kaka Ruto
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: actionpack
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '6.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '6.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: actionview
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '6.1'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '6.1'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: activejob
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '6.1'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '6.1'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: activerecord
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '6.1'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '6.1'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: activesupport
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '6.1'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '6.1'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: railties
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '6.1'
|
|
89
|
+
type: :runtime
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '6.1'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: sqlite3
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '2.0'
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '2.0'
|
|
110
|
+
description: Solid Agent stores agent runs in its own database and dispatches work
|
|
111
|
+
to tinyclaw/openclaw runtimes with a built-in Rails UI.
|
|
112
|
+
email:
|
|
113
|
+
- kr@kakaruto.com
|
|
114
|
+
executables: []
|
|
115
|
+
extensions: []
|
|
116
|
+
extra_rdoc_files: []
|
|
117
|
+
files:
|
|
118
|
+
- CHANGELOG.md
|
|
119
|
+
- LICENSE.txt
|
|
120
|
+
- README.md
|
|
121
|
+
- Rakefile
|
|
122
|
+
- app/controllers/solid_agents/agents_controller.rb
|
|
123
|
+
- app/controllers/solid_agents/application_controller.rb
|
|
124
|
+
- app/controllers/solid_agents/runs_controller.rb
|
|
125
|
+
- app/helpers/solid_agents/runs_helper.rb
|
|
126
|
+
- app/jobs/solid_agents/execute_run_job.rb
|
|
127
|
+
- app/models/solid_agents/agent.rb
|
|
128
|
+
- app/models/solid_agents/artifact.rb
|
|
129
|
+
- app/models/solid_agents/config.rb
|
|
130
|
+
- app/models/solid_agents/record.rb
|
|
131
|
+
- app/models/solid_agents/run.rb
|
|
132
|
+
- app/models/solid_agents/run_event.rb
|
|
133
|
+
- app/services/solid_agents/runs/context_builder.rb
|
|
134
|
+
- app/services/solid_agents/runs/dispatch.rb
|
|
135
|
+
- app/services/solid_agents/runs/executor.rb
|
|
136
|
+
- app/services/solid_agents/runs/prompt_builder.rb
|
|
137
|
+
- app/views/layouts/solid_agents/_style.html.erb
|
|
138
|
+
- app/views/layouts/solid_agents/application.html.erb
|
|
139
|
+
- app/views/solid_agents/agents/edit.html.erb
|
|
140
|
+
- app/views/solid_agents/agents/index.html.erb
|
|
141
|
+
- app/views/solid_agents/agents/show.html.erb
|
|
142
|
+
- app/views/solid_agents/runs/index.html.erb
|
|
143
|
+
- app/views/solid_agents/runs/show.html.erb
|
|
144
|
+
- config/locales/en.yml
|
|
145
|
+
- config/routes.rb
|
|
146
|
+
- lib/generators/solid_agents/install/USAGE
|
|
147
|
+
- lib/generators/solid_agents/install/install_generator.rb
|
|
148
|
+
- lib/generators/solid_agents/install/templates/config/initializers/solid_agents.rb
|
|
149
|
+
- lib/generators/solid_agents/install/templates/db/agent_schema.rb
|
|
150
|
+
- lib/solid_agents.rb
|
|
151
|
+
- lib/solid_agents/engine.rb
|
|
152
|
+
- lib/solid_agents/runtime/adapter.rb
|
|
153
|
+
- lib/solid_agents/runtime/openclaw_adapter.rb
|
|
154
|
+
- lib/solid_agents/runtime/tinyclaw_adapter.rb
|
|
155
|
+
- lib/solid_agents/version.rb
|
|
156
|
+
homepage: https://github.com/kaka-ruto/solid_agents
|
|
157
|
+
licenses:
|
|
158
|
+
- MIT
|
|
159
|
+
metadata:
|
|
160
|
+
homepage_uri: https://github.com/kaka-ruto/solid_agents
|
|
161
|
+
source_code_uri: https://github.com/kaka-ruto/solid_agents
|
|
162
|
+
rdoc_options: []
|
|
163
|
+
require_paths:
|
|
164
|
+
- lib
|
|
165
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
166
|
+
requirements:
|
|
167
|
+
- - ">="
|
|
168
|
+
- !ruby/object:Gem::Version
|
|
169
|
+
version: 2.6.0
|
|
170
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
171
|
+
requirements:
|
|
172
|
+
- - ">="
|
|
173
|
+
- !ruby/object:Gem::Version
|
|
174
|
+
version: '0'
|
|
175
|
+
requirements: []
|
|
176
|
+
rubygems_version: 4.0.3
|
|
177
|
+
specification_version: 4
|
|
178
|
+
summary: Database-backed Rails AI agent runtime bridge
|
|
179
|
+
test_files: []
|