activeagent 1.1.0 → 1.3.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +185 -0
  3. data/README.md +58 -14
  4. data/lib/active_agent/base.rb +11 -2
  5. data/lib/active_agent/concerns/delegation.rb +385 -0
  6. data/lib/active_agent/delegation/backend.rb +109 -0
  7. data/lib/active_agent/delegation/budget.rb +138 -0
  8. data/lib/active_agent/delegation/contract.rb +117 -0
  9. data/lib/active_agent/delegation/definition.rb +95 -0
  10. data/lib/active_agent/delegation/ledger.rb +56 -0
  11. data/lib/active_agent/delegation/pricing.rb +106 -0
  12. data/lib/active_agent/delegation/runner.rb +283 -0
  13. data/lib/active_agent/delegation/schema.rb +220 -0
  14. data/lib/active_agent/providers/_base_provider.rb +97 -1
  15. data/lib/active_agent/providers/open_ai/chat_provider.rb +21 -2
  16. data/lib/active_agent/telemetry/configuration.rb +13 -9
  17. data/lib/active_agent/telemetry/instrumentation.rb +38 -1
  18. data/lib/active_agent/telemetry/tool_origin.rb +90 -0
  19. data/lib/active_agent/telemetry.rb +1 -0
  20. data/lib/active_agent/version.rb +1 -1
  21. data/lib/active_agent.rb +1 -5
  22. metadata +31 -32
  23. data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/api/traces_controller.rb +0 -138
  24. data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/application_controller.rb +0 -64
  25. data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/dashboard_controller.rb +0 -129
  26. data/lib/active_agent/dashboard/app/controllers/active_agent/dashboard/traces_controller.rb +0 -123
  27. data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/agent_execution_job.rb +0 -56
  28. data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/application_job.rb +0 -14
  29. data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_cleanup_job.rb +0 -49
  30. data/lib/active_agent/dashboard/app/jobs/active_agent/dashboard/sandbox_provision_job.rb +0 -65
  31. data/lib/active_agent/dashboard/app/jobs/active_agent/process_telemetry_traces_job.rb +0 -86
  32. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent.rb +0 -256
  33. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_run.rb +0 -113
  34. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_template.rb +0 -208
  35. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/agent_version.rb +0 -60
  36. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/application_record.rb +0 -46
  37. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_action.rb +0 -125
  38. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/recording_snapshot.rb +0 -83
  39. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_run.rb +0 -52
  40. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/sandbox_session.rb +0 -169
  41. data/lib/active_agent/dashboard/app/models/active_agent/dashboard/session_recording.rb +0 -193
  42. data/lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb +0 -214
  43. data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/_trace_detail.html.erb +0 -117
  44. data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/index.html.erb +0 -135
  45. data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/metrics.html.erb +0 -145
  46. data/lib/active_agent/dashboard/app/views/active_agent/dashboard/traces/show.html.erb +0 -36
  47. data/lib/active_agent/dashboard/app/views/layouts/active_agent/dashboard/application.html.erb +0 -94
  48. data/lib/active_agent/dashboard/config/routes.rb +0 -19
  49. data/lib/active_agent/dashboard/engine.rb +0 -43
  50. data/lib/active_agent/dashboard.rb +0 -161
  51. data/lib/generators/active_agent/dashboard/install_generator.rb +0 -92
  52. data/lib/generators/active_agent/dashboard/templates/active_agent_dashboard.rb.erb +0 -67
  53. data/lib/generators/active_agent/dashboard/templates/create_active_agent_telemetry_traces.rb.erb +0 -46
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f352ce28ead5e975e8d64c76c4a263c1ac7cac3b202ea6fef1a137f899e3aea0
4
- data.tar.gz: d163e4360d38fdf440465ec0caf76dc9c196cab342de0de4cf67de020b31d0e2
3
+ metadata.gz: 45044ca9e6ddabcdfb1be0f32074149a3e9fb8fcaa9e7c8a79c02f81c73eb64b
4
+ data.tar.gz: 7f0a2489a68e01f0dd0e9a1fb96878bb900acaab173ba0a0157ad82a8a510fe6
5
5
  SHA512:
6
- metadata.gz: 8aed9bb0e1d7bec5b5e178940e4e43c8a117c82d36db703d34cbef485b0492d29d628350bb956cf709ee504a81327e152d5510d3c1b81cf43d5ecc70f33ffae8
7
- data.tar.gz: 6d8b3c9b6d6f7286985206a42db8949067dbb46bc39db67e4eb507ccc1a9dd405ed46720c06878c4d5ea748f8da184072ce4e9bf9951361d4fe5172ae65770d8
6
+ metadata.gz: 7c4797fc3bc8e97be8624ef989abc1486fe0d357f5248353dd09bc9852de605e0b14cebb33f3fb35ba166f62e64f7ce5522f62da94ad25b5b188a771f1f8aeec
7
+ data.tar.gz: 72bc8b89a042c1d3fd2489de0eb452d2e95106eaf9a46ad4dc5e8f1378b3441345c0b652c123490804943064f16526a1baa67584ce4b2a4315255beb3783203e
data/CHANGELOG.md CHANGED
@@ -5,6 +5,152 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.3.0] - 2026-08-18
9
+
10
+ ### Added
11
+
12
+ - **Agent-as-tool delegation.** A tool is a Ruby method the model can call; a
13
+ delegation is another agent it can call. The callee keeps its own
14
+ instructions, templates, model and budget, so a specialist agent stays
15
+ specialist and the generalist orchestrating it never inherits its prompt.
16
+ Declared with `delegation :action, description:` on the sub-agent, with a
17
+ JSON Schema for the inputs and an optional `returns` schema that becomes the
18
+ sub-agent's response format. See `docs/actions/delegation.md`.
19
+
20
+ ### Fixed
21
+
22
+ - **Streamed generations report their token usage.** A request with
23
+ `stream: true` recorded zero input and output tokens, and so zero cost and
24
+ no context-pressure estimate downstream — dashboards showed `Tokens 0` and
25
+ `$0.00` beside a run that had plainly called the API. Three things had to
26
+ hold at once for the usage to survive, and none did: the streaming path
27
+ returns `nil` rather than a response body to read usage from; Chat
28
+ Completions only emits its usage chunk when the request sets
29
+ `stream_options: {include_usage: true}`, which was never sent; and that
30
+ chunk arrives *after* `content.done`, where the response was already being
31
+ built. Completion now defers until the stream drains, and the usage chunk
32
+ is recorded on the way past. A provider hook (`api_stream_usage_parameters`,
33
+ empty by default) keeps providers that report unconditionally — or not at
34
+ all — unaffected.
35
+
36
+ Also fixed a silent conversion failure behind the same symptom:
37
+ `Usage.from_provider_usage` early-returns on anything that is not a Hash,
38
+ and the stainless gems hand back model objects, so usage was dropped even
39
+ when it did arrive.
40
+
41
+ ### Note on the 1.2.0 tag
42
+
43
+ The `v1.2.0` tag had been moved to a commit later than the one published as
44
+ `activeagent 1.2.0`, so the tag and the gem disagreed. It has been repointed
45
+ to the commit that actually produced the release. If you fetched the tag
46
+ between 2026-08-14 and 2026-08-18, re-fetch with `git fetch --tags --force`.
47
+
48
+ ## [1.2.2] - 2026-08-14
49
+
50
+ ### Fixed
51
+
52
+ - **`actionagent`: every engine constant resolves under a host's
53
+ inflections.** 1.2.1 scoped its autoloader override to the basename `api`,
54
+ which covered the controllers under `app/controllers/action_agent/api` and
55
+ nothing else. Seven files camelize differently once a host registers an
56
+ acronym — `mcp_catalog.rb`, `mcp_recording_middleware.rb`,
57
+ `playwright_mcp_client.rb`, `api_key.rb`, and the `api_keys`, `mcp` and
58
+ `mcp_servers` controllers — and each raised `Zeitwerk::NameError` on first
59
+ reference. In a host declaring `inflect.acronym "MCP"` the **Tools view was
60
+ unreachable** (`uninitialized constant
61
+ ActionAgent::ToolDiscovery::McpCatalog`), as were the MCP endpoints and
62
+ anything touching an API key.
63
+
64
+ Every path under the engine now camelizes with Zeitwerk's default
65
+ inflector, ignoring the host's acronyms, scoped by path so the host's own
66
+ constants keep their spelling. The router half generalizes with it: an
67
+ all-caps run in a missing constant is retried in the relaxed spelling
68
+ (`API` → `Api`, `MCPServersController` → `McpServersController`) rather
69
+ than aliasing each pair by hand.
70
+
71
+ ## [1.2.1] - 2026-08-14
72
+
73
+ ### Fixed
74
+
75
+ - **`actionagent`: the install migrations now run on MySQL.** Both templates
76
+ already chose the JSON column type per adapter, but kept `default: []` /
77
+ `default: {}` for every adapter, and MySQL rejects a default on a JSON
78
+ column outright — so `rails g action_agent:install && rails db:migrate`
79
+ aborted mid-`create_table` on any MySQL host. The default (and the paired
80
+ `null: false`, which without it would reject the inserts the default
81
+ existed to satisfy) is now PostgreSQL-only. Every JSON column is read
82
+ through `Array(...)` / `|| {}`, so a NULL reads as the empty value.
83
+ - **`actionagent`: the mount works in a host that declares
84
+ `inflect.acronym "API"`.** An engine's files are autoloaded under the
85
+ host's inflections, so such a host made Zeitwerk expect
86
+ `ActionAgent::API::TracesController` from a file defining
87
+ `ActionAgent::Api::TracesController`, and every request to the mount
88
+ raised `Zeitwerk::NameError`. Rails separately camelizes a route's stored
89
+ controller path with the host's global inflections, which no engine-level
90
+ setting scopes. The autoloader is now pinned to `Api` for this engine's
91
+ own path, and the namespace answers to `API` as well.
92
+
93
+ ## [1.2.0] - 2026-08-14
94
+
95
+ ### ⚠️ The dashboard has moved to its own gem
96
+
97
+ The dashboard engine that shipped inside `activeagent` is now a separate
98
+ gem, **`actionagent`**. Nothing is gone — the dashboard is the same
99
+ dashboard, and it gained a great deal in this release — but it comes from a
100
+ different gem now. `activeagent` is the framework alone: it no longer
101
+ defines `ActiveAgent::Dashboard`, and no longer pulls Active Record into
102
+ apps that do not use it.
103
+
104
+ **If you mount the dashboard, add the new gem in the same change that
105
+ upgrades `activeagent`:**
106
+
107
+ ```ruby
108
+ gem "activeagent", "~> 1.2"
109
+ gem "actionagent", "~> 1.2" # required if you mount the dashboard
110
+ ```
111
+
112
+ This is a minor version, so a `~> 1.0` or `~> 1.1` constraint **will** pick
113
+ it up on the next `bundle update`. If you mount the dashboard and do not add
114
+ `actionagent` at the same time, the app fails at boot with
115
+ `NameError: uninitialized constant ActiveAgent::Dashboard`, raised by your
116
+ own initializer or by the `mount ActiveAgent::Dashboard::Engine` line in
117
+ `config/routes.rb`. Adding the gem is the whole fix — your existing
118
+ configuration keeps working through the compatibility shims below.
119
+
120
+ If you do not mount the dashboard, there is nothing to do: the framework API
121
+ is unchanged, and the gem is 95% smaller.
122
+
123
+ With `actionagent` installed, the old constants keep resolving through
124
+ `ActionAgent::Compatibility` with a deprecation warning:
125
+
126
+ - `ActiveAgent::Dashboard` → `ActionAgent`
127
+ - `ActiveAgent::TelemetryTrace` → `ActionAgent::TelemetryTrace`
128
+ - `ActiveAgent::ProcessTelemetryTracesJob` → `ActionAgent::ProcessTelemetryTracesJob`
129
+
130
+ That last one matters beyond tidiness: Active Job serializes the class name
131
+ into the queue payload, so jobs enqueued before the upgrade still resolve
132
+ after it.
133
+
134
+ Other changes for mounted installs:
135
+
136
+ - **The server-rendered traces console moves from `/traces` to
137
+ `/console/traces`.** `/traces` is now the React traces view — the same
138
+ data, with more of it.
139
+ - **The mount is authenticated everywhere but development and test.** The
140
+ sandbox API, the session-recording capture endpoints and the template
141
+ endpoints previously allowed anonymous access; they no longer do. The
142
+ `GET /api/session_recordings/demo` endpoint is removed.
143
+ - **`current_user_method` / `current_account_method` are superseded by
144
+ `current_user_resolver` / `current_account_resolver`.** The engine's
145
+ controllers are their own base class, so a host app's `current_user`
146
+ helper is not available to them.
147
+ - **An unresolved owner now scopes to nothing rather than to everything.**
148
+ If you configure `user_class` or `account_class`, make sure the matching
149
+ resolver actually returns a record, or the dashboard will show no data.
150
+ - Existing installs upgrading from the in-gem dashboard: re-run
151
+ `rails generate action_agent:install`. It detects the migrations you
152
+ already have and emits only what is missing.
153
+
8
154
  ## [1.1.0] - 2026-08-12
