solid_agents 0.1.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f47eb3e38b46a127496f51a08a4a9613becb2abc109922b1ad4f5f2b203c89a2
4
- data.tar.gz: ad064cee60b057a8eb9dc3baccf5f06c48411550f91f984a35ff8a32fca51d5e
3
+ metadata.gz: f5972db63aa2e5fb01908f0a70c788856b9812fce9961b21cf75c1d79b041406
4
+ data.tar.gz: 922aafdc00524dab9c490c56031af71c99ef539afbbf87acba80c53a9c1b73c3
5
5
  SHA512:
6
- metadata.gz: fd4fe012fb44f5f2707152fae422cbc0a4a63b2342e3fac2e485231808d46c460c8465642fa747f37e7aa14820763f8a1bc9a722c65e1734b76db14c1153892f
7
- data.tar.gz: 31a7f4a1ecc53a08710c656b7c7009d118e01de305f273d112984d460e1a826a0f5b4c14edbe5833bcb4589ffd1b6fa620346b8b23f61b52f265c3afd49eaf4b
6
+ metadata.gz: 1290f03a93a621b1bc78f94b8f7031134809b368c1e37c9542f5d22b58abd8d579b7774e93c6d0d6720838b3b5ea618a807c5fd6e2562737701ac7b0e5598c28
7
+ data.tar.gz: e6f7ceca7356d904fc0adf8187eea3ac4784e64cac04db5c3e944ceda661112c9e6729d9d1524f34ec2fb610925eafe10452fb79d2d611a088ca0e9835e145c5
data/CHANGELOG.md CHANGED
@@ -2,13 +2,30 @@
2
2
 
3
3
  All notable changes to `solid_agents` will be documented in this file.
4
4
 
5
+ ## [v0.2.0] - 2026-03-23
6
+
7
+ Major architecture reset delivered as a minor release for rapid iteration.
8
+
9
+ - Replaced runtime adapters with a single pi-only execution path.
10
+ - Rebuilt the run model into an event-driven staged workflow.
11
+ - Added work-item board tracking and explicit inter-agent handoff records.
12
+ - Introduced stage ownership with alphabetical agents: alex, betty, chad, david, emma.
13
+ - Updated installer schema and initializer templates for the new workflow primitives.
14
+ - Reworked run UI to expose stage, owner, events, and artifacts in clean columns.
15
+ - Expanded Minitest coverage and moved to YAML fixtures for deterministic test data.
16
+
17
+ Status notes:
18
+
19
+ - This release is still WIP and not production-ready.
20
+ - Breaking changes are expected before `1.0`.
21
+
5
22
  ## [v0.1.0] - 2026-03-22
6
23
 
7
24
  Initial public release (WIP).
8
25
 
9
26
  - Introduced `solid_agents` Rails engine for database-backed agent run orchestration.
10
27
  - 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.
28
+ - Added dispatch and execution flow with runtime adapters for OpenAI pi runtime and OpenAI pi runtime.
12
29
  - Added built-in UI/controllers for managing agents and inspecting runs.
13
30
  - Added installer generator, schema template, and base configuration defaults.
14
31
 
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # SolidAgents
2
2
 
3
- **Automation workflows for Rails apps, powered by agent runtimes.**
3
+ **Event-driven error fixing workflow for Rails apps, powered by the pi runtime.**
4
4
 
5
5
  > [!WARNING]
6
6
  > `solid_agents` is an early release and still a work in progress.
@@ -8,26 +8,30 @@
8
8
  > Expect breaking changes before `1.0`.
9
9
  > Not production-ready yet.
10
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.
11
+ `solid_agents` is a Rails engine focused on a single pipeline:
12
+
13
+ `error received -> staged agent workflow -> code fix attempt -> PR/CI stage tracking`
12
14
 
13
15
  ## Scope
14
16
 
