rcrewai-rails 0.6.1 → 0.7.1

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -1
  3. data/README.md +78 -0
  4. data/app/controllers/rcrewai/rails/observations_controller.rb +27 -0
  5. data/app/jobs/rcrewai/rails/crew_execution_job.rb +18 -29
  6. data/app/models/rcrewai/rails/execution.rb +6 -0
  7. data/app/models/rcrewai/rails/span.rb +57 -0
  8. data/app/models/rcrewai/rails/span_event.rb +19 -0
  9. data/app/views/rcrewai/rails/observations/_span.html.erb +51 -0
  10. data/app/views/rcrewai/rails/observations/costs.html.erb +52 -0
  11. data/app/views/rcrewai/rails/observations/show.html.erb +47 -0
  12. data/config/routes.rb +6 -0
  13. data/db/migrate/002_add_config_to_rcrewai_agents.rb +1 -1
  14. data/db/migrate/003_add_output_processing_to_rcrewai_tasks.rb +1 -1
  15. data/db/migrate/004_add_lifecycle_to_rcrewai_crews.rb +1 -1
  16. data/db/migrate/005_add_batch_id_to_rcrewai_executions.rb +1 -1
  17. data/db/migrate/006_create_rcrewai_knowledge_sources.rb +1 -1
  18. data/db/migrate/007_create_rcrewai_flows.rb +1 -1
  19. data/db/migrate/008_add_consensus_agents_to_rcrewai_crews.rb +1 -1
  20. data/db/migrate/010_create_rcrewai_spans.rb +42 -0
  21. data/db/migrate/011_add_observation_rollups_to_rcrewai_executions.rb +8 -0
  22. data/docs/api.html +223 -0
  23. data/docs/capabilities.html +268 -0
  24. data/docs/examples.html +257 -0
  25. data/docs/getting-started.html +275 -0
  26. data/docs/index.html +57 -207
  27. data/lib/generators/rcrewai/rails/install/install_generator.rb +28 -16
  28. data/lib/generators/rcrewai/rails/install/templates/create_rcrewai_tables.rb +61 -1
  29. data/lib/generators/rcrewai/rails/install/templates/rcrewai.rb +17 -0
  30. data/lib/rcrewai/rails/configuration.rb +10 -1
  31. data/lib/rcrewai/rails/observation/collector.rb +301 -0
  32. data/lib/rcrewai/rails/observation/pruner.rb +48 -0
  33. data/lib/rcrewai/rails/observation/rollup.rb +60 -0
  34. data/lib/rcrewai/rails/observation/span_stack.rb +76 -0
  35. data/lib/rcrewai/rails/observation/writer.rb +89 -0
  36. data/lib/rcrewai/rails/version.rb +1 -1
  37. data/lib/rcrewai/rails.rb +13 -0
  38. data/lib/tasks/rcrewai_observation.rake +10 -0
  39. data/rcrewai-rails.gemspec +1 -1
  40. metadata +21 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 42ec86061d4e3a880fa4c985abc0f2ec55e6c7868b95c65d62f7d6de27324042
4
- data.tar.gz: f73a244bd91a867dd450f94d5fe82ab02e968f62e0a82e311a8e9e736be99e46
3
+ metadata.gz: ec3c830730933e9510d7749b89decd73af35f5b69e5f6f441a21d0cca9290b61
4
+ data.tar.gz: 698877cbbaf6cd49924144a585b5d32a4817ddd1e51307d34cd488f51ba5d0a7
5
5
  SHA512:
6
- metadata.gz: 366cdfeb973451b6144514e72bcfe07e3dc3269e0d9bf093f9c73949f2f660ce6f3b8f136cf3fb1af7e27f08b5210da4c785b0f3e6ac5a9827379f764733f151
7
- data.tar.gz: b91f369cbdd7141f83e36a407742cc31af9ecff0a3563668ec50b91dd6689c82f2bd2cd40c3db6753ef693bd535e90782cf51cfa4afc2a074a2012683d20ed74
6
+ metadata.gz: 94f1e5d38e34c5a8668a4c1be005d3fe4cf6e6e29353ea580de7581485262948af2824f166feb58e4903e59e3aef8dfd3eb1c3ecf8642da021b1f15e3025e89d
7
+ data.tar.gz: 71e6c33d7e34a248ce650556a1cb5bc900d20f282f02fccb82fe2fadd71744ea45fddb618962da777488213084f0048057817e6572f46f27320d40d685b6e14f
data/CHANGELOG.md CHANGED
@@ -7,6 +7,61 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.1] - 2026-08-13
11
+
12
+ ### Fixed
13
+ - **`rails db:migrate` raised `NameError` on 0.7.0.** The engine registers an
14
+ `RcrewAI` acronym inflection (Zeitwerk needs it to resolve
15
+ `rcrewai/rails/span.rb` to `RcrewAI::Rails::Span`), so Rails camelizes
16
+ `create_rcrewai_tables` to `CreateRcrewAITables` — but the shipped migrations
17
+ declared `CreateRcrewaiTables`. Rails resolves a migration's class from its
18
+ filename, so following the documented install verbatim failed. Nine migrations
19
+ in `db/migrate` and the install template were affected; all now declare the
20
+ name Rails derives. Thanks to the user who reported this from a real install.
21
+
22
+ **If you already installed 0.7.0** and renamed the class by hand, no action is
23
+ needed — your migration works. Otherwise, delete the copied migration, upgrade,
24
+ and re-run the install (or `rcrew_ai_rails:install:migrations`).
25
+
26
+ ## [0.7.0] - 2026-08-13
27
+
28
+ ### Added
29
+ - Observation engine: span-tree tracing of every crew execution with per-agent,
30
+ per-LLM-call and per-tool-call detail (timings, token counts, cost). Includes a
31
+ trace waterfall at `/rcrewai/executions/:id/observation`, a cost/performance
32
+ dashboard at `/rcrewai/observations/costs`, live monitoring over Turbo Streams,
33
+ and a `rcrewai:observation:prune` rake task with retention configuration
34
+ (`observation_retention_days`).
35
+
36
+ ### Fixed
37
+ - The install generator only wrote an initializer: it never copied a migration
38
+ and never mounted the engine, so following the documented install produced no
39
+ database tables and no reachable UI. It now creates a timestamped
40
+ `create_rcrewai_tables` migration, mounts the engine at `/rcrewai`, and
41
+ installs the fully documented initializer.
42
+ - The install migration template was missing the `rcrewai_tools` table and the
43
+ tasks table's `agent` reference, so a fresh install shipped a `Tool` model with
44
+ no backing table. The template now matches the schema the test suite validates.
45
+
46
+ ### Deprecated
47
+ - `Execution#log` and `ExecutionLog`, superseded by the observation engine. Both
48
+ still work and now emit a deprecation warning; scheduled for removal one minor
49
+ version after this release.
50
+
51
+ ### Requires
52
+ - `rcrewai >= 0.7.1` for agent-level tracing. On earlier 0.7.x versions traces
53
+ contain only crew-level spans.
54
+
55
+ ### Upgrading
56
+ - **Existing installs need two new migrations.** Do not run the install
57
+ generator (it creates every table and will fail on duplicates). Instead run
58
+ `rails rcrew_ai_rails:install:migrations && rails db:migrate` to copy only
59
+ what is missing: `010_create_rcrewai_spans` (the trace tree) and
60
+ `011_add_observation_rollups_to_rcrewai_executions` (cost/token totals). Both
61
+ are additive — nothing existing is altered or dropped. Observation is enabled
62
+ by default once the tables exist; set `config.observation_enabled = false` to
63
+ upgrade without turning tracing on.
64
+
10
65
  ## [0.6.1] - 2026-07-07
11
66
 
12
67
  ### Fixed
@@ -123,7 +178,9 @@ existing agents, tasks, and crews build unchanged.
123
178
  ### Changed
124
179
  - Rename generators from `rcrew_a_i` to `rcrewai` namespacing.
125
180
 
126
- [Unreleased]: https://github.com/gkosmo/rcrewai-rails/compare/v0.6.1...HEAD
181
+ [Unreleased]: https://github.com/gkosmo/rcrewai-rails/compare/v0.7.1...HEAD
182
+ [0.7.1]: https://github.com/gkosmo/rcrewai-rails/compare/v0.7.0...v0.7.1
183
+ [0.7.0]: https://github.com/gkosmo/rcrewai-rails/compare/v0.6.1...v0.7.0
127
184
  [0.6.1]: https://github.com/gkosmo/rcrewai-rails/compare/v0.6.0...v0.6.1