9
155
 
10
156
  ### Dashboard — self-hosted (enterprise) mount readiness
@@ -45,6 +191,45 @@ observability surface (see `docs/framework/self-hosted-observability.md`):
45
191
  it remains a no-op accessor with its historical default so existing
46
192
  initializers keep booting, and will be removed in the next major.
47
193
 
194
+ ### Agent-as-tool delegation
195
+
196
+ Sub-agents are now a first-class primitive. A tool is a Ruby method your
197
+ model can call; a delegation is another agent your model can call — with
198
+ its own instructions, templates, model and budget.
199
+
200
+ - **`delegation :action, description:`** declares what a sub-agent exposes:
201
+ a description for the calling model, a JSON Schema for its inputs (block
202
+ DSL, a plain hash, or any class responding to `to_json_schema`), and
203
+ optionally a `returns` schema. A declared `returns` becomes the
204
+ sub-agent's `response_format`, and its answer is parsed and checked
205
+ before the caller sees it.
206
+ - **`delegate_to AgentClass`** exposes those contracts to the calling model
207
+ as tools, with `only:`/`except:`/`as:` for scoping and renaming,
208
+ `params:` for forwarding, and `action:` for declaring a contract at the
209
+ call site when you don't own the sub-agent. Per-action scoping via the
210
+ `delegations:` prompt option.
211
+ - **Cost and latency budgets**: `max_calls`, `max_tokens`, `max_cost`,
212
+ `max_duration` and a per-call `timeout`, set per delegation and/or
213
+ agent-wide with `delegation_budget`. Exhausting one returns a structured
214
+ result the model can act on (`on_exceeded: :stop`, the default) instead
215
+ of raising mid-conversation; `:raise` is available. Budgets are scoped
216
+ to a single generation, and spend is readable afterwards via
217
+ `delegation_ledger`.
218
+ - **Swappable backends**: `backend: :ollama` or
219
+ `backend: { provider: :anthropic, model: "claude-haiku-4-5" }` moves a
220
+ delegation to different silicon without touching the sub-agent. Provider
221
+ swaps rebuild provider configuration rather than merging over it, and
222
+ template lookup still resolves to the original agent's views.
223
+ - **Cost registry**: `ActiveAgent::Delegation::Pricing.register` records
224
+ token rates in USD per 1M tokens (no built-in price list, so `max_cost`
225
+ never fires on stale numbers); rates can also be stated inline on a budget.
226
+ - **Instrumentation**: `delegate.active_agent` (agent, sub-agent, action,
227
+ model, duration, usage, cost, ledger) and
228
+ `delegation_refused.active_agent` (violated limit).
229
+ - **New docs** (`docs/actions/delegation.md`) with a worked support-triage
230
+ example, plus test coverage in `test/features/delegation_test.rb` and
231
+ `test/docs/actions/delegation_examples_test.rb`.
232
+
48
233
  ### Dashboard & Telemetry — dev console readiness