15
- `solid_agents` is strictly for automation and execution:
17
+ `solid_agents` is for automation orchestration and execution only:
16
18
 
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)
19
+ - Consume error-like events from source adapters (starting with `solid_errors` style payloads)
20
+ - Track runs in an event-driven stage machine
21
+ - Enforce non-overlapping stage ownership (`alex`, `betty`, `chad`, `david`, `emma`)
22
+ - Persist handoffs, notes, and artifacts for each stage
23
+ - Execute stage tasks through the pi runtime adapter
21
24
 
22
- It does **not** own observability storage or incident detection/state as a source of truth. That belongs in `solid_events`.
25
+ It does **not** own observability storage or incident detection as a source of truth.
23
26
 
24
27
  ## Features
25
28
 
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
29
+ - DB-backed run lifecycle and stage workflow
30
+ - Append-only run events with actor attribution
31
+ - Work-item board columns driven by stage transitions
32
+ - Handoff records between stage owners
33
+ - Built-in UI for runs, events, and artifacts
34
+ - Installer generator and schema template
31
35
 
32
36
  ## Installation
33
37
 
@@ -43,25 +47,12 @@ Run installer:
43
47
  rails generate solid_agents:install
44
48
  ```
45
49
 
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:
50
+ Configure engine DB connection if desired:
60
51
 
61
52
  ```ruby
62
53
  # config/environments/production.rb
63
54
  config.solid_agents.connects_to = { database: { writing: :solid_agents } }
64
- config.solid_agents.default_runtime = :openclaw
55
+ config.solid_agents.default_runtime = :pi
65
56
  ```
66
57
 
67
58
  Mount the UI:
@@ -75,30 +66,30 @@ end
75
66
 
76
67
  ## Usage
77
68
 
78
- Create an agent profile:
69
+ Create pipeline agents:
79
70
 
80
71
  ```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
- )
72
+ %w[alex betty chad david emma].each do |key|
73
+ SolidAgents::Agent.find_or_create_by!(key: key, environment: Rails.env) do |agent|
74
+ agent.name = key.capitalize
75
+ agent.role = key
76
+ agent.runtime = "pi"
77
+ agent.working_directory = Rails.root.to_s
78
+ agent.enabled = true
79
+ end
80
+ end
89
81
  ```
90
82
 
91
83
  Dispatch from a job:
92
84
 
93
85
  ```ruby
94
- SolidAgents.dispatch_error(source: solid_error_record, agent_key: "fixer")
86
+ SolidAgents.dispatch_error(source: solid_error_record, agent_key: "alex")
95
87
  ```
96
88
 
97
89
  ## Configuration
98
90
 
99
91
  ```ruby
100
- config.solid_agents.tinyclaw_command = "tinyclaw"
101
- config.solid_agents.openclaw_command = "openclaw"
92
+ config.solid_agents.pi_command = "pi"
102
93
  config.solid_agents.default_test_command = "bin/rails test"
103
94
  config.solid_agents.max_iterations = 8
104
95
  ```
@@ -108,4 +99,5 @@ config.solid_agents.max_iterations = 8
108
99
  ```bash
109
100
  bundle install
110
101
  bundle exec rake test
