rcrewai-rails 0.3.1 → 0.6.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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +123 -0
  3. data/app/controllers/rcrewai/rails/api/v1/crews_controller.rb +1 -1
  4. data/app/controllers/rcrewai/rails/crews_controller.rb +1 -1
  5. data/app/jobs/rcrewai/rails/crew_execution_job.rb +3 -2
  6. data/app/models/rcrewai/rails/active_record_state_store.rb +24 -0
  7. data/app/models/rcrewai/rails/agent.rb +36 -1
  8. data/app/models/rcrewai/rails/crew.rb +70 -3
  9. data/app/models/rcrewai/rails/flow_run.rb +41 -0
  10. data/app/models/rcrewai/rails/flow_state.rb +11 -0
  11. data/app/models/rcrewai/rails/knowledge_source.rb +27 -0
  12. data/app/models/rcrewai/rails/task.rb +39 -1
  13. data/app/views/rcrewai/rails/crews/edit.html.erb +2 -7
  14. data/app/views/rcrewai/rails/crews/new.html.erb +2 -7
  15. data/app/views/rcrewai/rails/crews/show.html.erb +0 -3
  16. data/db/migrate/002_add_config_to_rcrewai_agents.rb +9 -0
  17. data/db/migrate/003_add_output_processing_to_rcrewai_tasks.rb +13 -0
  18. data/db/migrate/004_add_lifecycle_to_rcrewai_crews.rb +10 -0
  19. data/db/migrate/005_add_batch_id_to_rcrewai_executions.rb +6 -0
  20. data/db/migrate/006_create_rcrewai_knowledge_sources.rb +11 -0
  21. data/db/migrate/007_create_rcrewai_flows.rb +24 -0
  22. data/db/migrate/008_add_consensus_agents_to_rcrewai_crews.rb +5 -0
  23. data/db/migrate/009_add_memory_config_to_agents_and_clean_crews.rb +10 -0
  24. data/lib/generators/rcrewai/rails/crew/templates/crew.rb.erb +0 -1
  25. data/lib/generators/rcrewai/rails/install/templates/create_rcrewai_tables.rb +53 -2
  26. data/lib/rcrewai/rails/configuration.rb +4 -1
  27. data/lib/rcrewai/rails/crew_builder.rb +0 -6
  28. data/lib/rcrewai/rails/version.rb +1 -1
  29. data/rcrewai-rails.gemspec +3 -3
  30. metadata +17 -5
  31. data/docs/superpowers/specs/2026-06-16-github-pages-landing-design.md +0 -61
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de3fd7383b01dfbcf053637b30537fb9e78d3541b0fdf09d0d7c92f0f132525e
4
- data.tar.gz: 0f3e47c876aefb405a63abdd5b22ea965afdf438bdc2af46d3154e619d89f42d
3
+ metadata.gz: 92b441696cd97a26aeca352027a7f8e4bdef639f34c452494599d51cb302b8b6
4
+ data.tar.gz: 00466757e89f9b12e46956dcdb38d67a40a938bb4a68d43e0292f79babfb6260
5
5
  SHA512:
6
- metadata.gz: 8c4bdd914f99b4e2f5bb324e2d5658e088698be20834b0f4186a3e6ef7d574330c0de9cdc112970f937858858738800cf80f97cbc88699c1b7397674ba979a2c
7
- data.tar.gz: c93205e5e31f6036f796c616ad623711136a6f2ab0f902a149c02737d3ecea902bafc4d564d3f2c36bcf1cfddd754c6bf8a35aefbcc74f3ecabc4165cf767f87
6
+ metadata.gz: ec0516a3dba432d2b1a6308f89981a65905e110a4ef543ed5a516476ab62f47c9ba86cdc538288e52512841c34e01f5c2e9146970420d6435144922c4e2926a7
7
+ data.tar.gz: 26fb59a9bed5be7db14f7520a57d037c23e46bf572adf7946c98cf6fbe2d67fd3dd682e85942c84b38d9807e612aeb575b104f4c411f1dcd450f3de2cb8ab7b1
data/CHANGELOG.md ADDED
@@ -0,0 +1,123 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are 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
+ ## [0.6.0] - 2026-07-07
11
+
12
+ Tracks rcrewai 0.7.0: adds the `:consensual` crew process and agent-level
13
+ cognitive memory configuration, and requires `rcrewai ~> 0.7`. Includes a
14
+ schema cleanup that removes unused columns (see Removed + the `009` migration).
15
+
16
+ ### Added
17
+ - Support the rcrewai 0.7.0 `:consensual` crew process: `process_type:
18
+ "consensual"` is now valid, a nullable `consensus_agents` column is forwarded to
19
+ the core crew (defaulting to the core's 3 when unset), and the web UI + API
20
+ permit it. Existing sequential/hierarchical crews are unaffected.
21
+ - Agent memory configuration: enable rcrewai 0.6+ cognitive memory per agent via
22
+ the existing `memory_enabled` flag, with `memory_scope` /
23
+ `memory_short_term_limit` columns forwarded to the core agent. The embedder and
24
+ store come from `RcrewAI::Rails.config.default_memory_embedder` /
25
+ `default_memory_store` (set in a host initializer). Memory is off by default, so
26
+ existing agents are unaffected.
27
+
28
+ ### Changed
29
+ - Require `rcrewai ~> 0.7` (was `~> 0.5`).
30
+
31
+ ### Removed
32
+ - Dropped the unused `memory_enabled` and `memory` columns from `rcrewai_crews`.
33
+ Core memory is agent-level; these crew columns were never wired to anything.
34
+ **Migration note:** the `009` migration removes them (reversible).
35
+
36
+ ## [0.5.1] - 2026-07-06
37
+
38
+ ### Fixed
39
+ - Exclude internal `docs/superpowers/` design and plan documents from the packaged
40
+ gem (they were swept in alongside the intended `docs/index.html`). No code
41
+ changes; first published build carrying the 0.4/0.5 parity work.
42
+
43
+ ## [0.5.0] - 2026-07-06
44
+
45
+ Adds the second CrewAI pillar — **Flows** — to the Rails engine as a persistence
46
+ layer, completing the rcrewai 0.4/0.5 feature-parity effort. Additive; existing
47
+ crews, agents, and tasks are unaffected.
48
+
49
+ ### Added
50
+ - Flows persistence: `RcrewAI::Rails::ActiveRecordStateStore` backs rcrewai Flow
51
+ state with a `rcrewai_flow_states` table so flows resume from the DB
52
+ (`flow.restore(state_id)`), and `RcrewAI::Rails::FlowRun` records each kickoff
53
+ (status, state id, inputs, result, timing) via `FlowRun.execute(FlowClass,
54
+ inputs:)`. Flow subclasses are still defined in the host app; the engine adds
55
+ the persistence layer (#13).
56
+
57
+ ## [0.4.0] - 2026-07-06
58
+
59
+ Feature-parity release: brings the rcrewai 0.4/0.5 agent, task, and crew
60
+ capabilities to the Rails engine. Requires `rcrewai ~> 0.5`. All additive —
61
+ existing agents, tasks, and crews build unchanged.
62
+
63
+ ### Added
64
+ - Forward rcrewai 0.5.0 agent options through `RcrewAI::Rails::Agent#to_rcrew_agent`:
65
+ `reasoning`, `max_reasoning_attempts`, `respect_context_window`, and per-agent
66
+ `llm` (from the `llm_config` column). Also fixes a latent gap where the existing
67
+ `max_rpm` and `llm_config` columns were never passed to the core agent. New
68
+ columns are added via a host migration, the install generator, and the test
69
+ schema; all options are emitted only when set, so existing agents are
70
+ unaffected (#6).
71
+ - Forward rcrewai 0.4/0.5 task output-processing options through
72
+ `RcrewAI::Rails::Task#to_rcrew_task`: `output_schema` (structured output),
73
+ `guardrail` (resolved from `guardrail_class` + `guardrail_method_name`),
74
+ `guardrail_max_retries`, `output_file`, `create_directory`, `markdown`, and
75
+ multimodal `attachments`. Options are emitted only when meaningfully set, so
76
+ existing tasks construct unchanged (#7).
77
+ - Forward rcrewai 0.5.0 crew options through `RcrewAI::Rails::Crew#to_rcrew`:
78
+ `planning` / `planning_llm`, and `before_kickoff` / `after_kickoff` lifecycle
79
+ hooks (resolved from `*_class` + `*_method` columns). Options are emitted and
80
+ hooks registered only when configured, so existing crews build unchanged (#9).
81
+ - Batch crew execution (`kickoff_for_each` parity): `Crew#execute_batch_sync` /
82
+ `#execute_batch_async` run the crew once per input set, creating one `Execution`
83
+ per input grouped by a shared `batch_id` (new nullable column). `#batch_executions`
84
+ returns a batch's runs in order. Existing single-run executions are unaffected (#10).
85
+ - Knowledge (RAG) sources: a polymorphic `RcrewAI::Rails::KnowledgeSource`
86
+ (owned by an Agent or a Crew) persists `{source_type, value}` for string, file,
87
+ PDF, CSV, and URL sources. `Agent#to_rcrew_agent` / `Crew#to_rcrew` forward
88
+ active sources as `knowledge_sources:`; the core embeds them lazily at
89
+ execution. Emitted only when sources exist, so existing agents/crews are
90
+ unaffected (#11).
91
+
92
+ ### Changed
93
+ - Require `rcrewai ~> 0.5` (was `~> 0.3`) (#6).
94
+
95
+ ## [0.3.1] - 2026-06-16
96
+
97
+ ### Added
98
+ - GitHub Pages landing + documentation site, served from `/docs` at
99
+ https://gkosmo.github.io/rcrewai-rails/ (#3).
100
+
101
+ ### Fixed
102
+ - Zeitwerk eager-load failure caused by `lib/` being on the autoload paths (#2).
103
+
104
+ ### Changed
105
+ - Point the gemspec `documentation_uri` at the new GitHub Pages site (#3).
106
+
107
+ ## [0.3.0] - 2026-05-12
108
+
109
+ ### Added
110
+ - Adapt the engine to rcrewai 0.3.
111
+ - Test suite (RSpec) covering models, jobs, builders, and tools.
112
+ - GitHub Actions CI workflow.
113
+
114
+ ### Changed
115
+ - Rename generators from `rcrew_a_i` to `rcrewai` namespacing.
116
+
117
+ [Unreleased]: https://github.com/gkosmo/rcrewai-rails/compare/v0.6.0...HEAD
118
+ [0.6.0]: https://github.com/gkosmo/rcrewai-rails/compare/v0.5.1...v0.6.0
119
+ [0.5.1]: https://github.com/gkosmo/rcrewai-rails/compare/v0.5.0...v0.5.1
120
+ [0.5.0]: https://github.com/gkosmo/rcrewai-rails/compare/v0.4.0...v0.5.0
121
+ [0.4.0]: https://github.com/gkosmo/rcrewai-rails/compare/v0.3.1...v0.4.0
122
+ [0.3.1]: https://github.com/gkosmo/rcrewai-rails/compare/v0.3.0...v0.3.1
123
+ [0.3.0]: https://github.com/gkosmo/rcrewai-rails/releases/tag/v0.3.0
@@ -45,7 +45,7 @@ module RcrewAI
45
45
  def crew_params
46
46
  params.require(:crew).permit(
47
47
  :name, :description, :process_type, :verbose,
48
- :memory_enabled, :cache_enabled, :max_rpm, :manager_llm, :active
48
+ :cache_enabled, :max_rpm, :manager_llm, :consensus_agents, :active
49
49
  )
50
50
  end
51
51
  end
@@ -59,7 +59,7 @@ module RcrewAI
59
59
  def crew_params
60
60
  params.require(:crew).permit(
61
61
  :name, :description, :process_type, :verbose,
62
- :memory_enabled, :cache_enabled, :max_rpm, :manager_llm, :active
62
+ :cache_enabled, :max_rpm, :manager_llm, :consensus_agents, :active
63
63
  )
64
64
  end
65
65
  end
@@ -5,10 +5,11 @@ module RcrewAI
5
5
 
6
6
  retry_on StandardError, wait: :exponentially_longer, attempts: 3
7
7
 
8
- def perform(crew, inputs = {})
8
+ def perform(crew, inputs = {}, batch_id: nil)
9
9
  execution = crew.executions.create!(
10
10
  status: "pending",
11
- inputs: inputs
11
+ inputs: inputs,
12
+ batch_id: batch_id
12
13
  )
13
14
 
14
15
  begin
@@ -0,0 +1,24 @@
1
+ module RcrewAI
2
+ module Rails
3
+ # Persists rcrewai Flow state to the DB. Implements the core state-store
4
+ # contract: save(id, hash) / load(id) => hash or nil. Pass an instance as
5
+ # +state_store:+ when constructing a Flow.
6
+ #
7
+ # State is stored as JSON (see FlowState), so values must be JSON-native —
8
+ # the same constraint as the core FileStateStore. A single flow run persists
9
+ # from one process, so saves are effectively serial; a genuinely concurrent
10
+ # first-write for the same state_id would hit the unique index and raise
11
+ # ActiveRecord::RecordNotUnique (fail-loud, by design).
12
+ class ActiveRecordStateStore
13
+ def save(id, hash)
14
+ record = FlowState.find_or_initialize_by(state_id: id)
15
+ record.data = hash
16
+ record.save!
17
+ end
18
+
19
+ def load(id)
20
+ FlowState.find_by(state_id: id)&.data
21
+ end
22
+ end
23
+ end
24
+ end
@@ -6,6 +6,7 @@ module RcrewAI
6
6
  belongs_to :crew
7
7
  has_many :tasks, dependent: :nullify
8
8
  has_many :tools, class_name: 'RcrewAI::Rails::Tool', dependent: :destroy
9
+ has_many :knowledge_sources, as: :owner, class_name: "RcrewAI::Rails::KnowledgeSource", dependent: :destroy
9
10
 
10
11
  validates :name, presence: true
11
12
  validates :role, presence: true
@@ -24,10 +25,44 @@ module RcrewAI
24
25
  verbose: verbose,
25
26
  allow_delegation: allow_delegation,
26
27
  tools: instantiated_tools,
27
- max_iterations: max_iterations
28
+ max_iterations: max_iterations,
29
+ **agent_options
28
30
  )
29
31
  end
30
32
 
33
+ # rcrewai 0.5.0 agent options. Only emit a key when it is meaningfully
34
+ # set, so an all-default record constructs exactly as it did pre-0.5.
35
+ def agent_options
36
+ opts = {}
37
+ opts[:max_rpm] = max_rpm if max_rpm.present? && max_rpm.positive?
38
+ opts[:reasoning] = reasoning if reasoning
39
+ opts[:max_reasoning_attempts] = max_reasoning_attempts if reasoning && max_reasoning_attempts
40
+ opts[:respect_context_window] = respect_context_window if respect_context_window
41
+ opts[:llm] = llm_config.symbolize_keys if llm_config.present?
42
+ sources = rcrew_knowledge_sources
43
+ opts[:knowledge_sources] = sources if sources.any?
44
+ opts[:memory] = memory_options if memory_enabled
45
+ opts
46
+ end
47
+
48
+ # Agent memory config (rcrewai 0.6+). Scalars come from columns; embedder
49
+ # and store come from the engine configuration (set in a host initializer).
50
+ # May return {} — an empty hash still enables memory with core defaults.
51
+ def memory_options
52
+ m = {}
53
+ m[:scope] = memory_scope if memory_scope.present?
54
+ m[:short_term_limit] = memory_short_term_limit if memory_short_term_limit.present?
55
+ embedder = RcrewAI::Rails.config.default_memory_embedder
56
+ store = RcrewAI::Rails.config.default_memory_store
57
+ m[:embedder] = embedder if embedder
58
+ m[:store] = store if store
59
+ m
60
+ end
61
+
62
+ def rcrew_knowledge_sources
63
+ knowledge_sources.active.map(&:to_rcrew_source)
64
+ end
65
+
31
66
  def instantiated_tools
32
67
  return [] if tools.blank?
33
68
 
@@ -6,12 +6,12 @@ module RcrewAI
6
6
  has_many :agents, dependent: :destroy
7
7
  has_many :tasks, dependent: :destroy
8
8
  has_many :executions, dependent: :destroy
9
+ has_many :knowledge_sources, as: :owner, class_name: "RcrewAI::Rails::KnowledgeSource", dependent: :destroy
9
10
 
10
11
  validates :name, presence: true
11
- validates :process_type, inclusion: { in: %w[sequential hierarchical] }
12
+ validates :process_type, inclusion: { in: %w[sequential hierarchical consensual] }
12
13
 
13
14
  serialize :config, coder: JSON
14
- serialize :memory, coder: JSON
15
15
 
16
16
  scope :active, -> { where(active: true) }
17
17
  scope :with_agents, -> { includes(:agents) }
@@ -20,7 +20,8 @@ module RcrewAI
20
20
  crew = RCrewAI::Crew.new(
21
21
  name,
22
22
  process: process_type.to_sym,
23
- verbose: verbose
23
+ verbose: verbose,
24
+ **crew_planning_options
24
25
  )
25
26
 
26
27
  agents.each do |agent|
@@ -31,9 +32,27 @@ module RcrewAI
31
32
  crew.add_task(task.to_rcrew_task)
32
33
  end
33
34
 
35
+ register_kickoff_hooks(crew)
34
36
  crew
35
37
  end
36
38
 
39
+ # Crew construction options (planning, consensus_agents, knowledge). Emit a
40
+ # key only when meaningfully set, so an all-default crew constructs exactly
41
+ # as it did before.
42
+ def crew_planning_options
43
+ opts = {}
44
+ opts[:planning] = planning if planning
45
+ opts[:planning_llm] = planning_llm.to_sym if planning_llm.present?
46
+ sources = rcrew_knowledge_sources
47
+ opts[:knowledge_sources] = sources if sources.any?
48
+ opts[:consensus_agents] = consensus_agents if consensus_agents.present?
49
+ opts
50
+ end
51
+
52
+ def rcrew_knowledge_sources
53
+ knowledge_sources.active.map(&:to_rcrew_source)
54
+ end
55
+
37
56
  def execute_async(inputs = {})
38
57
  CrewExecutionJob.perform_later(self, inputs)
39
58
  end
@@ -42,6 +61,26 @@ module RcrewAI
42
61
  CrewExecutionJob.perform_now(self, inputs)
43
62
  end
44
63
 
64
+ def execute_batch_async(inputs_list)
65
+ batch_id = SecureRandom.uuid
66
+ normalize_batch_inputs(inputs_list).each do |inputs|
67
+ CrewExecutionJob.perform_later(self, inputs, batch_id: batch_id)
68
+ end
69
+ batch_id
70
+ end
71
+
72
+ def execute_batch_sync(inputs_list)
73
+ batch_id = SecureRandom.uuid
74
+ normalize_batch_inputs(inputs_list).each do |inputs|
75
+ CrewExecutionJob.perform_now(self, inputs, batch_id: batch_id)
76
+ end
77
+ { batch_id: batch_id, executions: batch_executions(batch_id).to_a }
78
+ end
79
+
80
+ def batch_executions(batch_id)
81
+ executions.where(batch_id: batch_id).order(:created_at, :id)
82
+ end
83
+
45
84
  def last_execution
46
85
  executions.order(created_at: :desc).first
47
86
  end
@@ -55,6 +94,34 @@ module RcrewAI
55
94
  average_duration: executions.successful.average(:duration_seconds)
56
95
  }
57
96
  end
97
+
98
+ private
99
+
100
+ # Wraps a single inputs hash into a one-element array; leaves an array of
101
+ # hashes as-is. Avoids Array()'s hash-destructuring (Array({a:1}) => [[:a,1]]).
102
+ def normalize_batch_inputs(inputs_list)
103
+ inputs_list.is_a?(Hash) ? [inputs_list] : Array(inputs_list)
104
+ end
105
+
106
+ # Registers before/after kickoff hooks resolved from *_class + *_method
107
+ # columns, mirroring the guardrail/callback pattern. No-op when unconfigured.
108
+ def register_kickoff_hooks(crew)
109
+ if (before = hook_callable(before_kickoff_class, before_kickoff_method))
110
+ crew.before_kickoff { |inputs| before.call(inputs) }
111
+ end
112
+
113
+ if (after = hook_callable(after_kickoff_class, after_kickoff_method))
114
+ crew.after_kickoff { |result| after.call(result) }
115
+ end
116
+ end
117
+
118
+ # Resolves a *_class + *_method pair to a callable; nil when either is blank.
119
+ def hook_callable(class_name, method_name)
120
+ return nil unless class_name.present? && method_name.present?
121
+
122
+ klass = class_name.constantize
123
+ ->(arg) { klass.new.send(method_name, arg) }
124
+ end
58
125
  end
59
126
  end
60
127
  end
@@ -0,0 +1,41 @@
1
+ module RcrewAI
2
+ module Rails
3
+ class FlowRun < ApplicationRecord
4
+ self.table_name = "rcrewai_flow_runs"
5
+
6
+ serialize :inputs, coder: JSON
7
+ serialize :result, coder: JSON
8
+
9
+ validates :flow_class, presence: true
10
+ validates :status, inclusion: { in: %w[pending running completed failed] }
11
+
12
+ scope :successful, -> { where(status: "completed") }
13
+ scope :failed, -> { where(status: "failed") }
14
+
15
+ # Runs a Flow subclass (a Class or its String name) with an AR-backed state
16
+ # store, wrapped in a run record. Returns the run. Re-raises on failure
17
+ # after recording it.
18
+ def self.execute(flow_class, inputs: {})
19
+ klass = flow_class.is_a?(String) ? flow_class.constantize : flow_class
20
+ run = create!(flow_class: klass.name, status: "pending", inputs: inputs)
21
+ run.run!(klass, inputs)
22
+ run
23
+ end
24
+
25
+ def run!(klass, inputs)
26
+ update!(status: "running", started_at: Time.current)
27
+ flow = klass.new(state_store: ActiveRecordStateStore.new)
28
+ state = flow.kickoff(inputs: inputs)
29
+ update!(
30
+ status: "completed",
31
+ state_id: state.id,
32
+ result: state.to_h,
33
+ completed_at: Time.current
34
+ )
35
+ rescue => e
36
+ update!(status: "failed", error_message: e.message, completed_at: Time.current)
37
+ raise
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,11 @@
1
+ module RcrewAI
2
+ module Rails
3
+ class FlowState < ApplicationRecord
4
+ self.table_name = "rcrewai_flow_states"
5
+
6
+ serialize :data, coder: JSON
7
+
8
+ validates :state_id, presence: true, uniqueness: true
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,27 @@
1
+ module RcrewAI
2
+ module Rails
3
+ class KnowledgeSource < ApplicationRecord
4
+ self.table_name = "rcrewai_knowledge_sources"
5
+
6
+ TYPE_MAP = {
7
+ "string" => RCrewAI::Knowledge::StringSource,
8
+ "file" => RCrewAI::Knowledge::FileSource,
9
+ "pdf" => RCrewAI::Knowledge::PdfSource,
10
+ "csv" => RCrewAI::Knowledge::CsvSource,
11
+ "url" => RCrewAI::Knowledge::UrlSource,
12
+ }.freeze
13
+
14
+ belongs_to :owner, polymorphic: true
15
+
16
+ validates :source_type, inclusion: { in: TYPE_MAP.keys }
17
+ validates :value, presence: true
18
+
19
+ scope :active, -> { where(active: true) }
20
+
21
+ # Maps this row to the matching core Source object.
22
+ def to_rcrew_source
23
+ TYPE_MAP.fetch(source_type).new(value)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -15,6 +15,8 @@ module RcrewAI
15
15
  serialize :output_json, coder: JSON
16
16
  serialize :output_pydantic, coder: JSON
17
17
  serialize :tools, coder: JSON, type: Array
18
+ serialize :output_schema, coder: JSON
19
+ serialize :attachments, coder: JSON, type: Array
18
20
 
19
21
  scope :ordered, -> { order(:order_index) }
20
22
 
@@ -27,10 +29,28 @@ module RcrewAI
27
29
  context: context,
28
30
  async: async_execution,
29
31
  tools: instantiated_tools,
30
- callback: callback_method
32
+ callback: callback_method,
33
+ **task_output_options
31
34
  )
32
35
  end
33
36
 
37
+ # rcrewai 0.4/0.5 task output-processing options. Only emit a key when it
38
+ # is meaningfully set, so an all-default record constructs exactly as it
39
+ # did before these options existed.
40
+ def task_output_options
41
+ opts = {}
42
+ opts[:output_schema] = output_schema.deep_symbolize_keys if output_schema.present?
43
+ opts[:guardrail] = guardrail_callable if guardrail_callable
44
+ opts[:guardrail_max_retries] = guardrail_max_retries if guardrail_callable && guardrail_max_retries
45
+ opts[:output_file] = output_file if output_file.present?
46
+ # Core defaults create_directory to true; only forward when explicitly
47
+ # disabled, so an all-default record still emits nothing.
48
+ opts[:create_directory] = false if create_directory == false
49
+ opts[:markdown] = markdown if markdown
50
+ opts[:attachments] = normalized_attachments if attachments.present?
51
+ opts
52
+ end
53
+
34
54
 
35
55
  def instantiated_tools
36
56
  return [] if tools.blank?
@@ -65,6 +85,24 @@ module RcrewAI
65
85
  klass = callback_class.constantize
66
86
  ->(output) { klass.new.send(callback_method_name, output) }
67
87
  end
88
+
89
+ # Resolves guardrail_class + guardrail_method_name to a callable returning
90
+ # the core [ok, value_or_error] contract. Mirrors callback_method. nil when
91
+ # not configured.
92
+ def guardrail_callable
93
+ return nil unless guardrail_class.present? && guardrail_method_name.present?
94
+
95
+ klass = guardrail_class.constantize
96
+ ->(output) { klass.new.send(guardrail_method_name, output) }
97
+ end
98
+
99
+ # Symbolizes each attachment hash so { "type" => "image", "url" => ... }
100
+ # becomes { type: :image, url: ... } as the core Multimodal builder expects.
101
+ def normalized_attachments
102
+ attachments.map do |att|
103
+ att.symbolize_keys.tap { |h| h[:type] = h[:type].to_sym if h[:type] }
104
+ end
105
+ end
68
106
  end
69
107
  end
70
108
  end
@@ -34,7 +34,8 @@
34
34
  <%= form.select :process_type,
35
35
  options_for_select([
36
36
  ['Sequential', 'sequential'],
37
- ['Hierarchical', 'hierarchical']
37
+ ['Hierarchical', 'hierarchical'],
38
+ ['Consensual', 'consensual']
38
39
  ], @crew.process_type),
39
40
  { prompt: 'Select process type' },
40
41
  { class: "form-select" } %>
@@ -46,12 +47,6 @@
46
47
  <span class="form-help">Enable verbose logging</span>
47
48
  </div>
48
49
 
49
- <div class="form-group">
50
- <%= form.label :memory_enabled, class: "form-label" %>
51
- <%= form.check_box :memory_enabled, class: "form-checkbox" %>
52
- <span class="form-help">Enable memory for agents</span>
53
- </div>
54
-
55
50
  <div class="form-group">
56
51
  <%= form.label :cache_enabled, class: "form-label" %>
57
52
  <%= form.check_box :cache_enabled, class: "form-checkbox" %>
@@ -31,7 +31,8 @@
31
31
  <%= form.select :process_type,
32
32
  options_for_select([
33
33
  ['Sequential', 'sequential'],
34
- ['Hierarchical', 'hierarchical']
34
+ ['Hierarchical', 'hierarchical'],
35
+ ['Consensual', 'consensual']
35
36
  ], @crew.process_type),
36
37
  { prompt: 'Select process type' },
37
38
  { class: "form-select" } %>
@@ -43,12 +44,6 @@
43
44
  <span class="form-help">Enable verbose logging</span>
44
45
  </div>
45
46
 
46
- <div class="form-group">
47
- <%= form.label :memory_enabled, class: "form-label" %>
48
- <%= form.check_box :memory_enabled, class: "form-checkbox" %>
49
- <span class="form-help">Enable memory for agents</span>
50
- </div>
51
-
52
47
  <div class="form-group">
53
48
  <%= form.label :cache_enabled, class: "form-label" %>
54
49
  <%= form.check_box :cache_enabled, class: "form-checkbox" %>
@@ -14,9 +14,6 @@
14
14
  <div class="meta-item">
15
15
  <strong>Process Type:</strong> <%= @crew.process_type %>
16
16
  </div>
17
- <div class="meta-item">
18
- <strong>Memory:</strong> <%= @crew.memory_enabled ? "Enabled" : "Disabled" %>
19
- </div>
20
17
  <div class="meta-item">
21
18
  <strong>Cache:</strong> <%= @crew.cache_enabled ? "Enabled" : "Disabled" %>
22
19
  </div>
@@ -0,0 +1,9 @@
1
+ class AddConfigToRcrewaiAgents < ActiveRecord::Migration[7.0]
2
+ def change
3
+ # max_rpm and llm_config already exist on rcrewai_agents from the
4
+ # original create table; only the 0.5.0 additions are new.
5
+ add_column :rcrewai_agents, :reasoning, :boolean, default: false, null: false
6
+ add_column :rcrewai_agents, :max_reasoning_attempts, :integer, default: 3
7
+ add_column :rcrewai_agents, :respect_context_window, :boolean, default: false, null: false
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ class AddOutputProcessingToRcrewaiTasks < ActiveRecord::Migration[7.0]
2
+ def change
3
+ # output_file already exists on rcrewai_tasks from the original create
4
+ # table; only the new 0.4/0.5 output-processing options are added here.
5
+ add_column :rcrewai_tasks, :output_schema, :text
6
+ add_column :rcrewai_tasks, :guardrail_class, :string
7
+ add_column :rcrewai_tasks, :guardrail_method_name, :string
8
+ add_column :rcrewai_tasks, :guardrail_max_retries, :integer, default: 3
9
+ add_column :rcrewai_tasks, :create_directory, :boolean, default: true
10
+ add_column :rcrewai_tasks, :markdown, :boolean, default: false
11
+ add_column :rcrewai_tasks, :attachments, :text
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ class AddLifecycleToRcrewaiCrews < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_column :rcrewai_crews, :planning, :boolean, default: false, null: false
4
+ add_column :rcrewai_crews, :planning_llm, :string
5
+ add_column :rcrewai_crews, :before_kickoff_class, :string
6
+ add_column :rcrewai_crews, :before_kickoff_method, :string
7
+ add_column :rcrewai_crews, :after_kickoff_class, :string
8
+ add_column :rcrewai_crews, :after_kickoff_method, :string
9
+ end
10
+ end
@@ -0,0 +1,6 @@
1
+ class AddBatchIdToRcrewaiExecutions < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_column :rcrewai_executions, :batch_id, :string
4
+ add_index :rcrewai_executions, :batch_id
5
+ end
6
+ end
@@ -0,0 +1,11 @@
1
+ class CreateRcrewaiKnowledgeSources < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :rcrewai_knowledge_sources do |t|
4
+ t.references :owner, polymorphic: true, null: false
5
+ t.string :source_type, null: false
6
+ t.text :value, null: false
7
+ t.boolean :active, default: true, null: false
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,24 @@
1
+ class CreateRcrewaiFlows < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :rcrewai_flow_states do |t|
4
+ t.string :state_id, null: false
5
+ t.text :data, null: false
6
+ t.timestamps
7
+ end
8
+ add_index :rcrewai_flow_states, :state_id, unique: true
9
+
10
+ create_table :rcrewai_flow_runs do |t|
11
+ t.string :flow_class, null: false
12
+ t.string :state_id
13
+ t.string :status, null: false
14
+ t.text :inputs
15
+ t.text :result
16
+ t.string :error_message
17
+ t.datetime :started_at
18
+ t.datetime :completed_at
19
+ t.timestamps
20
+ end
21
+ add_index :rcrewai_flow_runs, :status
22
+ add_index :rcrewai_flow_runs, :state_id
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ class AddConsensusAgentsToRcrewaiCrews < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_column :rcrewai_crews, :consensus_agents, :integer
4
+ end
5
+ end
@@ -0,0 +1,10 @@
1
+ class AddMemoryConfigToAgentsAndCleanCrews < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_column :rcrewai_agents, :memory_scope, :string
4
+ add_column :rcrewai_agents, :memory_short_term_limit, :integer
5
+
6
+ # Core memory is agent-level; these crew columns were never used.
7
+ remove_column :rcrewai_crews, :memory_enabled, :boolean, default: false
8
+ remove_column :rcrewai_crews, :memory, :text
9
+ end
10
+ end
@@ -7,7 +7,6 @@ class <%= class_name %>Crew
7
7
  description: "<%= crew_description %>",
8
8
  process_type: "<%= process_type %>",
9
9
  verbose: true,
10
- memory_enabled: false,
11
10
  cache_enabled: true
12
11
  )
13
12
 
@@ -5,12 +5,17 @@ class CreateRcrewaiTables < ActiveRecord::Migration[7.0]
5
5
  t.text :description
6
6
  t.string :process_type, default: "sequential"
7
7
  t.boolean :verbose, default: false
8
- t.boolean :memory_enabled, default: false
9
8
  t.boolean :cache_enabled, default: false
10
9
  t.integer :max_rpm
11
10
  t.string :manager_llm
11
+ t.integer :consensus_agents
12
+ t.boolean :planning, default: false, null: false
13
+ t.string :planning_llm
14
+ t.string :before_kickoff_class
15
+ t.string :before_kickoff_method
16
+ t.string :after_kickoff_class
17
+ t.string :after_kickoff_method
12
18
  t.text :config
13
- t.text :memory
14
19
  t.boolean :active, default: true
15
20
 
16
21
  t.timestamps
@@ -26,11 +31,16 @@ class CreateRcrewaiTables < ActiveRecord::Migration[7.0]
26
31
  t.text :goal
27
32
  t.text :backstory
28
33
  t.boolean :memory_enabled, default: false
34
+ t.string :memory_scope
35
+ t.integer :memory_short_term_limit
29
36
  t.boolean :verbose, default: false
30
37
  t.boolean :allow_delegation, default: false
31
38
  t.text :tools
32
39
  t.integer :max_iterations, default: 25
33
40
  t.integer :max_rpm
41
+ t.boolean :reasoning, default: false, null: false
42
+ t.integer :max_reasoning_attempts, default: 3
43
+ t.boolean :respect_context_window, default: false, null: false
34
44
  t.text :llm_config
35
45
  t.boolean :active, default: true
36
46
 
@@ -48,6 +58,13 @@ class CreateRcrewaiTables < ActiveRecord::Migration[7.0]
48
58
  t.text :output_json
49
59
  t.text :output_pydantic
50
60
  t.string :output_file
61
+ t.text :output_schema
62
+ t.string :guardrail_class
63
+ t.string :guardrail_method_name
64
+ t.integer :guardrail_max_retries, default: 3
65
+ t.boolean :create_directory, default: true
66
+ t.boolean :markdown, default: false
67
+ t.text :attachments
51
68
  t.text :tools
52
69
  t.string :callback_class
53
70
  t.string :callback_method_name
@@ -86,12 +103,14 @@ class CreateRcrewaiTables < ActiveRecord::Migration[7.0]
86
103
  t.datetime :started_at
87
104
  t.datetime :completed_at
88
105
  t.integer :duration_seconds
106
+ t.string :batch_id
89
107
 
90
108
  t.timestamps
91
109
  end
92
110
 
93
111
  add_index :rcrewai_executions, :status
94
112
  add_index :rcrewai_executions, :created_at
113
+ add_index :rcrewai_executions, :batch_id
95
114
 
96
115
  create_table :rcrewai_execution_logs do |t|
97
116
  t.references :execution, null: false, foreign_key: { to_table: :rcrewai_executions }
@@ -105,5 +124,37 @@ class CreateRcrewaiTables < ActiveRecord::Migration[7.0]
105
124
 
106
125
  add_index :rcrewai_execution_logs, :level
107
126
  add_index :rcrewai_execution_logs, :timestamp
127
+
128
+ create_table :rcrewai_knowledge_sources do |t|
129
+ t.references :owner, polymorphic: true, null: false
130
+ t.string :source_type, null: false
131
+ t.text :value, null: false
132
+ t.boolean :active, default: true, null: false
133
+
134
+ t.timestamps
135
+ end
136
+
137
+ create_table :rcrewai_flow_states do |t|
138
+ t.string :state_id, null: false
139
+ t.text :data, null: false
140
+
141
+ t.timestamps
142
+ end
143
+ add_index :rcrewai_flow_states, :state_id, unique: true
144
+
145
+ create_table :rcrewai_flow_runs do |t|
146
+ t.string :flow_class, null: false
147
+ t.string :state_id
148
+ t.string :status, null: false
149
+ t.text :inputs
150
+ t.text :result
151
+ t.string :error_message
152
+ t.datetime :started_at
153
+ t.datetime :completed_at
154
+
155
+ t.timestamps
156
+ end
157
+ add_index :rcrewai_flow_runs, :status
158
+ add_index :rcrewai_flow_runs, :state_id
108
159
  end
109
160
  end
@@ -3,7 +3,8 @@ module RcrewAI
3
3
  class Configuration
4
4
  attr_accessor :job_queue_name, :enable_web_ui, :persistence_backend,
5
5
  :default_llm_provider, :default_llm_model, :max_retries,
6
- :timeout, :enable_logging, :log_level, :async_execution
6
+ :timeout, :enable_logging, :log_level, :async_execution,
7
+ :default_memory_embedder, :default_memory_store
7
8
 
8
9
  def initialize
9
10
  @job_queue_name = "default"
@@ -16,6 +17,8 @@ module RcrewAI
16
17
  @enable_logging = true
17
18
  @log_level = :info
18
19
  @async_execution = true # Use ActiveJob for async by default
20
+ @default_memory_embedder = nil
21
+ @default_memory_store = nil
19
22
  end
20
23
  end
21
24
  end
@@ -26,11 +26,6 @@ module RcrewAI
26
26
  @process_type || :sequential
27
27
  end
28
28
 
29
- def memory_enabled(enabled = nil)
30
- @memory_enabled = enabled unless enabled.nil?
31
- @memory_enabled || false
32
- end
33
-
34
29
  def cache_enabled(enabled = nil)
35
30
  @cache_enabled = enabled unless enabled.nil?
36
31
  @cache_enabled || true
@@ -66,7 +61,6 @@ module RcrewAI
66
61
  RcrewAI::Rails::Crew.find_or_create_by(name: self.class.crew_name) do |crew|
67
62
  crew.description = self.class.crew_description
68
63
  crew.process_type = self.class.process_type.to_s
69
- crew.memory_enabled = self.class.memory_enabled
70
64
  crew.cache_enabled = self.class.cache_enabled
71
65
  crew.verbose = verbose?
72
66
  end
@@ -1,6 +1,6 @@
1
1
  module RcrewAI
2
2
  module Rails
3
- VERSION = "0.3.1"
3
+ VERSION = "0.6.0"
4
4
  Version = VERSION
5
5
  end
6
6
  end
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
28
28
 
29
29
  spec.metadata["homepage_uri"] = spec.homepage
30
30
  spec.metadata["source_code_uri"] = spec.homepage
31
- spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
31
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/master/CHANGELOG.md"
32
32
  spec.metadata["documentation_uri"] = "https://gkosmo.github.io/rcrewai-rails/"
33
33
  spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
34
34
  spec.metadata["wiki_uri"] = "#{spec.homepage}/wiki"
@@ -37,7 +37,7 @@ Gem::Specification.new do |spec|
37
37
  spec.files = Dir.chdir(__dir__) do
38
38
  `git ls-files -z`.split("\x0").reject do |f|
39
39
  (File.expand_path(f) == __FILE__) ||
40
- f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
40
+ f.start_with?(*%w[bin/ test/ spec/ features/ docs/superpowers/ .git .github appveyor Gemfile])
41
41
  end
42
42
  end
43
43
  spec.bindir = "exe"
@@ -45,7 +45,7 @@ Gem::Specification.new do |spec|
45
45
  spec.require_paths = ["lib"]
46
46
 
47
47
  # Core dependency
48
- spec.add_dependency "rcrewai", "~> 0.3"
48
+ spec.add_dependency "rcrewai", "~> 0.7"
49
49
 
50
50
  # Rails dependencies
51
51
  spec.add_dependency "rails", ">= 7.0.0"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rcrewai-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - gkosmo
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '0.3'
18
+ version: '0.7'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '0.3'
25
+ version: '0.7'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rails
28
28
  requirement: !ruby/object:Gem::Requirement
@@ -195,6 +195,7 @@ extensions: []
195
195
  extra_rdoc_files: []
196
196
  files:
197
197
  - ".tool-versions"
198
+ - CHANGELOG.md
198
199
  - MIT-LICENSE
199
200
  - README.md
200
201
  - Rakefile
@@ -208,11 +209,15 @@ files:
208
209
  - app/controllers/rcrewai/rails/tools_controller.rb
209
210
  - app/jobs/rcrewai/rails/crew_execution_job.rb
210
211
  - app/jobs/rcrewai/rails/task_execution_job.rb
212
+ - app/models/rcrewai/rails/active_record_state_store.rb
211
213
  - app/models/rcrewai/rails/agent.rb
212
214
  - app/models/rcrewai/rails/application_record.rb
213
215
  - app/models/rcrewai/rails/crew.rb
214
216
  - app/models/rcrewai/rails/execution.rb
215
217
  - app/models/rcrewai/rails/execution_log.rb
218
+ - app/models/rcrewai/rails/flow_run.rb
219
+ - app/models/rcrewai/rails/flow_state.rb
220
+ - app/models/rcrewai/rails/knowledge_source.rb
216
221
  - app/models/rcrewai/rails/task.rb
217
222
  - app/models/rcrewai/rails/task_assignment.rb
218
223
  - app/models/rcrewai/rails/task_dependency.rb
@@ -238,8 +243,15 @@ files:
238
243
  - app/views/rcrewai/rails/tools/show.html.erb
239
244
  - config/routes.rb
240
245
  - db/migrate/001_add_agent_to_tasks.rb
246
+ - db/migrate/002_add_config_to_rcrewai_agents.rb
247
+ - db/migrate/003_add_output_processing_to_rcrewai_tasks.rb
248
+ - db/migrate/004_add_lifecycle_to_rcrewai_crews.rb
249
+ - db/migrate/005_add_batch_id_to_rcrewai_executions.rb
250
+ - db/migrate/006_create_rcrewai_knowledge_sources.rb
251
+ - db/migrate/007_create_rcrewai_flows.rb
252
+ - db/migrate/008_add_consensus_agents_to_rcrewai_crews.rb
253
+ - db/migrate/009_add_memory_config_to_agents_and_clean_crews.rb
241
254
  - docs/index.html
242
- - docs/superpowers/specs/2026-06-16-github-pages-landing-design.md
243
255
  - lib/generators/rcrewai/rails/crew/crew_generator.rb
244
256
  - lib/generators/rcrewai/rails/crew/templates/agent.rb.erb
245
257
  - lib/generators/rcrewai/rails/crew/templates/crew.rb.erb
@@ -265,7 +277,7 @@ licenses:
265
277
  metadata:
266
278
  homepage_uri: https://github.com/gkosmo/rcrewai-rails
267
279
  source_code_uri: https://github.com/gkosmo/rcrewai-rails
268
- changelog_uri: https://github.com/gkosmo/rcrewai-rails/blob/main/CHANGELOG.md
280
+ changelog_uri: https://github.com/gkosmo/rcrewai-rails/blob/master/CHANGELOG.md
269
281
  documentation_uri: https://gkosmo.github.io/rcrewai-rails/
270
282
  bug_tracker_uri: https://github.com/gkosmo/rcrewai-rails/issues
271
283
  wiki_uri: https://github.com/gkosmo/rcrewai-rails/wiki
@@ -1,61 +0,0 @@
1
- # GitHub Pages Landing Site for `rcrewai-rails`
2
-
3
- **Date:** 2026-06-16
4
- **Status:** Approved
5
-
6
- ## Goal
7
-
8
- Publish a polished landing + documentation page for the `rcrewai-rails` gem,
9
- served via GitHub Pages from the `/docs` folder on `master`. The page gives a
10
- strong first impression and doubles as practical usage documentation.
11
-
12
- ## Constraints
13
-
14
- - **Single self-contained file:** `docs/index.html`. No build step, no
15
- framework, no JavaScript dependencies, no external network requests.
16
- - **Styling:** Inline `<style>` in the document. Clean light / Rails-y look —
17
- off-white background, crimson Rails-style accent, system sans-serif for prose,
18
- monospace for code. Code blocks styled with CSS only (dark block, monospace,
19
- soft border) — no syntax-token coloring.
20
- - **Content accuracy:** All copy drawn from the existing `README.md` and
21
- `rcrewai-rails.gemspec` so the page stays correct.
22
-
23
- ## Hosting
24
-
25
- GitHub Pages configured to serve from the `master` branch `/docs` folder. The
26
- resulting URL is `https://gkosmo.github.io/rcrewai-rails/`. (Enabling Pages in
27
- the repo settings is a manual GitHub step the user performs; this spec only
28
- produces the file.)
29
-
30
- ## Page Structure
31
-
32
- Single scrolling page with a sticky top navigation:
33
-
34
- 1. **Sticky header** — gem name, tagline, anchor links (Features · Install ·
35
- Usage · Tools · API), GitHub link.
36
- 2. **Hero** — name, one-line pitch, `gem 'rcrewai-rails'` snippet, CTA buttons
37
- (GitHub repo, RubyGems).
38
- 3. **Features grid** — 6 cards: ActiveRecord persistence, ActiveJob integration,
39
- Rails generators, Web dashboard, Multi-LLM support, Rails-specific tools.
40
- 4. **Installation** — Gemfile, `bundle install`, install generator, `db:migrate`,
41
- manual route mounting.
42
- 5. **Configuration** — the initializer example from the README.
43
- 6. **Usage** — generator command, programmatic crew example, async/sync execution.
44
- 7. **Rails Tools** — tool ecosystem (ActiveRecord, ActionMailer, Cache,
45
- ActiveStorage, Logger) with the `DataAnalystAgent` example.
46
- 8. **Web UI & Models** — what `/rcrewai` provides + the ActiveRecord model list.
47
- 9. **API endpoints** — the JSON API table.
48
- 10. **Footer** — MIT license, links.
49
-
50
- ## Additional Change
51
-
52
- Update `rcrewai-rails.gemspec`:
53
- `spec.metadata["documentation_uri"]` from
54
- `https://gkosmo.github.io/rcrewAI/` to
55
- `https://gkosmo.github.io/rcrewai-rails/`.
56
-
57
- ## Out of Scope
58
-
59
- - Multi-page docs / Jekyll.
60
- - JavaScript-driven syntax highlighting.
61
- - Automated Pages deployment workflow (GitHub builds static `/docs` directly).