49
234
 
50
235
  The dashboard engine — Active Agent's local dev console — now works out of
data/README.md CHANGED
@@ -10,6 +10,16 @@
10
10
  >
11
11
  > *Makes code [TonsOfFun](https://tonsoffun.github.io)!*
12
12
 
13
+ [![Gem Version](https://img.shields.io/gem/v/activeagent?label=activeagent&logo=rubygems&color=CC342D)](https://rubygems.org/gems/activeagent)
14
+ [![actionagent](https://img.shields.io/gem/v/actionagent?label=actionagent&logo=rubygems&color=CC342D)](https://rubygems.org/gems/actionagent)
15
+ [![Downloads](https://img.shields.io/gem/dt/activeagent?label=downloads)](https://rubygems.org/gems/activeagent)
16
+ [![CI](https://github.com/activeagents/activeagent/actions/workflows/ci.yml/badge.svg)](https://github.com/activeagents/activeagent/actions/workflows/ci.yml)
17
+ [![Cross-repo integration](https://github.com/activeagents/activeagent/actions/workflows/integration.yml/badge.svg)](https://github.com/activeagents/activeagent/actions/workflows/integration.yml)
18
+ [![Docs](https://img.shields.io/badge/docs-docs.activeagents.ai-2563eb)](https://docs.activeagents.ai)
19
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.1-CC342D)](https://www.ruby-lang.org)
20
+ [![Rails](https://img.shields.io/badge/rails-7.2%20%7C%208.0%20%7C%208.1-D30001)](https://rubyonrails.org)
21
+ [![License](https://img.shields.io/github/license/activeagents/activeagent)](LICENSE)
22
+
13
23
  # Active Agent
14
24
  Active Agent provides that missing AI layer in the Rails framework, offering a structured approach to building AI-powered applications through Agent Oriented Programming. **Now Agents are Controllers!** Designing applications using agents allows developers to create modular, reusable components that can be easily integrated into existing systems. This approach promotes code reusability, maintainability, and scalability, making it easier to build complex AI-driven applications with the Object Oriented Ruby code you already use today.
15
25
 
@@ -25,6 +35,10 @@ Use bundler to add activeagent to your Gemfile and install:
25
35
  bundle add activeagent
26
36
  ```
27
37
 
38
+ That is the framework — agents, providers, generation and telemetry
39
+ reporting. The dashboard is a second gem, `actionagent`, added separately
40
+ when you want it; see [Dashboard & Dev Console](#dashboard--dev-console).
41
+
28
42
  Add the generation provider gem you want to use:
29
43
 
30
44
  ```bash
@@ -127,14 +141,19 @@ development:
127
141
  service: "RubyLLM"
128
142
  ```
129
143
 
130
- ## Dev Console & Observability
144
+ ## Dashboard & Dev Console
131
145
 
132
- Active Agent includes a local dev console traces with span waterfalls,
133
- token usage, and per-agent metrics as a mountable Rails engine, so you
134
- can watch your agents while you build:
146
+ The dashboard is its own gem, `actionagent`: a mountable Rails engine with
147
+ traces and span waterfalls, token usage and per-agent metrics, plus the agent
148
+ builder, runs, conversations, evaluations, scorecards and cost estimates — so
149
+ you can watch and drive your agents while you build. It ships separately
150
+ because its models are Active Record models and it runs agents through
151
+ [solid_agent](https://github.com/activeagents/solid_agent) — neither of which
152
+ `activeagent` depends on, so an app that only runs agents installs neither.
135
153
 
136
154
  ```bash
137
- rails generate active_agent:dashboard:install
155
+ bundle add actionagent
156
+ rails generate action_agent:install
138
157
  rails db:migrate
139
158
  ```
140
159
 
@@ -145,13 +164,14 @@ telemetry:
145
164
  local_storage: true
146
165
  ```
147
166
 
148
- Open `/activeagents` and every generation appears as a trace. See
167
+ The generator mounts the engine at `/activeagents` open it and every
168
+ generation appears as a trace. See
149
169
  [docs/framework/dashboard.md](docs/framework/dashboard.md) for
150
- authentication, remote ingestion, and multi-tenant mode. For production
151
- monitoring evaluations, cost estimates, retention, team workspaces —
152
- point telemetry at the hosted platform at
153
- [activeagents.ai](https://activeagents.ai); every workspace starts with a
154
- free low-volume trial.
170
+ authentication, remote ingestion, and multi-tenant mode. The hosted
171
+ platform at [activeagents.ai](https://activeagents.ai) runs this same
172
+ engine multi-tenant, adding what a hosted product has to have — accounts,
173
+ plans, billing, quotas and managed sandboxes; every workspace starts with
174
+ a free low-volume trial.
155
175
 
156
176
  ## Features
157
177
 
@@ -161,6 +181,7 @@ free low-volume trial.
161
181
  - **View Templates**: Use ERB templates for prompts (text, JSON, HTML)
162
182
  - **Streaming Support**: Real-time response streaming with ActionCable
163
183
  - **Tool/Function Calling**: Agents can use tools to interact with external services
184
+ - **Agent-as-Tool Delegation**: Hand work to sub-agents with declared schemas, cost/latency budgets, and swappable backends
164
185
  - **Context Management**: Maintain conversation history across interactions
165
186
  - **Structured Output**: Define JSON schemas for predictable responses
166
187
 
@@ -195,16 +216,39 @@ response = prompt.generate_now
195
216
  # Response includes tool call results
196
217
  ```
197
218
 
219
+ ### Delegation
220
+ Hand part of a job to a sub-agent, under a declared contract and a budget:
221
+
222
+ ```ruby
223
+ class SummarizerAgent < ApplicationAgent
224
+ generate_with :openai, model: "gpt-4o-mini"
225
+
226
+ delegation :summarize, description: "Condense a document into key points" do
227
+ string :text, required: true, description: "Full document text"
228
+ end
229
+
230
+ def summarize(text:) = prompt(message: text)
231
+ end
232
+
233
+ class ResearchAgent < ApplicationAgent
234
+ generate_with :openai, model: "gpt-4o"
235
+
236
+ delegate_to SummarizerAgent, budget: { max_calls: 3, timeout: 20 }
237
+
238
+ def research(topic:) = prompt(message: "Research #{topic}")
239
+ end
240
+ ```
241
+
198
242
  ## Learn More
199
243
 
200
244
  - [Documentation](https://docs.activeagents.ai)
201
245
  - [Getting Started Guide](https://docs.activeagents.ai/getting_started)
202
- - [API Reference](https://docs.activeagents.ai/docs/framework)
203
- - [Examples](https://docs.activeagents.ai/docs/agents)
246
+ - [API Reference](https://docs.activeagents.ai/framework)
247
+ - [Examples](https://docs.activeagents.ai/agents)
204
248
 
205
249
  ## Contributing
206
250
 
207
- We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
251
+ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.MD) for details.
208
252
 
209
253
  ## License
210
254
 
@@ -5,6 +5,7 @@ require "active_support/core_ext/module/anonymous"
5
5
  require "active_support/core_ext/string/inflections"
6
6
 
7
7
  require "active_agent/concerns/callbacks"
8
+ require "active_agent/concerns/delegation"
8
9
  require "active_agent/concerns/observers"
9
10
  require "active_agent/concerns/parameterized"
10
11
  require "active_agent/concerns/preview"
@@ -42,6 +43,7 @@ module ActiveAgent
42
43
  include AbstractController::Caching
43
44
 
44
45
  include Callbacks
46
+ include Delegation
45
47
  include Parameterized
46
48
  include Provider
47
49
  include Queueing
@@ -299,18 +301,25 @@ module ActiveAgent
299
301
 
300
302
  # @api private
301
303
  def prepare_prompt_parameters
302
- parameters = prompt_options.deep_dup.except(:locals, *PROTECTED_OPTIONS)
304
+ parameters = prompt_options.deep_dup.except(:locals, :delegations, *PROTECTED_OPTIONS)
303
305
 
304
306
  # Render out proc/lamda attributes before rendering templates
305
307
  parameters.deep_transform_values! { _1.respond_to?(:call) ? _1.call : _1 }
306
308
 
309
+ # Expose declared sub-agents (agent-as-tool) alongside the action's own tools
310
+ parameters = apply_delegated_tools(parameters, prompt_options)
311
+
307
312
  # Strip parameters the target model rejects (e.g. temperature/top_p
308
313
  # on thinking-first models) before they reach the provider.
309
314
  ModelCapabilities.sanitize!(parameters)
310
315
 
311
316
  # Apply Callbacks
317
+ #
318
+ # The trace id is written back so it stays stable for the generation and
319
+ # readable from prompt_options — delegated sub-agents inherit it, which
320
+ # is what lets a delegation tree show up as one trace.
312
321
  parameters.merge!(
313
- trace_id: prompt_options[:trace_id] || SecureRandom.uuid,
322
+ trace_id: prompt_options[:trace_id] ||= SecureRandom.uuid,
314
323
  exception_handler:,
315
324
  stream_broadcaster:,
316
325
  tools_function:,