128
185
  [0.6.0]: https://github.com/gkosmo/rcrewai-rails/compare/v0.5.1...v0.6.0
129
186
  [0.5.1]: https://github.com/gkosmo/rcrewai-rails/compare/v0.5.0...v0.5.1
data/README.md CHANGED
@@ -16,6 +16,50 @@ Rails engine for integrating [RcrewAI](https://github.com/gkosmo/rcrewai-rails)
16
16
  - Crew: `before_kickoff`/`after_kickoff` hooks, planning, the `consensual` process, batch execution
17
17
  - Knowledge (RAG) sources and Flow persistence
18
18
 
19
+ ## Observation Engine
20
+
21
+ Every crew execution is traced as a tree of spans — crew, agent, task, LLM call, and
22
+ tool call — carrying timings, token counts, and cost.
23
+
24
+ - **Trace view** at `/rcrewai/executions/:id/observation`: a waterfall of the run, with
25
+ prompts, tool arguments, and errors on each span.
26
+ - **Cost and performance** at `/rcrewai/observations/costs`: spend and token totals
27
+ across recent executions.
28
+ - **Live monitoring**: the trace view updates over Turbo Streams while a run is in progress.
29
+
30
+ Configure it in `config/initializers/rcrewai.rb`:
31
+
32
+ ```ruby
33
+ config.observation_enabled = true
34
+ config.observation_capture_prompts = :truncated # :none | :truncated | :full
35
+ config.observation_prompt_max_bytes = 4_096
36
+ config.observation_flush_mode = :batched # :batched | :immediate
37
+ config.observation_retention_days = 30
38
+ ```
39
+
40
+ Prompt text is truncated by default: full prompts can be large and may contain personal
41
+ data. Set `:full` only when you need lossless replay.
42
+
43
+ Prune old spans with the bundled rake task:
44
+
45
+ ```bash
46
+ rake rcrewai:observation:prune # uses observation_retention_days
47
+ rake rcrewai:observation:prune DAYS=7
48
+ ```
49
+
50
+ ### Limitations
51
+
52
+ Token and cost data depend on rcrewai's **streaming** execution path. `Usage` events are
53
+ only emitted when an agent runs via `ToolRunner`, which passes a `stream:` to the LLM
54
+ client — not via `LegacyReactRunner`, which does not. `ToolRunner` is selected when the
55
+ tools have JSON schemas **and** the LLM client reports `supports_native_tools?`. OpenAI,
56
+ Anthropic, and Google all report true, so cost capture works normally with them. With a
57
+ provider or configuration that falls back to `LegacyReactRunner` (for example Ollama
58
+ without native tools), **the cost dashboard will be empty rather than showing an error**.
59
+
60
+ Agent-level tracing requires **rcrewai >= 0.7.1**, the version that threads the event
61
+ stream down to agent execution. On earlier versions traces contain only crew-level spans.
62
+
19
63
  ## Installation
20
64
 
21
65
  Add this line to your application's Gemfile:
@@ -42,6 +86,40 @@ This will:
42
86
  - Add an initializer file for configuration
43
87
  - Mount the engine routes in `config/routes.rb`
44
88
 
89
+ ### Upgrading an existing install
90
+
91
+ The install generator above is for **new** installs — it creates every table, so
92
+ running it against an app that already has the RcrewAI tables will fail on
93
+ duplicates.
94
+
95
+ If you are already running rcrewai-rails, pull in only the migrations you are
96
+ missing using the standard Rails engine task:
97
+
98
+ ```bash
99
+ $ rails rcrew_ai_rails:install:migrations
100
+ $ rails db:migrate
101
+ ```
102
+
103
+ (The task name comes from the engine's railtie name, `rcrew_ai_rails`.)
104
+
105
+ Rails copies only the migrations your app does not already have. Upgrading to
106
+ 0.7.0 from 0.6.x adds two:
107
+
108
+ | Migration | Purpose |
109
+ |---|---|
110
+ | `010_create_rcrewai_spans` | `rcrewai_spans` and `rcrewai_span_events` — the observation engine's trace tree |
111
+ | `011_add_observation_rollups_to_rcrewai_executions` | `total_cost_usd`, `total_tokens`, `span_count`, `error_count` on executions |
112
+
113
+ Both are additive: no existing column or table is changed, and nothing is
114
+ dropped. Existing crews, agents, tasks, and executions are unaffected, and
115
+ observation is enabled by default once the tables exist. To upgrade the gem
116
+ without turning tracing on, set `config.observation_enabled = false` in
117
+ `config/initializers/rcrewai.rb` before migrating.
118
+
119
+ If your schema was created by hand (the install generator did not copy a
120
+ migration before 0.7.0, so this is likely), review the copied migrations before
121
+ running `db:migrate` and delete any whose tables you already have.
122
+
45
123
  ### Manual Routes Setup
46
124
 
47
125
  If you need to mount the routes manually, add this to your `config/routes.rb`:
@@ -0,0 +1,27 @@
1
+ module RcrewAI
2
+ module Rails
3
+ class ObservationsController < ApplicationController
4
+ # Hard ceiling on how deep the span partial will recurse. Real traces
5
+ # are a handful of levels deep; this only exists so a malformed tree
6
+ # cannot recurse until the request dies.
7
+ MAX_SPAN_DEPTH = 50
8
+
9
+ def show
10
+ @execution = Execution.find(params[:execution_id])
11
+ # Load the whole tree in one query and nest in memory — a recursive
12
+ # per-node query would be N+1 on deep traces.
13
+ @spans = @execution.spans.ordered.to_a
14
+ @children = @spans.group_by(&:parent_span_id)
15
+ @roots = @children[nil] || []
16
+ end
17
+
18
+ def costs
19
+ @executions = Execution.where.not(total_cost_usd: nil)
20
+ .order(created_at: :desc)
21
+ .limit(100)
22
+ @total_cost = @executions.sum(&:total_cost_usd)
23
+ @total_tokens = @executions.sum { |e| e.total_tokens.to_i }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -11,6 +11,7 @@ module RcrewAI
11
11
  inputs: inputs,
12
12
  batch_id: batch_id
13
13
  )
14
+ collector = nil
14
15
 
15
16
  begin
16
17
  execution.start!
@@ -18,7 +19,16 @@ module RcrewAI
18
19
 
19
20
  rcrew = crew.to_rcrew
20
21
 
21
- result = rcrew.execute(stream: stream_sink_for(execution))
22
+ collector = collector_for(execution)
23
+ collector&.start_crew_span(crew_name: crew.name)
24
+
25
+ result = rcrew.execute(stream: collector)
26
+
27
+ # Close agent spans as successful before finish!, which treats
28
+ # anything still open as an aborted run.
29
+ collector&.finish_open_agent_spans(status: "ok")
30
+ collector&.finish_crew_span(status: "ok")
31
+ collector&.finish!
22
32
 
23
33
  execution.complete!(result)
24
34
  execution.log("info", "Crew execution completed", { result: result })
@@ -27,6 +37,7 @@ module RcrewAI
27
37
 
28
38
  result
29
39
  rescue => e
40
+ collector&.finish!
30
41
  execution.fail!(e)
31
42
  execution.log("error", "Crew execution failed", {
32
43
  error: e.message,
@@ -39,34 +50,12 @@ module RcrewAI
39
50
 
40
51
  private
41
52
 
42
- # Build a stream sink that translates rcrewai events into ExecutionLog rows.
43
- # Note: the gem currently builds the sink but does not yet thread it down
44
- # to per-agent execution, so this is wired up for forward-compatibility.
45
- def stream_sink_for(execution)
46
- lambda do |event|
47
- case event
48
- when RCrewAI::Events::IterationStart
49
- execution.log("debug", "Iteration #{event.iteration_index} start", { agent: event.agent })
50
- when RCrewAI::Events::IterationEnd
51
- execution.log("debug", "Iteration end", { agent: event.agent, finish_reason: event.finish_reason })
52
- when RCrewAI::Events::ToolCallStart
53
- execution.log("info", "Tool call: #{event.tool}", { args: event.args, agent: event.agent })
54
- when RCrewAI::Events::ToolCallResult
55
- execution.log("info", "Tool result: #{event.tool}", { duration_ms: event.duration_ms, agent: event.agent })
56
- when RCrewAI::Events::ToolCallError
57
- execution.log("error", "Tool error: #{event.tool}", { error: event.error, agent: event.agent })
58
- when RCrewAI::Events::Usage
59
- execution.log("debug", "Usage", {
60
- prompt_tokens: event.prompt_tokens,
61
- completion_tokens: event.completion_tokens,
62
- total_tokens: event.total_tokens,
63
- cost_usd: event.cost_usd,
64
- agent: event.agent
65
- })
66
- when RCrewAI::Events::Error
67
- execution.log("error", "Crew error", { error: event.error, agent: event.agent })
68
- end
69
- end
53
+ # Translates rcrewai events into the span tree. Returns nil when
54
+ # observation is disabled so no sink is attached at all.
55
+ def collector_for(execution)
56
+ return nil unless RcrewAI::Rails.config.observation_enabled
57
+
58
+ RcrewAI::Rails::Observation::Collector.new(execution: execution)
70
59
  end
71
60
 
72
61
  def notify_completion(crew, execution, result)
@@ -5,6 +5,7 @@ module RcrewAI
5
5
 
6
6
  belongs_to :crew
7
7
  has_many :execution_logs, dependent: :destroy
8
+ has_many :spans, dependent: :destroy
8
9
 
9
10
  validates :status, inclusion: { in: %w[pending running completed failed cancelled] }
10
11
 
@@ -74,7 +75,12 @@ module RcrewAI
74
75
  %w[completed failed cancelled].include?(status)
75
76
  end
76
77
 
78
+ # Deprecated: superseded by the observation engine's span tree.
79
+ # Scheduled for removal one minor version after the engine ships.
77
80
  def log(level, message, details = {})
81
+ RcrewAI::Rails.deprecator_warn(
82
+ "Execution#log and ExecutionLog are deprecated; use the observation engine (Execution#spans)."
83
+ )
78
84
  execution_logs.create!(
79
85
  level: level,
80
86
  message: message,
@@ -0,0 +1,57 @@
1
+ module RcrewAI
2
+ module Rails
3
+ class Span < ApplicationRecord
4
+ self.table_name = "rcrewai_spans"
5
+
6
+ KINDS = %w[crew agent task llm_call tool_call].freeze
7
+ STATUSES = %w[running ok error].freeze
8
+
9
+ belongs_to :execution
10
+ belongs_to :parent, class_name: "RcrewAI::Rails::Span",
11
+ foreign_key: :parent_span_id, optional: true
12
+ has_many :children, class_name: "RcrewAI::Rails::Span",
13
+ foreign_key: :parent_span_id, dependent: :destroy
14
+ has_many :span_events, dependent: :destroy
15
+
16
+ validates :kind, inclusion: { in: KINDS }
17
+ validates :status, inclusion: { in: STATUSES }
18
+ validates :name, :trace_id, :started_at, :sequence, presence: true
19
+
20
+ scope :roots, -> { where(parent_span_id: nil).order(:sequence) }
21
+ scope :ordered, -> { order(:sequence) }
22
+ scope :errored, -> { where(status: "error") }
23
+ scope :running, -> { where(status: "running") }
24
+ scope :llm_calls, -> { where(kind: "llm_call") }
25
+ scope :tool_calls, -> { where(kind: "tool_call") }
26
+
27
+ def attributes_hash
28
+ raw = self[:attributes_json]
29
+ return {} if raw.blank?
30
+
31
+ JSON.parse(raw)
32
+ rescue JSON::ParserError
33
+ {}
34
+ end
35
+
36
+ def attributes_hash=(hash)
37
+ self[:attributes_json] = hash.nil? ? nil : JSON.generate(hash)
38
+ end
39
+
40
+ def finish!(status: "ok", ended_at: Time.current)
41
+ update!(
42
+ status: status,
43
+ ended_at: ended_at,
44
+ duration_ms: ((ended_at - started_at) * 1000).round
45
+ )
46
+ end
47
+
48
+ def running?
49
+ status == "running"
50
+ end
51
+
52
+ def errored?
53
+ status == "error"
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,19 @@
1
+ module RcrewAI
2
+ module Rails
3
+ class SpanEvent < ApplicationRecord
4
+ self.table_name = "rcrewai_span_events"
5
+
6
+ LEVELS = %w[debug info warn error].freeze
7
+
8
+ belongs_to :span
9
+
10
+ validates :level, inclusion: { in: LEVELS }
11
+ validates :name, presence: true
12
+
13
+ serialize :details, coder: JSON
14
+
15
+ scope :errors, -> { where(level: "error") }
16
+ scope :recent, -> { order(timestamp: :desc) }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,51 @@
1
+ <%#
2
+ Renders one span and, recursively, its children.
3
+
4
+ `children` is the parent_span_id => [spans] map built once by the
5
+ controller. Children are read from that map, never via `span.children`,
6
+ which would issue a query per node and turn a deep trace into an N+1.
7
+
8
+ A malformed tree (a parent/child cycle, e.g. a bad backfill) would
9
+ otherwise recurse until the request dies, so recursion is bounded by
10
+ MAX_SPAN_DEPTH and by `seen`, which drops any span already on the path.
11
+ %>
12
+ <% depth = local_assigns.fetch(:depth, 0) %>
13
+ <% children = local_assigns.fetch(:children, {}) %>
14
+ <% seen = local_assigns.fetch(:seen, []) %>
15
+ <% max_depth = RcrewAI::Rails::ObservationsController::MAX_SPAN_DEPTH %>
16
+
17
+ <div class="span-row span-<%= span.status %> span-kind-<%= span.kind %>" id="span-<%= span.id %>">
18
+ <div class="span-line" style="padding-left: <%= depth * 20 %>px">
19
+ <span class="span-kind"><%= span.kind %></span>
20
+ <span class="span-name"><%= span.name %></span>
21
+ <span class="span-status status-<%= span.status %>"><%= span.status %></span>
22
+ <% if span.duration_ms %>
23
+ <span class="span-duration"><%= span.duration_ms %>ms</span>
24
+ <% end %>
25
+ <% if span.total_tokens.to_i.positive? %>
26
+ <span class="span-tokens"><%= span.total_tokens %> tokens</span>
27
+ <% end %>
28
+ <% if span.cost_usd.present? %>
29
+ <span class="span-cost">$<%= span.cost_usd %></span>
30
+ <% end %>
31
+ </div>
32
+
33
+ <% attrs = span.attributes_hash %>
34
+ <% if attrs.present? %>
35
+ <details class="span-attributes" style="margin-left: <%= (depth * 20) + 20 %>px">
36
+ <summary>Details</summary>
37
+ <pre class="code-block"><%= JSON.pretty_generate(attrs) %></pre>
38
+ </details>
39
+ <% end %>
40
+ </div>
41
+
42
+ <% if depth < max_depth %>
43
+ <% (children[span.id] || []).each do |child| %>
44
+ <% next if seen.include?(child.id) %>
45
+ <%= render "rcrewai/rails/observations/span",
46
+ span: child,
47
+ children: children,
48
+ depth: depth + 1,
49
+ seen: seen + [span.id] %>
50
+ <% end %>
51
+ <% end %>
@@ -0,0 +1,52 @@
1
+ <div class="page-header">
2
+ <h1>Cost &amp; Performance</h1>
3
+ </div>
4
+
5
+ <div class="metadata-grid">
6
+ <div class="meta-item">
7
+ <strong>Total Cost:</strong> $<%= @total_cost %>
8
+ </div>
9
+ <div class="meta-item">
10
+ <strong>Total Tokens:</strong> <%= @total_tokens %>
11
+ </div>
12
+ <div class="meta-item">
13
+ <strong>Executions:</strong> <%= @executions.size %>
14
+ </div>
15
+ </div>
16
+
17
+ <div class="section">
18
+ <h2>Recent Executions</h2>
19
+
20
+ <% if @executions.empty? %>
21
+ <p class="empty-state">No executions with recorded cost yet.</p>
22
+ <% else %>
23
+ <table class="table">
24
+ <thead>
25
+ <tr>
26
+ <th>Execution</th>
27
+ <th>Crew</th>
28
+ <th>Status</th>
29
+ <th>Spans</th>
30
+ <th>Errors</th>
31
+ <th>Tokens</th>
32
+ <th>Cost</th>
33
+ <th></th>
34
+ </tr>
35
+ </thead>
36
+ <tbody>
37
+ <% @executions.each do |execution| %>
38
+ <tr>
39
+ <td><%= link_to "##{execution.id}", execution_path(execution) %></td>
40
+ <td><%= execution.crew&.name %></td>
41
+ <td><span class="status-<%= execution.status %>"><%= execution.status.humanize %></span></td>
42
+ <td><%= execution.span_count.to_i %></td>
43
+ <td><%= execution.error_count.to_i %></td>
44
+ <td><%= execution.total_tokens.to_i %></td>
45
+ <td>$<%= execution.total_cost_usd %></td>
46
+ <td><%= link_to "Trace", execution_observation_path(execution), class: "btn btn-sm" %></td>
47
+ </tr>
48
+ <% end %>
49
+ </tbody>
50
+ </table>
51
+ <% end %>
52
+ </div>
@@ -0,0 +1,47 @@
1
+ <div class="page-header">
2
+ <h1>Trace for Execution #<%= @execution.id %></h1>
3
+ <div class="actions">
4
+ <%= link_to "Back to Execution", execution_path(@execution), class: "btn" %>
5
+ <%= link_to "Costs", observation_costs_path, class: "btn" %>
6
+ </div>
7
+ </div>
8
+
9
+ <div class="execution-details">
10
+ <div class="status-banner status-<%= @execution.status %>">
11
+ <strong>Status:</strong> <%= @execution.status.humanize %>
12
+ </div>
13
+
14
+ <div class="metadata-grid">
15
+ <div class="meta-item">
16
+ <strong>Spans:</strong> <%= @execution.span_count.to_i %>
17
+ </div>
18
+ <div class="meta-item">
19
+ <strong>Errors:</strong> <%= @execution.error_count.to_i %>
20
+ </div>
21
+ <div class="meta-item">
22
+ <strong>Tokens:</strong> <%= @execution.total_tokens.to_i %>
23
+ </div>
24
+ <div class="meta-item">
25
+ <strong>Cost:</strong> <%= @execution.total_cost_usd.present? ? "$#{@execution.total_cost_usd}" : "N/A" %>
26
+ </div>
27
+ </div>
28
+ </div>
29
+
30
+ <div class="section">
31
+ <h2>Waterfall</h2>
32
+
33
+ <div class="span-tree" id="execution-<%= @execution.id %>-spans">
34
+ <% if @roots.empty? %>
35
+ <p class="empty-state">No spans recorded for this execution.</p>
36
+ <% else %>
37
+ <% @roots.each do |span| %>
38
+ <%= render "rcrewai/rails/observations/span",
39
+ span: span, children: @children, depth: 0, seen: [] %>
40
+ <% end %>
41
+ <% end %>
42
+ </div>
43
+ </div>
44
+
45
+ <% if defined?(turbo_stream_from) && @execution.running? %>
46
+ <%= turbo_stream_from "rcrewai_execution_#{@execution.id}" %>
47
+ <% end %>
data/config/routes.rb CHANGED
@@ -16,6 +16,12 @@ RcrewAI::Rails::Engine.routes.draw do
16
16
  end
17
17
  end
18
18
 
19
+ resources :executions, only: [] do
20
+ resource :observation, only: [:show], controller: "observations"
21
+ end
22
+
23
+ get "observations/costs", to: "observations#costs", as: :observation_costs
24
+
19
25
  resources :agents do
20
26
  resources :tools
21
27
  end
@@ -1,4 +1,4 @@
1
- class AddConfigToRcrewaiAgents < ActiveRecord::Migration[7.0]
1
+ class AddConfigToRcrewAIAgents < ActiveRecord::Migration[7.0]
2
2
  def change
3
3
  # max_rpm and llm_config already exist on rcrewai_agents from the
4
4
  # original create table; only the 0.5.0 additions are new.
@@ -1,4 +1,4 @@
1
- class AddOutputProcessingToRcrewaiTasks < ActiveRecord::Migration[7.0]
1
+ class AddOutputProcessingToRcrewAITasks < ActiveRecord::Migration[7.0]
2
2
  def change
3
3
  # output_file already exists on rcrewai_tasks from the original create
4
4
  # table; only the new 0.4/0.5 output-processing options are added here.
@@ -1,4 +1,4 @@
1
- class AddLifecycleToRcrewaiCrews < ActiveRecord::Migration[7.0]
1
+ class AddLifecycleToRcrewAICrews < ActiveRecord::Migration[7.0]
2
2
  def change
3
3
  add_column :rcrewai_crews, :planning, :boolean, default: false, null: false
4
4
  add_column :rcrewai_crews, :planning_llm, :string
@@ -1,4 +1,4 @@
1
- class AddBatchIdToRcrewaiExecutions < ActiveRecord::Migration[7.0]
1
+ class AddBatchIdToRcrewAIExecutions < ActiveRecord::Migration[7.0]
2
2
  def change
3
3
  add_column :rcrewai_executions, :batch_id, :string
4
4
  add_index :rcrewai_executions, :batch_id
@@ -1,4 +1,4 @@
1
- class CreateRcrewaiKnowledgeSources < ActiveRecord::Migration[7.0]
1
+ class CreateRcrewAIKnowledgeSources < ActiveRecord::Migration[7.0]
2
2
  def change
3
3
  create_table :rcrewai_knowledge_sources do |t|
4
4
  t.references :owner, polymorphic: true, null: false
@@ -1,4 +1,4 @@
1
- class CreateRcrewaiFlows < ActiveRecord::Migration[7.0]
1
+ class CreateRcrewAIFlows < ActiveRecord::Migration[7.0]
2
2
  def change
3
3
  create_table :rcrewai_flow_states do |t|
4
4
  t.string :state_id, null: false
@@ -1,4 +1,4 @@
1
- class AddConsensusAgentsToRcrewaiCrews < ActiveRecord::Migration[7.0]
1
+ class AddConsensusAgentsToRcrewAICrews < ActiveRecord::Migration[7.0]
2
2
  def change
3
3
  add_column :rcrewai_crews, :consensus_agents, :integer
4
4
  end
@@ -0,0 +1,42 @@
1
+ class CreateRcrewAISpans < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table :rcrewai_spans do |t|
4
+ t.references :execution, null: false, foreign_key: { to_table: :rcrewai_executions }
5
+ t.bigint :parent_span_id
6
+ t.string :trace_id, null: false
7
+ t.string :kind, null: false
8
+ t.string :name, null: false
9
+ t.string :status, null: false, default: "running"
10
+ t.datetime :started_at, null: false
11
+ t.datetime :ended_at
12
+ t.integer :duration_ms
13
+ t.integer :prompt_tokens
14
+ t.integer :completion_tokens
15
+ t.integer :total_tokens
16
+ t.decimal :cost_usd, precision: 12, scale: 6
17
+ t.text :attributes_json
18
+ t.integer :sequence, null: false
19
+
20
+ t.timestamps
21
+ end
22
+
23
+ add_index :rcrewai_spans, :parent_span_id
24
+ add_index :rcrewai_spans, :trace_id
25
+ add_index :rcrewai_spans, :kind
26
+ add_index :rcrewai_spans, :status
27
+ add_index :rcrewai_spans, %i[execution_id sequence]
28
+
29
+ create_table :rcrewai_span_events do |t|
30
+ t.references :span, null: false, foreign_key: { to_table: :rcrewai_spans }
31
+ t.string :level, null: false, default: "info"
32
+ t.string :name, null: false
33
+ t.text :details
34
+ t.datetime :timestamp, null: false
35
+
36
+ t.timestamps
37
+ end
38
+
39
+ add_index :rcrewai_span_events, :level
40
+ add_index :rcrewai_span_events, :timestamp
41
+ end
42
+ end
@@ -0,0 +1,8 @@
1
+ class AddObservationRollupsToRcrewAIExecutions < ActiveRecord::Migration[7.0]
2
+ def change
3
+ add_column :rcrewai_executions, :total_cost_usd, :decimal, precision: 12, scale: 6
4
+ add_column :rcrewai_executions, :total_tokens, :integer
5
+ add_column :rcrewai_executions, :span_count, :integer, default: 0, null: false
6
+ add_column :rcrewai_executions, :error_count, :integer, default: 0, null: false
7
+ end
8
+ end