102
+ gem build solid_agents.gemspec
111
103
  ```
@@ -17,13 +17,16 @@ module SolidAgents
17
17
  def retry
18
18
  duplicated = @run.dup
19
19
  duplicated.status = :queued
20
+ duplicated.stage = "received"
21
+ duplicated.stage_owner = "alex"
20
22
  duplicated.started_at = nil
21
23
  duplicated.finished_at = nil
22
24
  duplicated.error_payload = nil
23
25
  duplicated.result_payload = nil
24
26
  duplicated.external_key = "retry-#{@run.id}-#{Time.current.to_i}"
25
27
  duplicated.save!
26
- duplicated.append_event!("retried", message: "Run created from retry", payload: {original_run_id: @run.id})
28
+ duplicated.create_work_item!(column_key: duplicated.stage, title: @run.work_item&.title || "Retry ##{@run.id}", summary: "Retry workflow run", metadata_json: {"original_run_id" => @run.id})
29
+ duplicated.append_event!("retried", message: "Run created from retry", payload: {"original_run_id" => @run.id}, actor: "alex")
27
30
  SolidAgents::ExecuteRunJob.perform_later(duplicated.id)
28
31
 
29
32
  redirect_to run_path(duplicated), notice: "Run retried."
@@ -6,6 +6,8 @@ module SolidAgents
6
6
 
7
7
  def perform(run_id)
8
8
  run = SolidAgents::Run.find(run_id)
9
+ return if SolidAgents::Workflow.final_stage?(run.stage) || run.failed? || run.succeeded?
10
+
9
11
  SolidAgents::Runs::Executor.call(run)
10
12
  end
11
13
  end
@@ -4,7 +4,7 @@ module SolidAgents
4
4
  class Agent < Record
5
5
  self.table_name = "solid_agents_agents"
6
6
 
7
- ROLES = %w[fixer reviewer qa custom].freeze
7
+ ROLES = %w[alex betty chad david emma].freeze
8
8
 
9
9
  has_many :runs, class_name: "SolidAgents::Run", dependent: :nullify
10
10
 
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidAgents
4
+ class Handoff < Record
5
+ self.table_name = "solid_agents_handoffs"
6
+
7
+ belongs_to :run, class_name: "SolidAgents::Run"
8
+
9
+ validates :from_agent, :to_agent, :stage, presence: true
10
+ end
11
+ end
@@ -7,6 +7,7 @@ module SolidAgents
7
7
  STATUSES = {
8
8
  queued: "queued",
9
9
  running: "running",
10
+ blocked: "blocked",
10
11
  pr_opened: "pr_opened",
11
12
  succeeded: "succeeded",
12
13
  failed: "failed",
@@ -16,23 +17,57 @@ module SolidAgents
16
17
  belongs_to :agent, class_name: "SolidAgents::Agent"
17
18
  has_many :events, class_name: "SolidAgents::RunEvent", foreign_key: :run_id, dependent: :delete_all
18
19
  has_many :artifacts, class_name: "SolidAgents::Artifact", foreign_key: :run_id, dependent: :delete_all
20
+ has_many :handoffs, class_name: "SolidAgents::Handoff", foreign_key: :run_id, dependent: :delete_all
21
+ has_one :work_item, class_name: "SolidAgents::WorkItem", foreign_key: :run_id, dependent: :delete
19
22
 
20
23
  enum :status, STATUSES
21
24
 
22
- validates :runtime, :environment, :status, :source_type, presence: true
25
+ validates :runtime, :environment, :status, :source_type, :stage, presence: true
23
26
  validates :external_key, uniqueness: true, allow_nil: true
24
27
 
25
28
  before_validation :set_defaults, on: :create
26
29
 
27
- def append_event!(event_type, message:, payload: nil)
30
+ def append_event!(event_type, message:, payload: nil, actor: nil)
28
31
  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)
32
+ events.create!(
33
+ event_type: event_type,
34
+ message: message,
35
+ payload: payload || {},
36
+ actor: actor || stage_owner,
37
+ event_time: Time.current,
38
+ sequence: next_sequence
39
+ )
40
+ end
41
+
42
+ def transition_to!(next_stage, actor:, message:, payload: {})
43
+ update!(stage: next_stage, stage_owner: SolidAgents::Workflow.stage_agent(next_stage))
44
+ work_item&.update!(column_key: next_stage)
45
+ append_event!("stage_transitioned", message: message, payload: payload.merge("next_stage" => next_stage), actor: actor)
46
+ end
47
+
48
+ def start_stage!(actor:)
49
+ update!(status: :running, started_at: (started_at || Time.current))
50
+ append_event!("stage_started", message: "Stage #{stage} started", payload: {"stage" => stage}, actor: actor)
51
+ end
52
+
53
+ def complete!(result_payload:, actor:)
54
+ update!(status: :succeeded, stage: "done", stage_owner: SolidAgents::Workflow.stage_agent("done"), finished_at: Time.current, result_payload: result_payload)
55
+ work_item&.update!(column_key: "done")
56
+ append_event!("run_completed", message: "Run completed successfully", payload: result_payload, actor: actor)
57
+ end
58
+
59
+ def fail!(error_payload:, actor:)
60
+ update!(status: :failed, finished_at: Time.current, error_payload: error_payload)
61
+ work_item&.update!(column_key: "failed")
62
+ append_event!("run_failed", message: "Run failed", payload: error_payload, actor: actor)
30
63
  end
31
64
 
32
65
  private
33
66
 
34
67
  def set_defaults
35
68
  self.status ||= :queued
69
+ self.stage ||= "received"
70
+ self.stage_owner ||= SolidAgents::Workflow.stage_agent(stage)
36
71
  self.runtime ||= agent&.runtime || SolidAgents.default_runtime.to_s
37
72
  self.environment ||= Rails.env
38
73
  self.test_command ||= SolidAgents.default_test_command
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidAgents
4
+ class WorkItem < Record
5
+ self.table_name = "solid_agents_work_items"
6
+
7
+ belongs_to :run, class_name: "SolidAgents::Run"
8
+
9
+ validates :column_key, :title, presence: true
10
+ end
11
+ end
@@ -16,12 +16,15 @@ module SolidAgents
16
16
  external_key: "#{source.class.name}-#{source.respond_to?(:id) ? source.id : source.object_id}-#{Time.current.to_i}",
17
17
  repo_path: agent.working_directory,
18
18
  base_branch: "main",
19
- max_iterations: agent.max_iterations
19
+ max_iterations: agent.max_iterations,
20
+ stage: "received",
21
+ stage_owner: "alex"
20
22
  )
21
23
 
22
24
  context = ContextBuilder.call(source: source)
23
25
  run.update!(prompt_payload: context)
24
- run.append_event!("dispatched", message: "Run dispatched from source", payload: {agent_key: agent.key})
26
+ run.append_event!("run_received", message: "Run received from source", payload: {"agent_key" => agent.key, "source_type" => run.source_type}, actor: "alex")
27
+ run.create_work_item!(column_key: run.stage, title: "Fix #{run.source_type}##{run.source_id || "n/a"}", summary: "Automated error-fixing pipeline", metadata_json: {"agent_key" => agent.key})
25
28
 
26
29
  SolidAgents::ExecuteRunJob.perform_later(run.id)
27
30
  run
@@ -4,24 +4,56 @@ module SolidAgents
4
4
  module Runs
5
5
  class Executor
6
6
  def self.call(run)
7
- run.update!(status: :running, started_at: Time.current)
8
- run.append_event!("runtime_started", message: "Runtime execution started")
7
+ stage = run.stage
8
+ owner = SolidAgents::Workflow.stage_agent(stage)
9
+
10
+ run.start_stage!(actor: owner)
9
11
 
10
12
  prompt = PromptBuilder.call(run: run, context: run.prompt_payload || {})
11
13
  adapter = SolidAgents.runtime_adapter(run.runtime)
12
14
  result = adapter.execute(run: run, prompt: prompt)
13
15
 
14
- run.artifacts.create!(kind: "log", label: "runtime_output", storage_type: "inline", content_text: result.output.to_s)
16
+ run.artifacts.create!(
17
+ kind: "log",
18
+ label: "#{stage}_runtime_output",
19
+ storage_type: "inline",
20
+ content_text: result.output.to_s,
21
+ content_json: {"metadata" => result.metadata.to_h}
22
+ )
23
+
24
+ unless result.ok
25
+ run.fail!(error_payload: {"stage" => stage, "stderr" => result.error.to_s, "metadata" => result.metadata.to_h}, actor: owner)
26
+ return run
27
+ end
28
+
29
+ next_stage = SolidAgents::Workflow.next_stage(stage)
30
+ next_owner = SolidAgents::Workflow.stage_agent(next_stage)
15
31
 
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)
32
+ run.handoffs.create!(
33
+ stage: stage,
34
+ from_agent: owner,
35
+ to_agent: next_owner,
36
+ note: "#{owner} completed #{stage} and handed off to #{next_owner}",
37
+ payload: {"output_excerpt" => result.output.to_s.slice(0, 500)}
38
+ )
39
+
40
+ run.append_event!(
41
+ "stage_completed",
42
+ message: "#{owner} completed #{stage}",
43
+ payload: {"stage" => stage, "next_stage" => next_stage, "next_owner" => next_owner},
44
+ actor: owner
45
+ )
46
+
47
+ if next_stage == "done"
48
+ run.complete!(result_payload: {"final_stage" => stage, "metadata" => result.metadata.to_h, "output" => result.output.to_s}, actor: next_owner)
20
49
  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))
50
+ run.transition_to!(next_stage, actor: owner, message: "Transitioned from #{stage} to #{next_stage}", payload: {"next_owner" => next_owner})
51
+ SolidAgents::ExecuteRunJob.perform_later(run.id)
23
52
  end
24
53
 
54
+ run
55
+ rescue StandardError => e
56
+ run.fail!(error_payload: {"stage" => run.stage, "exception" => e.class.name, "message" => e.message}, actor: SolidAgents::Workflow.stage_agent(run.stage))
25
57
  run
26
58
  end
27
59
  end
@@ -7,25 +7,23 @@ module SolidAgents
7
7
  class PromptBuilder
8
8
  def self.call(run:, context:)
9
9
  <<~PROMPT
10
- You are #{run.agent.name} (#{run.agent.role}).
11
- Goal: fix the reported Rails issue end-to-end.
10
+ You are #{run.stage_owner} executing stage #{run.stage} for run #{run.id}.
11
+ Goal: move the workflow to the next stage with clear evidence.
12
12
 
13
13
  Constraints:
14
- - Work in repository: #{run.repo_path}
14
+ - Repository path: #{run.repo_path}
15
15
  - Base branch: #{run.base_branch}
16
16
  - Test command: #{run.test_command}
17
+ - Runtime: #{run.runtime}
17
18
  - Max iterations: #{run.max_iterations || SolidAgents.max_iterations}
18
- - Open PR allowed: #{run.agent.capability?("allow_pr")}
19
+
20
+ Stage handoff contract:
21
+ - Produce concise notes for the next stage owner.
22
+ - Include reproducible evidence and command output.
23
+ - Respect repository rules from AGENTS.md.
19
24
 
20
25
  Context JSON:
21
26
  #{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
27
  PROMPT
30
28
  end
31
29
  end
@@ -1,17 +1,14 @@
1
1
  <div class="card">
2
- <h2>Edit Agent <%= @agent.key %></h2>
3
-
2
+ <h2>Edit Agent</h2>
4
3
  <%= form_with model: @agent, url: agent_path(@agent), method: :patch do |f| %>
5
4
  <p><%= f.label :name %><br><%= f.text_field :name %></p>
6
5
  <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>
6
+ <p><%= f.label :runtime %><br><%= f.select :runtime, %w[pi] %></p>
8
7
  <p><%= f.label :environment %><br><%= f.text_field :environment %></p>
9
- <p><%= f.label :model %><br><%= f.text_field :model %></p>
10
8
  <p><%= f.label :working_directory %><br><%= f.text_field :working_directory %></p>
11
9
  <p><%= f.label :timeout_seconds %><br><%= f.number_field :timeout_seconds %></p>
12
10
  <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>
11
+ <p><%= f.label :enabled %><br><%= f.check_box :enabled %></p>
12
+ <%= f.submit "Save", class: "btn primary" %>
16
13
  <% end %>
17
14
  </div>
@@ -6,9 +6,10 @@
6
6
  <th>ID</th>
7
7
  <th>Agent</th>
8
8
  <th>Status</th>
9
+ <th>Stage</th>
10
+ <th>Owner</th>
9
11
  <th>Runtime</th>
10
12
  <th>Source</th>
11
- <th>Started</th>
12
13
  </tr>
13
14
  </thead>
14
15
  <tbody>
@@ -17,9 +18,10 @@
17
18
  <td><%= link_to "##{run.id}", run_path(run) %></td>
18
19
  <td><%= run.agent&.key %></td>
19
20
  <td><span class="badge <%= status_badge_class(run.status) %>"><%= run.status %></span></td>
21
+ <td><%= run.stage %></td>
22
+ <td><%= run.stage_owner %></td>
20
23
  <td><%= run.runtime %></td>
21
24
  <td><%= [run.source_type, run.source_id].compact.join("#") %></td>
22
- <td><%= run.started_at || run.created_at %></td>
23
25
  </tr>
24
26
  <% end %>
25
27
  </tbody>
@@ -5,6 +5,11 @@
5
5
  Runtime: <strong><%= @run.runtime %></strong> |
6
6
  Status: <span class="badge <%= status_badge_class(@run.status) %>"><%= @run.status %></span>
7
7
  </p>
8
+ <p>
9
+ Stage: <strong><%= @run.stage %></strong> |
10
+ Owner: <strong><%= @run.stage_owner %></strong> |
11
+ Board Column: <strong><%= @run.work_item&.column_key || @run.stage %></strong>
12
+ </p>
8
13
  <p>
9
14
  Source: <%= [@run.source_type, @run.source_id].compact.join("#") %>
10
15
  </p>
@@ -19,6 +24,7 @@
19
24
  <tr>
20
25
  <th>#</th>
21
26
  <th>Type</th>
27
+ <th>Actor</th>
22
28
  <th>Message</th>
23
29
  </tr>
24
30
  </thead>
@@ -27,6 +33,7 @@
27
33
  <tr>
28
34
  <td><%= event.sequence %></td>
29
35
  <td><%= event.event_type %></td>
36
+ <td><%= event.actor %></td>
30
37
  <td><%= event.message %></td>
31
38
  </tr>
32
39
  <% end %>
@@ -18,7 +18,7 @@ module SolidAgents
18
18
  "",
19
19
  '\\1# Configure Solid Agent',
20
20
  '\\1config.solid_agents.connects_to = { database: { writing: :solid_agents } }',
21
- '\\1config.solid_agents.default_runtime = :openclaw'
21
+ '\\1config.solid_agents.default_runtime = :pi'
22
22
  ].join("\n")
23
23
  end
24
24
  end
@@ -4,8 +4,7 @@ Rails.application.configure do
4
4
  # Optional: override in specific environments.
5
5
  # config.solid_agents.connects_to = { database: { writing: :solid_agents } }
6
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")
7
+ config.solid_agents.pi_command = ENV.fetch("SOLID_AGENTS_PI_COMMAND", "pi")
9
8
  config.solid_agents.default_test_command = ENV.fetch("SOLID_AGENTS_TEST_COMMAND", "bin/rails test")
10
9
  config.solid_agents.max_iterations = ENV.fetch("SOLID_AGENTS_MAX_ITERATIONS", 8).to_i
11
10
  end
@@ -4,8 +4,8 @@ ActiveRecord::Schema[6.1].define do
4
4
  create_table :solid_agents_agents, force: :cascade do |t|
5
5
  t.string :key, null: false
6
6
  t.string :name, null: false
7
- t.string :role, null: false, default: "fixer"
8
- t.string :runtime, null: false, default: "tinyclaw"
7
+ t.string :role, null: false, default: "alex"
8
+ t.string :runtime, null: false, default: "pi"
9
9
  t.boolean :enabled, null: false, default: true
10
10
  t.string :environment
11
11
  t.string :model
@@ -26,7 +26,9 @@ ActiveRecord::Schema[6.1].define do
26
26
  t.bigint :source_id
27
27
  t.string :error_fingerprint
28
28
  t.string :status, null: false, default: "queued"
29
- t.string :runtime, null: false
29
+ t.string :stage, null: false, default: "received"
30
+ t.string :stage_owner, null: false, default: "alex"
31
+ t.string :runtime, null: false, default: "pi"
30
32
  t.string :environment, null: false
31
33
  t.string :repo_path
32
34
  t.string :base_branch
@@ -47,14 +49,39 @@ ActiveRecord::Schema[6.1].define do
47
49
 
48
50
  add_index :solid_agents_runs, :external_key, unique: true
49
51
  add_index :solid_agents_runs, :status
52
+ add_index :solid_agents_runs, :stage
50
53
  add_index :solid_agents_runs, :error_fingerprint
51
54
  add_index :solid_agents_runs, [:source_type, :source_id]
52
55
 
56
+ create_table :solid_agents_work_items, force: :cascade do |t|
57
+ t.references :run, null: false, foreign_key: {to_table: :solid_agents_runs}
58
+ t.string :column_key, null: false, default: "received"
59
+ t.string :title, null: false
60
+ t.text :summary
61
+ t.json :metadata_json, default: {}
62
+ t.timestamps
63
+ end
64
+
65
+ add_index :solid_agents_work_items, :column_key
66
+
67
+ create_table :solid_agents_handoffs, force: :cascade do |t|
68
+ t.references :run, null: false, foreign_key: {to_table: :solid_agents_runs}
69
+ t.string :stage, null: false
70
+ t.string :from_agent, null: false
71
+ t.string :to_agent, null: false
72
+ t.text :note
73
+ t.json :payload, default: {}
74
+ t.timestamps
75
+ end
76
+
77
+ add_index :solid_agents_handoffs, [:run_id, :stage]
78
+
53
79
  create_table :solid_agents_run_events, force: :cascade do |t|
54
80
  t.references :run, null: false, foreign_key: {to_table: :solid_agents_runs}
55
81
  t.string :event_type, null: false
56
82
  t.datetime :event_time, null: false
57
83
  t.text :message, null: false
84
+ t.string :actor
58
85
  t.json :payload, default: {}
59
86
  t.integer :sequence, null: false
60
87
  t.timestamps
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidAgents
4
+ module Runtime
5
+ class PiAdapter < Adapter
6
+ def execute(run:, prompt:)
7
+ command = [SolidAgents.pi_command.to_s, "agent", "run", "--json", "--message", prompt]
8
+ run_command(*command).tap do |result|
9
+ result.metadata[:stage] = run.stage
10
+ result.metadata[:owner] = run.stage_owner
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidAgents
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidAgents
4
+ module Workflow
5
+ STAGES = [
6
+ "received",
7
+ "triaged",
8
+ "repro_test",
9
+ "repro_manual",
10
+ "fixing",
11
+ "verifying",
12
+ "pr_opened",
13
+ "ci_wait",
14
+ "done"
15
+ ].freeze
16
+
17
+ STAGE_TO_AGENT = {
18
+ "received" => "alex",
19
+ "triaged" => "alex",
20
+ "repro_test" => "betty",
21
+ "repro_manual" => "betty",
22
+ "fixing" => "chad",
23
+ "verifying" => "david",
24
+ "pr_opened" => "emma",
25
+ "ci_wait" => "emma",
26
+ "done" => "emma"
27
+ }.freeze
28
+
29
+ FINAL_STAGES = ["done", "failed"].freeze
30
+
31
+ module_function
32
+
33
+ def next_stage(current_stage)
34
+ index = STAGES.index(current_stage)
35
+ return "done" if index.nil? || index == STAGES.length - 1
36
+
37
+ STAGES[index + 1]
38
+ end
39
+
40
+ def stage_agent(stage)
41
+ STAGE_TO_AGENT.fetch(stage.to_s, "alex")
42
+ end
43
+
44
+ def final_stage?(stage)
45
+ FINAL_STAGES.include?(stage.to_s)
46
+ end
47
+ end
48
+ end
data/lib/solid_agents.rb CHANGED
@@ -1,31 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "solid_agents/version"
4
+ require_relative "solid_agents/workflow"
4
5
  require_relative "solid_agents/runtime/adapter"
5
- require_relative "solid_agents/runtime/tinyclaw_adapter"
6
- require_relative "solid_agents/runtime/openclaw_adapter"
6
+ require_relative "solid_agents/runtime/pi_adapter"
7
7
  require_relative "solid_agents/engine"
8
8
 
9
9
  module SolidAgents
10
10
  mattr_accessor :connects_to
11
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"
12
+ mattr_accessor :default_runtime, default: :pi
13
+ mattr_accessor :pi_command, default: "pi"
15
14
  mattr_accessor :default_test_command, default: "bin/rails test"
16
15
  mattr_accessor :max_iterations, default: 8
17
16
 
18
17
  class << self
19
18
  def runtime_adapter(runtime)
20
19
  case runtime.to_sym
21
- when :tinyclaw then SolidAgents::Runtime::TinyclawAdapter.new
22
- when :openclaw then SolidAgents::Runtime::OpenclawAdapter.new
20
+ when :pi then SolidAgents::Runtime::PiAdapter.new
23
21
  else
24
22
  raise ArgumentError, "Unsupported runtime: #{runtime.inspect}"
25
23
  end
26
24
  end
27
25
 
28
- def dispatch_error(source:, agent_key: "fixer")
26
+ def dispatch_error(source:, agent_key: "alex")
29
27
  SolidAgents::Runs::Dispatch.call(source: source, agent_key: agent_key)
30
28
  end
31
29
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_agents
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto
@@ -107,8 +107,8 @@ dependencies:
107
107
  - - ">="
108
108
  - !ruby/object:Gem::Version
109
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.
110
+ description: Solid Agent stores agent runs in its own database and dispatches staged
111
+ workflow tasks to the pi runtime with a built-in Rails UI.
112
112
  email:
113
113
  - kr@kakaruto.com
114
114
  executables: []
@@ -127,9 +127,11 @@ files:
127
127
  - app/models/solid_agents/agent.rb
128
128
  - app/models/solid_agents/artifact.rb
129
129
  - app/models/solid_agents/config.rb
130
+ - app/models/solid_agents/handoff.rb
130
131
  - app/models/solid_agents/record.rb
131
132
  - app/models/solid_agents/run.rb
132
133
  - app/models/solid_agents/run_event.rb
134
+ - app/models/solid_agents/work_item.rb
133
135
  - app/services/solid_agents/runs/context_builder.rb
134
136
  - app/services/solid_agents/runs/dispatch.rb
135
137
  - app/services/solid_agents/runs/executor.rb
@@ -150,9 +152,9 @@ files:
150
152
  - lib/solid_agents.rb
151
153
  - lib/solid_agents/engine.rb
152
154
  - 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/runtime/pi_adapter.rb
155
156
  - lib/solid_agents/version.rb
157
+ - lib/solid_agents/workflow.rb
156
158
  homepage: https://github.com/kaka-ruto/solid_agents
157
159
  licenses:
158
160
  - MIT
@@ -1,12 +0,0 @@
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
@@ -1,12 +0,0 @@
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