activeagent 1.8.1 → 1.9.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1058 -0
- data/README.md +9 -1
- data/lib/active_agent/base.rb +22 -1
- data/lib/active_agent/concerns/delegation.rb +4 -0
- data/lib/active_agent/concerns/input_requests.rb +178 -0
- data/lib/active_agent/concerns/parameterized.rb +7 -4
- data/lib/active_agent/concerns/provider.rb +1 -0
- data/lib/active_agent/concerns/tooling.rb +7 -2
- data/lib/active_agent/delegation/runner.rb +31 -0
- data/lib/active_agent/evals/catalog.rb +305 -0
- data/lib/active_agent/evals/ruby_llm.rb +411 -0
- data/lib/active_agent/evals/scenario.rb +10 -3
- data/lib/active_agent/evals/scenario_parser.rb +38 -10
- data/lib/active_agent/evals.rb +1 -0
- data/lib/active_agent/generation.rb +62 -2
- data/lib/active_agent/generation_job.rb +14 -3
- data/lib/active_agent/input_request/resume.rb +199 -0
- data/lib/active_agent/input_request.rb +279 -0
- data/lib/active_agent/providers/_base_provider.rb +153 -31
- data/lib/active_agent/providers/anthropic/transforms.rb +57 -28
- data/lib/active_agent/providers/anthropic_provider.rb +102 -20
- data/lib/active_agent/providers/common/responses/prompt.rb +21 -0
- data/lib/active_agent/providers/concerns/input_requests.rb +332 -0
- data/lib/active_agent/providers/concerns/mcp_serving.rb +267 -0
- data/lib/active_agent/providers/concerns/tool_choice_clearing.rb +18 -3
- data/lib/active_agent/providers/deep_seek_provider.rb +6 -0
- data/lib/active_agent/providers/deepseek/_types.rb +4 -0
- data/lib/active_agent/providers/deepseek/chat/_types.rb +54 -0
- data/lib/active_agent/providers/deepseek/chat/request.rb +121 -0
- data/lib/active_agent/providers/deepseek/options.rb +53 -0
- data/lib/active_agent/providers/deepseek_provider.rb +46 -0
- data/lib/active_agent/providers/log_subscriber.rb +13 -0
- data/lib/active_agent/providers/mcp_bridge.rb +691 -0
- data/lib/active_agent/providers/mcp_tool_cache.rb +230 -0
- data/lib/active_agent/providers/open_ai/_base.rb +1 -1
- data/lib/active_agent/providers/open_ai/chat_provider.rb +22 -3
- data/lib/active_agent/providers/open_ai/options.rb +1 -1
- data/lib/active_agent/providers/open_ai/responses_provider.rb +68 -15
- data/lib/active_agent/providers/open_router/options.rb +6 -2
- data/lib/active_agent/providers/ruby_llm/messages/_types.rb +3 -1
- data/lib/active_agent/providers/ruby_llm/options.rb +5 -0
- data/lib/active_agent/providers/ruby_llm_provider.rb +127 -29
- data/lib/active_agent/schema_tools.rb +12 -0
- data/lib/active_agent/telemetry/instrumentation.rb +10 -1
- data/lib/active_agent/version.rb +1 -1
- data/lib/active_agent.rb +2 -0
- data/lib/generators/active_agent/schema_tools/USAGE +11 -0
- data/lib/generators/active_agent/schema_tools/schema_tools_generator.rb +39 -4
- data/lib/generators/active_agent/schema_tools/templates/schema_tools.rb.tt +13 -0
- metadata +31 -2
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,1022 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.9.1] - 2026-10-16
|
|
11
|
+
|
|
12
|
+
Releases `activeagent` and `actionagent` 1.9.1, with the matching browser
|
|
13
|
+
sidecar version. Adds user feedback to the dashboard. Upgrade by running
|
|
14
|
+
`bin/rails generate action_agent:install --skip` and `bin/rails db:migrate`
|
|
15
|
+
before deploying the updated dashboard (migration 027).
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Interaction feedback in `actionagent`: submit thumbs up/down and optional
|
|
20
|
+
comments through the authenticated feedback API, then review and filter votes
|
|
21
|
+
in an agent’s Feedback tab. Includes response span attribution, retry-safe vote
|
|
22
|
+
updates, workspace scoping, an upgrade migration and user feedback docs (#591).
|
|
23
|
+
|
|
24
|
+
## [1.9.0] - 2026-10-14
|
|
25
|
+
|
|
26
|
+
Releases `activeagent` and `actionagent` 1.9.0 from one tag. A minor release.
|
|
27
|
+
The dashboard gains **Projects**: a GitHub repository boots in a checkout
|
|
28
|
+
sandbox with a browser of its own, which a person can watch live and take
|
|
29
|
+
over, and an agent is evaluated against the running app. A browser run can
|
|
30
|
+
stop for a person: `request_handoff` records where the agent stopped and
|
|
31
|
+
Session Replay's **Take Over Session** opens that page. A run can pause to ask
|
|
32
|
+
the user, for an approval or a secret, and resume with the answer. Checkouts
|
|
33
|
+
can go through a GitHub App, provider keys can be personal as well as the
|
|
34
|
+
organization's, the explorer agent proposes scenarios from a project's app,
|
|
35
|
+
and a sandbox can open a draft pull request. The framework gains a DeepSeek
|
|
36
|
+
provider; `mcps:` runs client-side on every provider through `MCPBridge`, with
|
|
37
|
+
`mcp_strategy:` and a cached tool list (needs the optional `mcp` gem); a
|
|
38
|
+
generation can pause to ask the user and require approval before a tool runs;
|
|
39
|
+
the RubyLLM provider reports cache and reasoning tokens, streams tool-call
|
|
40
|
+
fragments correctly, carries usage and stop reason for streamed turns and
|
|
41
|
+
takes a `protocol:` option on ruby_llm 2.x; the flat `json_schema`
|
|
42
|
+
response_format is accepted by every provider. Evaluation scenarios can live
|
|
43
|
+
in a YAML **catalog** of products and sets in the repository, imported from a
|
|
44
|
+
connected repository at a ref, written to Active Storage and run one set at a
|
|
45
|
+
time as an evaluation that records the catalog version it tested; every
|
|
46
|
+
What-to-fix card copies a Markdown fix brief for a coding harness, or hands
|
|
47
|
+
it to Claude Code in the project's checkout sandbox and verifies the change
|
|
48
|
+
against the same scenarios; a user can sign Claude Code in to a sandbox with
|
|
49
|
+
their own Claude subscription (`claude_code_auth = :sandbox_login`, opt-in);
|
|
50
|
+
and Active Storage is one engine option (`config.active_storage`). When a
|
|
51
|
+
project's boot fails, Claude Code fixes the step in its checkout and the
|
|
52
|
+
boot resumes; a project's browser opens from its own page; Catalogs become
|
|
53
|
+
**Scenarios**, written, generated from a project's app, exported and
|
|
54
|
+
imported in the dashboard; and destructive actions confirm in the
|
|
55
|
+
dashboard's own dialog. Carries the 1.8.2 owner fix.
|
|
56
|
+
|
|
57
|
+
Upgrading: run `bin/rails generate action_agent:install --skip` and
|
|
58
|
+
`bin/rails db:migrate`. The generator adds the migrations an install lacks,
|
|
59
|
+
now shipped as numbered templates: input requests, recording events, sandbox
|
|
60
|
+
browsers, projects and their secrets, GitHub App installations, explorations,
|
|
61
|
+
draft pull requests, scenario catalogs and evaluation fixes (migration 026:
|
|
62
|
+
code sessions linked to the fix they implement, and the sandbox's Claude
|
|
63
|
+
login owner). `AgentRun` gains the
|
|
64
|
+
`awaiting_input` status, which
|
|
65
|
+
a client that treats every status other than `pending` and `running` as
|
|
66
|
+
finished must handle. Session Replay is now **Sessions** in the sidebar.
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
|
|
70
|
+
- **Scenario catalogs** (`activeagent`). `ActiveAgent::Evals::Catalog` reads a
|
|
71
|
+
YAML document of products, each with sets of scenarios, from one or more
|
|
72
|
+
files (`Catalog.load`) or strings (`Catalog.parse`), layering later
|
|
73
|
+
documents over earlier ones by key as `Suite.load` does. A suite document
|
|
74
|
+
(`suite:` and `groups:`) loads as a catalog of one product whose sets are
|
|
75
|
+
its groups. `catalog.scenarios(product:, set:, keys:,
|
|
76
|
+
include_production_only:)` narrows the scenarios, `suite_for(product, set)`
|
|
77
|
+
hands one set to the `Runner` as a `Suite` named `catalog/product/set`,
|
|
78
|
+
`to_yaml` writes the canonical document and `digest` is its SHA-256. A
|
|
79
|
+
`Scenario` now carries free-form `tags` and `params` from the document.
|
|
80
|
+
- **Scenario catalogs on the dashboard** (`actionagent`). The Scenarios page and
|
|
81
|
+
`/api/scenario_catalogs` import a catalog from pasted YAML, an uploaded
|
|
82
|
+
file or a connected repository at a ref (`.activeagents/evals/*.yml` by
|
|
83
|
+
default), store it as `scenario_catalogs`, `scenario_products`,
|
|
84
|
+
`scenario_sets` and `catalog_scenarios` records under the owner (install
|
|
85
|
+
generator migration 025), export the canonical YAML, and write it to
|
|
86
|
+
Active Storage once per digest when the engine has it, with a pull that
|
|
87
|
+
restores the records from the stored document. Running a set materializes
|
|
88
|
+
it as an evaluation of the product's agent named `catalog/product/set`,
|
|
89
|
+
refreshing scenarios by key with removed ones disabled, and queues a run
|
|
90
|
+
whose `selection.catalog` and the evaluation's `config.catalog` name the
|
|
91
|
+
catalog, product, set and document digest, so results, traces and
|
|
92
|
+
recordings resolve to the version they tested. A set may run against a
|
|
93
|
+
project instead: its sandbox boots at the project's checkout ref and every
|
|
94
|
+
replay reaches the app and its browser, which is how a branch's acceptance
|
|
95
|
+
set runs from the dashboard. Importing and running need the
|
|
96
|
+
`replace_scenarios` permission; a run is gated as an evaluation run is.
|
|
97
|
+
The MCP facade gains `catalogs_list`, `catalogs_import` and
|
|
98
|
+
`catalog_set_run`, and `action_agent:catalogs:import`, `:export` and
|
|
99
|
+
`:run` rake tasks do the same from a shell.
|
|
100
|
+
- **Manage scenarios in the dashboard** (`actionagent`). Catalogs are now
|
|
101
|
+
**Scenarios** in the sidebar, at `/scenarios`, with `/catalogs` redirecting;
|
|
102
|
+
the tables, the API and the YAML `catalog:` key are unchanged. A collection
|
|
103
|
+
can be started empty and its products, sets and scenarios added, edited and
|
|
104
|
+
deleted in place (prompt, notes as the rubric, expected tools, contains and
|
|
105
|
+
never-contains, tags, enabled, production-only), each change validated as an
|
|
106
|
+
import validates a document. The **Generate** tab picks a project, runs the
|
|
107
|
+
explorer in its sandbox's browser beside the live view, and accepts its
|
|
108
|
+
proposals (the same records as the project's Explorations) into a chosen
|
|
109
|
+
set. A collection, or any products, sets and scenarios checked, exports as
|
|
110
|
+
YAML to download or copy; an import (pasted, uploaded or from a repository)
|
|
111
|
+
previews by key what it creates, overwrites, removes or keeps before it
|
|
112
|
+
writes, and in the dashboard keeps what the document leaves out by default.
|
|
113
|
+
- **Fix a failed project boot with Claude Code** (`actionagent`). When a
|
|
114
|
+
project's boot fails with its checkout kept, on a backend that runs Claude
|
|
115
|
+
Code and resumes boots, and the owner's Claude Code is connected,
|
|
116
|
+
`ActionAgent::ProjectBootFix` starts a Claude Code session in the checkout
|
|
117
|
+
with the failed step, the boot's error and the end of the step's log (the
|
|
118
|
+
project's secrets masked, the log fenced as data, not instructions), asking
|
|
119
|
+
for the smallest change. The session may run `bundle install`, `lock` and
|
|
120
|
+
`update`, `gem search`, `list` and `info`, `git status` and `diff`, and
|
|
121
|
+
`npm`/`yarn install` when a JavaScript step failed, and nothing else. When
|
|
122
|
+
it changes files the boot resumes from the failed step and the files join
|
|
123
|
+
the install pull request (the secret scan and the `.github/` exclusion
|
|
124
|
+
still apply); when it changes nothing the boot stays failed with Claude
|
|
125
|
+
Code's answer. Claude Code goes first and the setup assistant after it,
|
|
126
|
+
sharing "Start it when a boot fails" and the three automatic attempts.
|
|
127
|
+
`POST /api/projects/:id/setup/claude_code` starts one by hand, and the
|
|
128
|
+
project's Setup card follows the session live.
|
|
129
|
+
- **A project's browser on its page** (`actionagent`). A **Browser** tab
|
|
130
|
+
beside Overview, Explorations and Environment (`?tab=browser`) starts,
|
|
131
|
+
watches, takes over and hands back the sandbox's browser; the header offers
|
|
132
|
+
**Watch live** while it runs. Settings → Integrations stays where every
|
|
133
|
+
runtime is managed: each sandbox links to its project, and the project
|
|
134
|
+
page links back as **Manage runtimes**. The New project checklist says
|
|
135
|
+
what the backend's browser can do instead of "not available yet".
|
|
136
|
+
- **Copy fix prompt** (`actionagent`). Every What-to-fix card on an
|
|
137
|
+
evaluation run has a *Copy fix prompt* button that puts a Markdown brief
|
|
138
|
+
on the clipboard for a coding harness: the agent, evaluation and run by
|
|
139
|
+
name and id, the fault and recommendation or the judge's instruction
|
|
140
|
+
change, the tools and MCP server involved, each scenario's prompt,
|
|
141
|
+
expectations, tool calls, diagnosis and answer, where to look in the
|
|
142
|
+
agent, and the `evaluations_run`, `evaluation_runs_get` and
|
|
143
|
+
`evaluation_runs_compare` calls that prove the fix, with the evaluation
|
|
144
|
+
id, scenario keys and models filled in.
|
|
145
|
+
- **Implement a fix with Claude Code** (`actionagent`). On an evaluation of a
|
|
146
|
+
project's agent, a fault or instruction card's *Implement with Claude Code*
|
|
147
|
+
starts one Claude Code session in a ready checkout sandbox with a brief the
|
|
148
|
+
server builds from the card (the sandbox cannot reach the dashboard, so the
|
|
149
|
+
brief says where the agent lives and that the dashboard verifies). The
|
|
150
|
+
session records the run and card it fixes (`evaluation_run_id`,
|
|
151
|
+
`fix_item`). When it succeeds with a diff, the backend restarts the
|
|
152
|
+
checkout's app runtime (`refresh_runtime`, on `:local`) and the dashboard
|
|
153
|
+
re-runs exactly the card's scenarios and models against that sandbox
|
|
154
|
+
(`verification_run_id`). The card shows each scenario before and after,
|
|
155
|
+
what was fixed and what regressed; *Try again* carries the previous diff and
|
|
156
|
+
results into a new session, *Re-run the full suite* checks for regressions, and
|
|
157
|
+
*Open draft PR* includes the comparison and both runs. One click starts one
|
|
158
|
+
session; nothing launches fixes in batches or on a schedule.
|
|
159
|
+
- **Sign in to Claude Code inside a sandbox** (`actionagent`). With
|
|
160
|
+
`config.claude_code_auth = :sandbox_login`, a signed-in user connects their
|
|
161
|
+
own Claude subscription from Settings → Integrations, a sandbox's Claude
|
|
162
|
+
Code panel or the fix dialog. The `:local` backend runs the unmodified
|
|
163
|
+
`claude auth login` under a PTY with the sandbox's own `CLAUDE_CONFIG_DIR`;
|
|
164
|
+
the user authorizes on Claude's site and pastes the one-time code, which is
|
|
165
|
+
written once to the CLI and never stored, logged or returned. The login
|
|
166
|
+
belongs to that user and that sandbox (`claude_login_user_id`), and no other
|
|
167
|
+
member's session runs beside it; they use a sandbox of their own, with
|
|
168
|
+
their own login or the account's API key. Sessions on it get no
|
|
169
|
+
Anthropic credential variables, credential paths are excluded from file
|
|
170
|
+
reads, diffs and draft pull requests, and disconnect, stop and expiry run
|
|
171
|
+
`claude auth logout` and remove the config. Backends without the four login
|
|
172
|
+
verbs refuse the mode, and a remote backend also needs
|
|
173
|
+
`config.claude_code_hosted_login_enabled`; what a host-registered backend
|
|
174
|
+
raises from a login verb comes out as `SandboxOrchestrator::BackendError`
|
|
175
|
+
and answers with the same fixed message. `:api_key` stays the default.
|
|
176
|
+
- **Active Storage as one engine option** (`actionagent`). `config.active_storage`
|
|
177
|
+
decides whether the dashboard attaches files anywhere: run attachments, a
|
|
178
|
+
recording's snapshots and oversized event payloads, and scenario catalogs.
|
|
179
|
+
`:auto` (the default) uses Active Storage when the host has it loaded and
|
|
180
|
+
migrated, `true` requires it and raises `ActionAgent::ConfigurationError`
|
|
181
|
+
at boot without it, `false` never attaches. `config.active_storage_service`
|
|
182
|
+
names the `config/storage.yml` service those attachments go to. The
|
|
183
|
+
models' `defined?(ActiveStorage)` guards now read the option, and the
|
|
184
|
+
unused `config.storage_service` accessor is gone.
|
|
185
|
+
- **Hand a browser run to a person** (`actionagent`). Agents with the
|
|
186
|
+
`playwright_mcp` tools gain `request_handoff`: the agent calls it when a
|
|
187
|
+
page asks for something only its owner may give (payment details, a login
|
|
188
|
+
code), the run's session recording stores the page URL and the non-secret
|
|
189
|
+
values already entered, and Session Replay's **Take Over Session** opens
|
|
190
|
+
that page for the person. Browser tool calls in such runs are recorded, so
|
|
191
|
+
the run plays back in Session Replay.
|
|
192
|
+
|
|
193
|
+
- **Conference Ticket Agent template** (`actionagent`). Registers on an
|
|
194
|
+
event's ticket page and stops before paying, built for the SF Ruby
|
|
195
|
+
Conference demo. The Playwright demo template now names the published
|
|
196
|
+
`@playwright/mcp` package.
|
|
197
|
+
|
|
198
|
+
- **Conference-ticket sample data** (`actionagent`). `bin/rails
|
|
199
|
+
action_agent:sample:conference_ticket` seeds a workspace with fictional
|
|
200
|
+
but complete data for the scenario: the agent, a week of runs with their
|
|
201
|
+
telemetry traces (browser tool spans, model calls, the handoff), a
|
|
202
|
+
completed session recording that offers Take Over at the ticket page, and
|
|
203
|
+
an evaluation whose two runs show a caught regression — the agent once
|
|
204
|
+
clicked Pay — and the fix. Every record carries a `sample` marker;
|
|
205
|
+
`action_agent:sample:clear` removes it all. `ACCOUNT_ID` names the
|
|
206
|
+
workspace on a multi-tenant install.
|
|
207
|
+
|
|
208
|
+
- **Typing and forms in the browser tools** (`actionagent`). The
|
|
209
|
+
`playwright_mcp` tools gain `browser_type`, `browser_fill_form`,
|
|
210
|
+
`browser_select_option` and `browser_press_key`, and address elements the
|
|
211
|
+
way current Playwright MCP releases expect (`target`), accepting the `ref`
|
|
212
|
+
the schemas ask the model for.
|
|
213
|
+
|
|
214
|
+
- **Support MCP across providers** (`activeagent`). Anthropic and OpenAI
|
|
215
|
+
Responses handle remote servers natively; ActiveAgent bridges other providers
|
|
216
|
+
and all local `command:` servers. `mcp_strategy:` selects automatic, client,
|
|
217
|
+
or required-native execution. Tool lists are cached for five minutes; a cold
|
|
218
|
+
cache connects during discovery, while a warm cache connects only when a tool
|
|
219
|
+
is called. `mcp_cache: false` bypasses the cache for one generation. Entries
|
|
220
|
+
are scoped by endpoint, credentials, environment, and `allowed_tools:`. The
|
|
221
|
+
client-side bridge requires the optional `mcp` gem.
|
|
222
|
+
|
|
223
|
+
- **Add a DeepSeek provider** (`activeagent`). `generate_with :deepseek` uses
|
|
224
|
+
DeepSeek's OpenAI-compatible API, `deepseek-flash` by default, and
|
|
225
|
+
`DEEPSEEK_API_KEY` as the credential fallback. JSON output and tool calling
|
|
226
|
+
are native. Instructions are sent as `system` messages, and provider defaults
|
|
227
|
+
such as thinking mode are left unchanged. Thinking is billed and disables
|
|
228
|
+
sampling parameters; prompts can opt out with `thinking: { type: "disabled" }`.
|
|
229
|
+
|
|
230
|
+
- **Cache and reasoning tokens from the RubyLLM provider** (`activeagent`).
|
|
231
|
+
`response.usage` now carries `cached_tokens`, `cache_creation_tokens` and
|
|
232
|
+
`reasoning_tokens` when RubyLLM counted them, and a response that calls
|
|
233
|
+
tools reports `tool_use` as its `finish_reason` however the API ends it;
|
|
234
|
+
OpenAI's Responses API ends one with `stop` (#502).
|
|
235
|
+
|
|
236
|
+
- **A `protocol:` option for the RubyLLM provider** (`activeagent`). Pins
|
|
237
|
+
which wire protocol carries a request, as RubyLLM's own `protocol:` does:
|
|
238
|
+
`:chat_completions` or `:responses` for OpenAI. Set it in `generate_with`
|
|
239
|
+
or `config/active_agent.yml`, beside `platform:`. ruby_llm 2.x sends OpenAI
|
|
240
|
+
chat to the Responses API (`/v1/responses`) where 1.16 used Chat
|
|
241
|
+
Completions, and an OpenAI-compatible server behind `openai_api_base` may
|
|
242
|
+
not implement it; `protocol: :chat_completions` keeps an agent on the old
|
|
243
|
+
endpoint, and `config.openai_protocol` in `RubyLLM.configure` keeps every
|
|
244
|
+
agent there. The option needs ruby_llm 2.x; with 1.16, which has no other
|
|
245
|
+
protocol to choose, it raises `ArgumentError` rather than being ignored
|
|
246
|
+
(#502).
|
|
247
|
+
|
|
248
|
+
- **Ship dashboard migrations as numbered templates** (`actionagent`).
|
|
249
|
+
`rails g action_agent:install` also emits every
|
|
250
|
+
`templates/migrations/NNN_<name>.rb.erb` in the generator, in number order and
|
|
251
|
+
after the other dashboard migrations, skipping any whose name `db/migrate`
|
|
252
|
+
already holds. A new engine migration is one new template file.
|
|
253
|
+
|
|
254
|
+
- **Ask the host before privileged dashboard actions** (`actionagent`).
|
|
255
|
+
`ActionAgent.permission_checker = ->(user, action, subject) { ... }` is asked
|
|
256
|
+
before a provider credential is stored, tested or deleted
|
|
257
|
+
(`:manage_credentials`), before GitHub is connected, its repositories chosen
|
|
258
|
+
or it is disconnected (`:manage_github`), and before an API key is created or
|
|
259
|
+
revoked (`:manage_api_keys`). A denial answers 403, except that the GitHub
|
|
260
|
+
connect and callback navigations return to Settings instead. The remaining
|
|
261
|
+
actions in `ActionAgent::PERMISSION_ACTIONS` are reserved for features that
|
|
262
|
+
need them. Unset, every action is allowed as before, which in multi-tenant
|
|
263
|
+
mode means every member of a tenant may perform every action; the engine
|
|
264
|
+
logs a warning at boot in that case. A checker that raises denies; in
|
|
265
|
+
multi-tenant mode a nil answer or a missing user also denies.
|
|
266
|
+
|
|
267
|
+
- **Record who created an API key** (`actionagent`). A key created in Settings
|
|
268
|
+
stores the signed-in user beside its owner, and an MCP call made with an
|
|
269
|
+
account's key runs as that user. Keys created earlier have no creator and
|
|
270
|
+
behave as before.
|
|
271
|
+
|
|
272
|
+
- **Organization and personal provider keys** (`actionagent`). A provider
|
|
273
|
+
key is an organization key, shared by the account's members, or a member's
|
|
274
|
+
personal key. With `ActionAgent.provider_key_scope = :personal_override`, a
|
|
275
|
+
member saves personal keys in Settings → API Keys, and the runs they start,
|
|
276
|
+
the dashboard assistant and the model pickers use them before the
|
|
277
|
+
organization's; the judge, sandboxes, Claude Code and Codex use organization
|
|
278
|
+
keys only, as do scenario replays on a multi-tenant install, so the
|
|
279
|
+
evaluation form offers only the providers those keys cover. A personal
|
|
280
|
+
Ollama host decides where the server sends requests, so saving or testing
|
|
281
|
+
one asks `permission_checker` for `:manage_credentials`, with the personal
|
|
282
|
+
key as the subject, as an organization key does. Every lookup goes through
|
|
283
|
+
`ActionAgent::ProviderCredentials.resolve(owner:, actor:, provider:)`: the
|
|
284
|
+
actor's personal key, then `provider_credentials_resolver`, then the
|
|
285
|
+
organization key, then `config/active_agent.yml`. A resolver that declares
|
|
286
|
+
`actor:` is told who is acting. With `multi_tenant` on, an owner that does
|
|
287
|
+
not resolve to the configured owner class, or a resolver that raises, fails
|
|
288
|
+
the generation with `ProviderCredentials::Unresolved` instead of using the
|
|
289
|
+
platform's credentials. The provider keys API takes `scope=organization` or
|
|
290
|
+
`scope=personal` and returns `scope`, `effective_source`, `set_by` and
|
|
291
|
+
`updated_at`, and `POST /api/provider_keys/test` sends a stored key only to
|
|
292
|
+
the stored host. The Organization page manages the
|
|
293
|
+
organization's keys and lists the members `ActionAgent.members_resolver`
|
|
294
|
+
returns, with "+ Invite Member" linking to `ActionAgent.member_invite_url`
|
|
295
|
+
(`GET /api/members`). **Upgrading:** run the new `add_provider_key_scope`
|
|
296
|
+
migration (`rails g action_agent:install` emits it); it stops and lists the
|
|
297
|
+
keys if an account holds two for one provider. A host that keeps its own
|
|
298
|
+
`provider_keys` table with a unique `(account_id, provider)` index replaces
|
|
299
|
+
it with `(account_id, scope_key, provider)`. Before turning on
|
|
300
|
+
`:personal_override`, change any `provider_credentials_resolver` that reads
|
|
301
|
+
`provider_keys` by provider alone so it reads organization rows only
|
|
302
|
+
(`ProviderKey.for_owner`), or remove it.
|
|
303
|
+
|
|
304
|
+
- **Declare optional sandbox backend verbs** (`actionagent`).
|
|
305
|
+
`SandboxOrchestrator` dispatches `changed_files`, `read_file`,
|
|
306
|
+
`start_browser`, `stop_browser` and `resume_boot` to a backend that defines
|
|
307
|
+
them, and `supports?` is false for one that does not. Their signatures are
|
|
308
|
+
documented on the orchestrator and in the dashboard guide; the `:local`
|
|
309
|
+
backend implements `changed_files` and `read_file` only, and the `:mock`
|
|
310
|
+
backend none of them. `read_file` refuses a path outside the checkout before
|
|
311
|
+
the backend sees it.
|
|
312
|
+
|
|
313
|
+
- **Check out sandboxes through a GitHub App** (`actionagent`). Configure
|
|
314
|
+
`github_app_id`, `github_app_private_key`, `github_app_slug`,
|
|
315
|
+
`github_app_client_id` and `github_app_client_secret` (or the matching
|
|
316
|
+
`GITHUB_APP_*` variables), and Settings -> Integrations offers **Install
|
|
317
|
+
the GitHub App**. An installation is linked only after the App's user
|
|
318
|
+
authorization shows the user is the account it is installed on or an
|
|
319
|
+
active admin of its organization, and that user's token is then dropped.
|
|
320
|
+
An installation belongs to one owner at most. Its repositories are chosen
|
|
321
|
+
like the OAuth connection's, and a checkout of one of them mints a one-hour
|
|
322
|
+
token limited to that repository's contents, once per provision, held in
|
|
323
|
+
memory only. When a repository is reachable both ways, the installation
|
|
324
|
+
is used. The App's JWT is signed with OpenSSL, so there is no new
|
|
325
|
+
dependency. Run `rails g action_agent:install` and `rails db:migrate` for
|
|
326
|
+
the `github_installations` table and
|
|
327
|
+
`sandbox_sessions.github_installation_id`. The OAuth connection works as
|
|
328
|
+
before. Installing, linking, listing and choosing an installation's
|
|
329
|
+
repositories, and unlinking it ask `ActionAgent.permission_checker` for
|
|
330
|
+
`:manage_github`. An installation GitHub reports removed or suspended is
|
|
331
|
+
marked, and **Check again** clears the mark once GitHub serves it again.
|
|
332
|
+
|
|
333
|
+
- **Create the GitHub App from Settings** (`actionagent`). On a
|
|
334
|
+
single-tenant dashboard, **Create GitHub App** posts a manifest to GitHub
|
|
335
|
+
and shows the new App's credentials once, with the configuration to add.
|
|
336
|
+
The dashboard stores none of them.
|
|
337
|
+
|
|
338
|
+
- **Mask GitHub tokens in sandbox output by their shape** (`actionagent`).
|
|
339
|
+
`SecretScrubber` masks `ghp_`, `gho_`, `ghu_`, `ghs_`, `ghr_` and
|
|
340
|
+
`github_pat_` tokens even when it is given no values, so a code session's
|
|
341
|
+
transcript masks a checkout token it never knew.
|
|
342
|
+
|
|
343
|
+
- **Store browser events on session recordings, and read any session as a
|
|
344
|
+
timeline** (`actionagent`). `POST <mount>/api/session_recordings/:id/events`
|
|
345
|
+
takes a batch of `rrweb`, `console` and `marker` events under the
|
|
346
|
+
`recording_events` key, authenticated by the recording's ingest token
|
|
347
|
+
(`SessionRecording#issue_ingest_token!`, stored as a digest, expiring with
|
|
348
|
+
the recording or its sandbox) or by a dashboard session that owns the
|
|
349
|
+
recording. Each batch's clock offset is applied, so events are stored in
|
|
350
|
+
server time, and an event more than a day before the batch's `sent_at` is
|
|
351
|
+
refused. Batches over `ActionAgent.recording_limits` answer 413 and are
|
|
352
|
+
counted as dropped. The engine adds `recording_events` to the app's
|
|
353
|
+
`filter_parameters`. `GET .../events?after=` reads them back in time order.
|
|
354
|
+
`GET <mount>/api/session_recordings/:id/timeline` and
|
|
355
|
+
`GET <mount>/api/sessions/{context,run,scenario_result}/:id/timeline` return
|
|
356
|
+
message, LLM, tool and browser lanes, derived when read from runs, messages,
|
|
357
|
+
generations and telemetry spans. An agent's browser tool calls are recorded
|
|
358
|
+
as `action` events with typed values masked. A recording can belong to a
|
|
359
|
+
conversation (`agent_context_id`) and records its `source`. Deleting a
|
|
360
|
+
recording asks the permission checker about `:manage_recordings`. Adds
|
|
361
|
+
migration `create_active_agent_recording_events`.
|
|
362
|
+
|
|
363
|
+
- **Boot a checkout that does not bundle the engine** (`actionagent`). An
|
|
364
|
+
`app_runtime` sandbox of a Rails app whose `Gemfile.lock` locks no
|
|
365
|
+
`actionagent` now installs it first: the dashboard's own engine version (or
|
|
366
|
+
the same git revision or path), the generators with `--skip`, asset builds
|
|
367
|
+
the app defines, `db:prepare`, then the manifest. `POST /api/sandboxes`
|
|
368
|
+
takes `bootstrap` (`auto` by default, `always` or `never`), `start_url` and
|
|
369
|
+
`keep_on_failure`. Before any of a bootstrapped checkout's commands runs, a
|
|
370
|
+
preflight refuses one without a root `config/application.rb`, or locking
|
|
371
|
+
Ruby older than 3.2 or railties older than 7.2. With `"auto"` a checkout
|
|
372
|
+
without a `Gemfile.lock` boots as before, and with `"always"` the preflight
|
|
373
|
+
refuses it. Each step has a log and a timeout of its own (1800 seconds for
|
|
374
|
+
the whole bootstrap, or `local_sandbox_boot_timeout` on `:local` when that
|
|
375
|
+
is longer), and `GET /api/sandboxes/:id/boot` and `…/boot_log` read them
|
|
376
|
+
back, scrubbed.
|
|
377
|
+
With `keep_on_failure`, `POST /api/sandboxes/:id/resume_boot` continues a
|
|
378
|
+
failed boot from the step that failed, on the same checkout and databases.
|
|
379
|
+
Once the MCP facade answers, `start_url` must not answer `5xx`. The boot is
|
|
380
|
+
a plain-JSON spec (`ActionAgent::SandboxBootSpec`) the orchestrator hands a
|
|
381
|
+
backend whose `create_sandbox` takes `boot_config:`; any other backend boots
|
|
382
|
+
as before. The `:local` backend implements `resume_boot`, `boot_status` and
|
|
383
|
+
`boot_log`, and the reaper releases a kept boot once its sandbox expires.
|
|
384
|
+
|
|
385
|
+
- **Sync a checkout's agents in the sandbox manifest** (`actionagent`).
|
|
386
|
+
`bin/rails action_agent:sandbox:manifest` also mirrors the app's
|
|
387
|
+
`app/agents` classes with `AgentSync`, so a sandbox's MCP facade serves
|
|
388
|
+
their `run_<slug>` tools. The manifest's API key and the synced agents each
|
|
389
|
+
belong to the only record of the class their model is owned through, when
|
|
390
|
+
there is exactly one (the only account for the key and the only user for the
|
|
391
|
+
agents, in an app that configures both), and to nobody in an app with no
|
|
392
|
+
owner model, where `AgentSync` now accepts a nil owner.
|
|
393
|
+
|
|
394
|
+
- **Create evaluations and merge scenarios over MCP** (`actionagent`). The
|
|
395
|
+
facade's `evaluations_create` tool creates an evaluation of one of the key's
|
|
396
|
+
agents, with scenarios or criteria, and runs nothing. `scenarios_merge` adds
|
|
397
|
+
scenarios to an evaluation and updates the ones whose keys it holds, through
|
|
398
|
+
the new `Evaluation#merge_scenarios!`, which never removes, disables or
|
|
399
|
+
reorders a scenario it was not given. A scenario without a key gets the next
|
|
400
|
+
`<group>_<n>` the evaluation does not use, and `key_prefix` namespaces
|
|
401
|
+
generated keys. Both tools ask the permission checker about
|
|
402
|
+
`:replace_scenarios` as the key's user, refuse an observed agent's suite as
|
|
403
|
+
`evaluations_run` does, and are held to 100 evaluations per agent, 2,000
|
|
404
|
+
scenarios per evaluation, 2 MiB of scenarios per call, and 200-character
|
|
405
|
+
scenario keys, groups and judge models. A refused call writes nothing.
|
|
406
|
+
|
|
407
|
+
- **Generate scenario keys around keys already in use** (`activeagent`).
|
|
408
|
+
`ScenarioParser.parse` and `.scenarios` take `reserved_keys:`, which
|
|
409
|
+
generated keys skip ignoring case, and `key_prefix:`, a namespace in front
|
|
410
|
+
of every generated key. A prefix with no letters or digits a key can use
|
|
411
|
+
raises `ParseError`. Keys a paste names are unchanged.
|
|
412
|
+
|
|
413
|
+
- **Pause a generation to ask the user, and resume it with the answer**
|
|
414
|
+
(`activeagent`). A tool returns an `ActiveAgent::InputRequest` (`:text`,
|
|
415
|
+
`:choice`, `:confirm` or `:secret`); the turn's other calls finish, nothing
|
|
416
|
+
is sent back to the model, and the response is `awaiting_input?` with
|
|
417
|
+
`input_requests` and a JSON-safe `checkpoint`. `Generation#resume_now(checkpoint:,
|
|
418
|
+
answers:)`, on the generation that paused or on one built the same way,
|
|
419
|
+
restores the conversation and dispatches each paused call again with its
|
|
420
|
+
answer readable through `input_answer` / `InputRequest.answer_for`; `false`
|
|
421
|
+
declines without running the tool. A `:secret` answer is scrubbed from tool
|
|
422
|
+
results, telemetry tool spans and tool errors. Pauses publish
|
|
423
|
+
`input_requested.active_agent` and run `on_input_request` callbacks; a
|
|
424
|
+
delegated agent that pauses returns `{ error: "input_required" }` to its
|
|
425
|
+
caller instead, without announcing the pause, and raises
|
|
426
|
+
`Delegation::InputRequiredError` under `on_exceeded: :raise`. Supported by
|
|
427
|
+
every built-in tool loop, streamed or not: Anthropic, OpenAI Chat
|
|
428
|
+
Completions, OpenAI Responses (reasoning items included) and RubyLLM. Under a
|
|
429
|
+
custom provider that does not run its calls through `dispatch_tool_calls`, a
|
|
430
|
+
tool that asks raises `InputRequest::UnsupportedProviderError`.
|
|
431
|
+
|
|
432
|
+
- **Let a dashboard run stop to ask a person, and continue with the answer**
|
|
433
|
+
(`actionagent`). A paused run is `awaiting_input`, with one
|
|
434
|
+
`ActionAgent::InputRequest` per paused tool call (migration `016`, emitted by
|
|
435
|
+
`rails g action_agent:install`). The request's owner is copied from the run's
|
|
436
|
+
agent, and its answer and the pause's checkpoint are encrypted at rest. The
|
|
437
|
+
`ask` tools (`ask_user`, `request_approval`) raise `text`, `choice` and
|
|
438
|
+
`confirm` requests. An agent's `approval_required_tools` holds a listed
|
|
439
|
+
tool's call until a person approves it, and changing the list makes the
|
|
440
|
+
agent's evaluations stale. `request_secret` is reserved for agents the
|
|
441
|
+
engine defines. `GET /api/input_requests` lists requests without their
|
|
442
|
+
answers. `POST /api/input_requests/:id/answer` and `/decline` check
|
|
443
|
+
`:answer_input_request` (with no checker, a multi-tenant install lets only
|
|
444
|
+
the run's actor answer), are refused while execution is disabled, and
|
|
445
|
+
return 409 for a settled or expired request and 422 for an invalid answer.
|
|
446
|
+
A `confirm` request is approved by `true` and declined by `false`. Once a
|
|
447
|
+
pause is settled, `AgentResumeJob` resumes the same run under its trace id.
|
|
448
|
+
MCP `run_<slug>` returns a paused run's request ids, and
|
|
449
|
+
`input_requests_list` and `input_requests_answer` (text and choice only)
|
|
450
|
+
join the MCP facade. `config.input_request_ttl` (one day by default) bounds
|
|
451
|
+
how long a request waits, and `InputRequestExpiryJob`, which a host
|
|
452
|
+
schedules, fails the runs whose requests expired unread.
|
|
453
|
+
|
|
454
|
+
- **Open a draft pull request from a sandbox** (`actionagent`). A ready
|
|
455
|
+
checkout's **Open draft PR** previews the exact diff of the files it would
|
|
456
|
+
publish, then publishes them from the dashboard's own process through
|
|
457
|
+
GitHub's Git Data API: a commit on the checkout commit, a new branch (an
|
|
458
|
+
existing one is refused, never overwritten) and a draft pull request. No
|
|
459
|
+
git process holds the token, which is minted at publish time for the one
|
|
460
|
+
repository, or is the OAuth connection's token when the user who connected
|
|
461
|
+
it publishes and its scopes allow writing. `.github/`, symlinks,
|
|
462
|
+
submodules, oversize files and files holding one of the sandbox's secrets
|
|
463
|
+
or a GitHub token are never published, and a file that changed since the
|
|
464
|
+
preview refuses the publish. One publish carries at most 300 files and
|
|
465
|
+
10 MB, chosen from every changed file. **Update draft PR** fast-forwards
|
|
466
|
+
the branch with a commit message of its own; a draft GitHub refuses keeps
|
|
467
|
+
the branch and offers a regular pull request as a second step. Where
|
|
468
|
+
nothing can write, **Download patch** offers the same files for `git am`.
|
|
469
|
+
Publishing asks `ActionAgent.permission_checker` for
|
|
470
|
+
`:publish_pull_request`, also when its job runs, needs a ready or running
|
|
471
|
+
sandbox, and runs one at a time per sandbox; a publish that never finishes
|
|
472
|
+
is failed as stalled after 15 minutes. The `:local` backend now implements
|
|
473
|
+
`changed_files` and `read_file`, which gains `base: true` (a backend whose
|
|
474
|
+
`read_file` takes no `base:` offers no publishing), and checks every object
|
|
475
|
+
of the checkout commit it reads against its id. The OAuth connect callback
|
|
476
|
+
records the user who connected. Run `rails g action_agent:install` and
|
|
477
|
+
`rails db:migrate` for the `draft_pull_requests` table.
|
|
478
|
+
|
|
479
|
+
- **Answer input requests in the dashboard** (`actionagent`). The
|
|
480
|
+
Interactions nav item shows how many requests are waiting, and Interactions
|
|
481
|
+
opens with a Needs input lane listing them. The runner keeps a paused run in
|
|
482
|
+
flight, shows its requests inline, and after each answer polls the same run
|
|
483
|
+
until its reply arrives. A `?run=` link opens a run that way. One card
|
|
484
|
+
answers every kind: a text field or option buttons (through Generative UI),
|
|
485
|
+
Approve and Decline beside a `confirm` request's arguments, and a password
|
|
486
|
+
field for a `secret`, marked `data-aa-secret` and emptied once sent. The
|
|
487
|
+
Tools tab has an Approval switch per enabled tool that writes the agent's
|
|
488
|
+
`approval_required_tools`, using the `approval_names` the roster endpoint
|
|
489
|
+
now returns for each row.
|
|
490
|
+
|
|
491
|
+
- **Projects** (`actionagent`). A project boots a GitHub repository in a
|
|
492
|
+
checkout sandbox, installing the engine there when the repository lacks it,
|
|
493
|
+
and evaluates an agent against the running app. `ActionAgent::Project` and
|
|
494
|
+
`ActionAgent::ProjectSecret` (migration template `019`, owned by the account
|
|
495
|
+
first) are served under `/api/projects` and the new **Projects** view.
|
|
496
|
+
`GET /api/projects/capabilities` is a checklist of what the install still
|
|
497
|
+
needs, with the line that fixes each item; the mock backend is refused
|
|
498
|
+
outside the test environment. Before any sandbox exists, the repository is
|
|
499
|
+
checked through GitHub's contents API (Ruby, railties, a root
|
|
500
|
+
`config/application.rb`) and its environment variables are found in
|
|
501
|
+
`.env.example`, `.env.sample`, a new `secrets:` key in
|
|
502
|
+
`.activeagents/sandbox.yml` and `ENV` call sites. Both read the commit the
|
|
503
|
+
ref names and are cached per commit. Reading a repository the GitHub
|
|
504
|
+
connection has not selected needs `:manage_github`. Secrets are encrypted,
|
|
505
|
+
never returned, refused for names the sandbox sets or that change how code
|
|
506
|
+
loads, and need `:manage_project_secrets`, as do changing the ref they are
|
|
507
|
+
handed to (preflighted again, and stopping the old ref's sandbox) and
|
|
508
|
+
deleting a project that has them. A provider key variable can use the
|
|
509
|
+
organization's stored key, with consent and `:manage_credentials`, and
|
|
510
|
+
without copying it. Values reach only the steps that run the repository's
|
|
511
|
+
code and are scrubbed, with their URL-encoded and Base64 forms, from
|
|
512
|
+
errors, logs, code-session events and the MCP dashboard tools. The
|
|
513
|
+
project's agent is an App assistant over the sandbox's tools, or a proxy for
|
|
514
|
+
one of the checkout's own agents; Run evaluation boots an expired sandbox
|
|
515
|
+
again and waits for it. The first boot on `:local` asks for confirmation,
|
|
516
|
+
and `quota_checker` is asked about `:project`. A boot spec that does not
|
|
517
|
+
apply to a checkout now still hands it its `secrets`.
|
|
518
|
+
|
|
519
|
+
- **Run a browser per checkout sandbox** (`actionagent`,
|
|
520
|
+
`@activeagents/browser-sidecar`). `POST <mount>/api/sandboxes/:id/browser`
|
|
521
|
+
(`mode: "headless"` or `"headed"`, optional `capabilities`) starts one,
|
|
522
|
+
`GET` shows it and `DELETE` stops it. While it runs, every agent run and
|
|
523
|
+
evaluation against the sandbox reaches it as the MCP server
|
|
524
|
+
`browser:<session_id>`, and the toolbox's own `browser_*` tools are left
|
|
525
|
+
out so no tool name is offered twice. A run whose browser stopped after it
|
|
526
|
+
was queued fails before generation. Each browser records into a session
|
|
527
|
+
recording of its own: rrweb with input values and `contenteditable` text
|
|
528
|
+
masked, console errors and markers, posted gzipped to the recording's
|
|
529
|
+
ingest, which now accepts `Content-Encoding: gzip`. Deleting a sandbox
|
|
530
|
+
stops its browser before expiring it, and a browser stops itself 30
|
|
531
|
+
seconds before its sandbox expires, so the recording keeps its last
|
|
532
|
+
events. Starting asks the quota checker about `:browser_minutes`; each
|
|
533
|
+
stop, including the sandbox's expiry and the reaper, reports the minutes
|
|
534
|
+
used, counted to when the browser stopped itself at the latest. In a
|
|
535
|
+
multi-tenant install both are asked about the sandbox's account. `:local`
|
|
536
|
+
runs the new `@activeagents/browser-sidecar` npm package (in
|
|
537
|
+
`browser-sidecar/`, outside the gem, at the engine's version): Chromium
|
|
538
|
+
with a fresh profile over a pipe, Playwright MCP behind a bearer token and
|
|
539
|
+
`Host`/`Origin` checks, every connection through a proxy in the sidecar
|
|
540
|
+
that refuses loopback, link-local and private addresses but the app's
|
|
541
|
+
(after redirects too), and top-level navigation pinned to the sandbox app,
|
|
542
|
+
with a tab that a redirect takes elsewhere closed. Install it with
|
|
543
|
+
`bin/rails action_agent:browser:install` and check the machine with
|
|
544
|
+
`bin/rails action_agent:browser:doctor`; `ActionAgent.browser_sidecar_path`
|
|
545
|
+
runs a checkout of it instead. The browser token is encrypted like the
|
|
546
|
+
runtime token, never serialized, and masked in recordings and MCP tool
|
|
547
|
+
output. The sidecar also builds as an OCI image. Adds migration
|
|
548
|
+
`add_browser_to_active_agent_sandbox_sessions`.
|
|
549
|
+
|
|
550
|
+
- **Watch a sandbox's browser live and take it over** (`actionagent`,
|
|
551
|
+
`@activeagents/browser-sidecar`). A ready checkout sandbox in Settings →
|
|
552
|
+
Integrations gets a Browser panel to start and stop its browser, and,
|
|
553
|
+
while it runs, a live view with "Watch live", "Take over" and "Hand back".
|
|
554
|
+
The sidecar streams the page on screen over a WebSocket (`/live`),
|
|
555
|
+
following new tabs and the tab the agent selects, and relays the input of
|
|
556
|
+
the one person in control. `POST <mount>/api/sandboxes/:id/browser/tickets`
|
|
557
|
+
issues a ticket that lives 30 seconds and is accepted once: `mode:
|
|
558
|
+
"view"`, sent as the WebSocket's first message, needs access to the
|
|
559
|
+
sandbox, and `mode: "control"`, which every Take over asks for afresh,
|
|
560
|
+
also asks the permission checker about `:take_over_browser`. The
|
|
561
|
+
WebSocket also checks `Host` and the dashboard's
|
|
562
|
+
`Origin` (the one the browser was started from, plus
|
|
563
|
+
`ActionAgent.browser_live_origins`). While a person holds control, an
|
|
564
|
+
agent's browser call that would change the page waits for them to hand
|
|
565
|
+
back and then returns an error naming them; calls that only read the page
|
|
566
|
+
go ahead. Control is released 10 seconds after its holder's connection
|
|
567
|
+
drops, and the same person may continue driving from another tab before
|
|
568
|
+
then. Relayed input is never logged or recorded; a takeover's start and
|
|
569
|
+
end are recorded as `marker` events with `source: "human"`. The sandbox
|
|
570
|
+
listing reports `browser_modes`.
|
|
571
|
+
|
|
572
|
+
- **Report quantities to the usage recorder** (`actionagent`).
|
|
573
|
+
`ActionAgent.record_usage(owner, kind, quantity)` passes the quantity to a
|
|
574
|
+
`usage_recorder` that takes a third argument; one that takes two is still
|
|
575
|
+
called as `(owner, kind)`.
|
|
576
|
+
|
|
577
|
+
- **Review explorations' candidate scenarios** (`actionagent`). An
|
|
578
|
+
`ActionAgent::Exploration` holds the scenarios a walk through a project's
|
|
579
|
+
app proposed, for a person to accept or reject on the project's
|
|
580
|
+
Explorations tab before any reaches the evaluation. Each candidate's
|
|
581
|
+
expected tools are checked against the tools the project's agent can call
|
|
582
|
+
(`answerable`, `needs_tool` with the missing tools, or `unverified` when the
|
|
583
|
+
sandbox did not answer), and its text is scrubbed of the project's secrets
|
|
584
|
+
and the owner's credentials before it is stored. A prompt or rubric over
|
|
585
|
+
4,000 characters, an expectation list over 50 entries, or a call or
|
|
586
|
+
exploration over 2 MiB of JSON is refused. Accepting merges into the
|
|
587
|
+
project's evaluation under `x<exploration id>_<candidate id>` keys with the
|
|
588
|
+
rubric as `notes`, never touches another scenario, asks the permission
|
|
589
|
+
checker about `:replace_scenarios`, and writes each scenario so that a Save
|
|
590
|
+
in the suite editor leaves it unchanged. The `/api/explorations` endpoints
|
|
591
|
+
list, show, submit, edit, accept and stop explorations, and the MCP
|
|
592
|
+
facade's `explorations_submit` tool lets an agent outside the dashboard
|
|
593
|
+
submit candidates and read back their verdicts. The review shows a budget
|
|
594
|
+
meter, Stop and review, and the executions an accept-and-run uses;
|
|
595
|
+
`config.exploration_preselect_limit` caps how many candidates it
|
|
596
|
+
pre-selects. Ships as migration template `021`.
|
|
597
|
+
|
|
598
|
+
- **Interactive project setup and the install pull request** (`actionagent`).
|
|
599
|
+
A failed project boot starts the project's setup assistant, an agent the
|
|
600
|
+
engine defines whose runs have exactly `read_step_log`, `set_env`,
|
|
601
|
+
`request_secret` and `retry_boot` and nothing else: no shell, no file reads,
|
|
602
|
+
no code session. At most three automatic runs follow each other before a
|
|
603
|
+
boot succeeds; `PATCH /api/projects/:id/setup` with `auto: false` switches
|
|
604
|
+
them off, and `POST /api/projects/:id/setup` (`:manage_project_secrets`)
|
|
605
|
+
starts one. A new run waits until the last one is done or no longer waiting
|
|
606
|
+
for an answer. Each run is an execution for `quota_checker`. `set_env`
|
|
607
|
+
stores a value that is not secret (source `setup_assistant`, passed as boot
|
|
608
|
+
`env`, never masked, shown in the Environment tab), refuses the names
|
|
609
|
+
project secrets refuse and leaves values a person set alone. The secrets API
|
|
610
|
+
refuses that source. A `request_secret` question names who asks, the
|
|
611
|
+
repository and the variable; answering it also needs
|
|
612
|
+
`:manage_project_secrets`, and the answer becomes a project secret the
|
|
613
|
+
resumed boot gets, never the transcript, telemetry or a job argument. The
|
|
614
|
+
Project page answers the requests waiting on the project's agents in place
|
|
615
|
+
(`GET /api/projects/:id/input_requests`) and reads "Waiting for you: N".
|
|
616
|
+
The sandbox manifest lists the app's models and columns (secret-looking
|
|
617
|
+
columns left out), and a project evaluated with the App assistant chooses
|
|
618
|
+
which it may filter on and read (`GET /api/projects/:id/app_models`,
|
|
619
|
+
`PUT /api/projects/:id/schema_tools`); every later boot writes
|
|
620
|
+
`app/agent_tools/<model>_tools.rb` with them, also once the repository
|
|
621
|
+
bundles the engine and boots from its own `sandbox.yml`, and removes the
|
|
622
|
+
files it wrote for models taken off the list. A tools file the dashboard
|
|
623
|
+
did not write is left alone. `POST /api/projects/:id/install_pull_request`
|
|
624
|
+
publishes the install through `DraftPullRequestPublisher` with the
|
|
625
|
+
project's allowlist: the Gemfile and lock, the initializer and routes, the
|
|
626
|
+
framework's config and application agent when the bootstrap wrote them, the
|
|
627
|
+
engine's migrations by name, the schema, the chosen schema tools, and a
|
|
628
|
+
generated `.activeagents/sandbox.yml` (setup and secret names) and
|
|
629
|
+
`.activeagents/evals/<project>.yml` (the evaluation's enabled scenarios, as
|
|
630
|
+
a suite `ActiveAgent::Evals::Suite.load` reads). A publish must take every
|
|
631
|
+
one of those install files the sandbox changed, and a file holding a
|
|
632
|
+
project secret is refused. While the pull request is open every boot checks
|
|
633
|
+
out its branch without installing again, and once GitHub reports it merged
|
|
634
|
+
the project is installed. A boot spec step marked `always` runs whether the
|
|
635
|
+
spec applies or not: after a checkout's own `sandbox.yml` setup when it does
|
|
636
|
+
not.
|
|
637
|
+
|
|
638
|
+
- **Declare schema tool columns from the generator** (`activeagent`).
|
|
639
|
+
`bin/rails generate active_agent:schema_tools Model --filterable a --returns
|
|
640
|
+
a b` writes those columns as the class's allowlist instead of commented
|
|
641
|
+
suggestions, leaving out a column that looks like a secret or that the
|
|
642
|
+
model lacks. `ActiveAgent::SchemaTools::SECRET_COLUMNS` names the pattern.
|
|
643
|
+
`--managed` heads the file with `ActiveAgent::SchemaTools::MANAGED_MARKER`,
|
|
644
|
+
which a dashboard project looks for before it rewrites or removes the file.
|
|
645
|
+
|
|
646
|
+
- **Explore a project's app with the engine's explorer agent**
|
|
647
|
+
(`actionagent`). `POST <mount>/api/projects/:id/explorations` (and
|
|
648
|
+
**Explore the app** on the Project page) asks the quota checker about the
|
|
649
|
+
new `:exploration` kind (402 and nothing started on a denial), gives the
|
|
650
|
+
project's ready sandbox a browser, records `:exploration` once and walks
|
|
651
|
+
the app within a budget of minutes, browser steps and an optional cost
|
|
652
|
+
(15 minutes and 150 steps by default). The explorer is an agent run of a
|
|
653
|
+
project-owned agent, traced and recorded: it is offered an allowlist of
|
|
654
|
+
the browser's tools pinned to the app, `sign_in(secret_ref:)`,
|
|
655
|
+
`read_last_email(to:)`, `propose_candidate` (verdicts against the target
|
|
656
|
+
agent's real tools, provenance with the pages, steps and recording range
|
|
657
|
+
filled in server-side) and `finish`. Browser results are scrubbed of the
|
|
658
|
+
project's secrets and cut to 24,000 characters each. Running out of
|
|
659
|
+
budget or of conversation room, or Stop and review, moves the exploration
|
|
660
|
+
to review with its candidates; a crash fails it and keeps them.
|
|
661
|
+
|
|
662
|
+
- **Sign a project's browser in without a model seeing the credentials**
|
|
663
|
+
(`actionagent`, `@activeagents/browser-sidecar`). A project secret now has
|
|
664
|
+
a `kind`: `env` (as before), `sign_in` (login URL, login, password and
|
|
665
|
+
optional field selectors) or `storage_state` (a saved browser sign-in).
|
|
666
|
+
Only `env` secrets reach the sandbox's environment, the two sign-in names
|
|
667
|
+
are refused for `env` secrets, and the password and a saved sign-in's
|
|
668
|
+
session values are scrubbed on their own. `sign_in` types the credentials
|
|
669
|
+
from the Rails process, never logging them, and answers only whether the
|
|
670
|
+
browser left the login form, or `unsupported` when the login page has no
|
|
671
|
+
password field.
|
|
672
|
+
`<mount>/api/projects/:id/sign_in` sets, checks, saves from the running
|
|
673
|
+
browser and removes them, asked as `:manage_project_secrets`. The sidecar
|
|
674
|
+
takes a `storage_state` to start with and answers `GET /storage-state`.
|
|
675
|
+
Adds migration `add_kind_to_active_agent_project_secrets`.
|
|
676
|
+
|
|
677
|
+
- **Read the mail a sandbox app sends** (`actionagent`). Sandbox backends set
|
|
678
|
+
`ACTION_AGENT_SANDBOX_MAIL_DIR`, which switches the sandbox app's Action
|
|
679
|
+
Mailer to file delivery, and `ActionAgent::SandboxMail.last_message`
|
|
680
|
+
reads the newest message for an address through the backend's
|
|
681
|
+
`read_file` verb.
|
|
682
|
+
|
|
683
|
+
- **Mask JSON-escaped secrets** (`actionagent`).
|
|
684
|
+
`ActionAgent::SecretScrubber.with_encodings` also lists each value as it
|
|
685
|
+
appears escaped inside a JSON string, so a secret with a quote or a
|
|
686
|
+
backslash is masked in JSON output too.
|
|
687
|
+
|
|
688
|
+
- **Give a project's evaluation runs the sandbox's browser**
|
|
689
|
+
(`actionagent`). A run started from the project page reaches the
|
|
690
|
+
sandbox's browser in every replay, starting it headless with the
|
|
691
|
+
project's saved sign-in when none runs and opening the start URL before
|
|
692
|
+
each replay, and records the browser in its selection. A browser that
|
|
693
|
+
cannot start fails the run before any replay, and one the run started is
|
|
694
|
+
stopped when it ends.
|
|
695
|
+
|
|
696
|
+
- **List and replay sessions in the dashboard** (`actionagent`). The Sessions
|
|
697
|
+
page (`<mount>/sessions`, from `GET <mount>/api/sessions`) lists every
|
|
698
|
+
conversation, evaluation scenario replay and lone agent browser recording,
|
|
699
|
+
newest first, with server-side filters for agent, `user=me`, source,
|
|
700
|
+
outcome and a last-activity date range, a page at a time by cursor.
|
|
701
|
+
`<mount>/replay/:id` replays a recording and `<mount>/replay/:kind/:id` a
|
|
702
|
+
conversation, run or scenario result: messages, model calls and tool calls
|
|
703
|
+
on one axis, with idle stretches shortened, a scrubber, stepping and speed.
|
|
704
|
+
A recorded browser replays beneath them in step, in a frame served at
|
|
705
|
+
`<mount>/session_player` that runs the new `action_agent_replay.js` bundle
|
|
706
|
+
(rrweb) under a policy that allows no other script and loads nothing from
|
|
707
|
+
the network. The engine adds the bundle to the Sprockets precompile list.
|
|
708
|
+
The Run Agent workbench, Interactions, an agent's interactions page and
|
|
709
|
+
evaluation scenario results link to their replays.
|
|
710
|
+
|
|
711
|
+
- **Record the Run Agent workbench for its conversation's replay**
|
|
712
|
+
(`actionagent`). While the workbench has a conversation open, the dashboard
|
|
713
|
+
records the page with rrweb, each visit into a recording of its own
|
|
714
|
+
(`source: "dashboard"`), which replays in the conversation's browser lane,
|
|
715
|
+
and says so beside the conversation. It stops when you leave the workbench
|
|
716
|
+
or switch conversation, and no other view is recorded. Field values are
|
|
717
|
+
masked. Elements marked `data-aa-secret` (the dashboard's credential fields
|
|
718
|
+
and key displays), hidden inputs and the CSRF token are left out, and the
|
|
719
|
+
server masks the owner's stored credentials in every batch before storing
|
|
720
|
+
it. The recorder is a new bundle, `action_agent_recorder.js`, imported only
|
|
721
|
+
while it records and added to the Sprockets precompile list. `POST
|
|
722
|
+
<mount>/api/session_recordings` with `agent_context_id` starts the caller's
|
|
723
|
+
recording of a visit to a conversation.
|
|
724
|
+
`ActionAgent.capture_dashboard_sessions = false` turns it off: no recorder
|
|
725
|
+
loads, and that endpoint and dashboard-session batches answer 403.
|
|
726
|
+
|
|
727
|
+
- **Require approval before a tool runs** (`activeagent`). The
|
|
728
|
+
`requires_approval:` prompt option lists tools whose calls pause with a
|
|
729
|
+
`:confirm` request carrying the tool name and arguments before the tool runs;
|
|
730
|
+
`true` runs it, `false` declines. It covers agent tools, delegations and
|
|
731
|
+
client-side MCP tools, and is never sent to the provider. An MCP declaration's
|
|
732
|
+
own `require_approval` (`"always"`, or a map of `always`/`never` tool lists)
|
|
733
|
+
gates its tools the same way. A remote MCP server that may offer a listed
|
|
734
|
+
tool runs client-side so that its calls are gated too: one whose
|
|
735
|
+
`allowed_tools:` include the tool, or one without `allowed_tools:` when no
|
|
736
|
+
tool in `tools:` has the name; `mcp_strategy: :server` with such a server
|
|
737
|
+
raises `ArgumentError`, as does a `requires_approval:` value that is not tool
|
|
738
|
+
names. A paused call to an MCP tool its server no longer offers returns an
|
|
739
|
+
`{ error: }` result on resume.
|
|
740
|
+
|
|
741
|
+
- **Resume a paused generation in a job** (`activeagent`).
|
|
742
|
+
`Generation#resume_later(checkpoint:, answers:, **job_options)` validates the
|
|
743
|
+
answers, refuses any answer to a `:secret` request, and enqueues
|
|
744
|
+
`GenerationJob` with a new `resume:` argument; params and actor travel as they
|
|
745
|
+
do for `generate_later`. `InputRequest#arguments` holds the arguments of the
|
|
746
|
+
paused call.
|
|
747
|
+
|
|
748
|
+
### Changed
|
|
749
|
+
|
|
750
|
+
- **Confirm destructive actions in the dashboard's own dialog**
|
|
751
|
+
(`actionagent`). Every `window.confirm` is gone: a `useConfirm()` dialog
|
|
752
|
+
(an `alertdialog` with a focus trap, Escape to cancel and focus starting on
|
|
753
|
+
Cancel) asks instead, and deleting a project, an agent, an evaluation or a
|
|
754
|
+
scenario collection asks for its name to be typed first.
|
|
755
|
+
- **New project shows the repository first** (`actionagent`). The repository
|
|
756
|
+
picker heads the page and the capabilities checklist folds to one line,
|
|
757
|
+
opening itself when something blocks; each fix the checklist names links to
|
|
758
|
+
where it is made.
|
|
759
|
+
- **The install pull request never publishes a local gem path**
|
|
760
|
+
(`actionagent`). A Gemfile or lockfile change that newly points a gem at a
|
|
761
|
+
path outside the repository is refused.
|
|
762
|
+
- **Transcripts leave out hook, thinking and rate-limit events**
|
|
763
|
+
(`actionagent`). A code session's transcript shows what Claude Code did.
|
|
764
|
+
- **Releases check the browser sidecar against npm** (release workflow).
|
|
765
|
+
`@activeagents/browser-sidecar` is staged through npm trusted publishing
|
|
766
|
+
(`npm stage publish`, approved by a maintainer with 2FA), and a release
|
|
767
|
+
fails when `browser-sidecar/` differs from the version already on npm.
|
|
768
|
+
- **Keep the shared Playwright MCP browser out of multi-tenant installs**
|
|
769
|
+
(`actionagent`). With `multi_tenant = true` the toolbox's `playwright_mcp`
|
|
770
|
+
tools are neither offered nor called, and `PlaywrightMCPClient.instance`
|
|
771
|
+
raises: a run gets a browser only from its sandbox.
|
|
772
|
+
|
|
773
|
+
- **The dashboard page no longer carries the telemetry key**
|
|
774
|
+
(`actionagent`). `account.telemetry_api_key` is gone from the dashboard's
|
|
775
|
+
`data-props`, so nothing that reads the page's markup sees it. The
|
|
776
|
+
Organization page reads it from `GET <mount>/api/telemetry_key` when you
|
|
777
|
+
show or copy it. A host layout or script that read it from the props must
|
|
778
|
+
call that endpoint.
|
|
779
|
+
|
|
780
|
+
- **Mask dashboard API keys and the telemetry key in MCP tool output**
|
|
781
|
+
(`actionagent`). The MCP facade masks the same owner credentials as session
|
|
782
|
+
recording ingest, which adds the owner's dashboard API keys and telemetry
|
|
783
|
+
key to the provider keys, GitHub tokens and sandbox runtime tokens it
|
|
784
|
+
already masked.
|
|
785
|
+
|
|
786
|
+
- **Session Replay is now Sessions** (`actionagent`). The sidebar entry opens
|
|
787
|
+
the Sessions list, and `<mount>/replay` without an id opens it too. The
|
|
788
|
+
replay view plays a session's timeline and its rrweb recording instead of
|
|
789
|
+
loading the recording's page URL in an iframe and animating a cursor over
|
|
790
|
+
it. A recording that carries handoff state still offers Take over session.
|
|
791
|
+
|
|
792
|
+
- **Record an agent's browser tool calls as recording events**
|
|
793
|
+
(`actionagent`). `ActionAgent::MCPRecordingMiddleware#intercept` now stores
|
|
794
|
+
each call to a tool in `MCPRecordingMiddleware::PLAYWRIGHT_TOOLS` as one
|
|
795
|
+
`action` event, with typed values masked and the owner's credentials
|
|
796
|
+
scrubbed, instead of writing `RecordingAction`s through
|
|
797
|
+
`SessionRecordingService`. It no longer extracts screenshots from tool
|
|
798
|
+
results. `AgentExecutionService` wraps every tool call with it, so a run's
|
|
799
|
+
first browser call starts the run's recording (`source: "agent"`). The
|
|
800
|
+
constructor no longer starts a recording: `#recording` and
|
|
801
|
+
`#recording_service` find or start one on first use, and are `nil` when none
|
|
802
|
+
can be started. The Session Replay page reads `/actions`, so it lists these
|
|
803
|
+
recordings with no actions until the replay view moves to the timeline; read
|
|
804
|
+
them from `GET .../timeline` or `GET .../events?kind=action`. The
|
|
805
|
+
`record_navigate`, `record_click`, `record_type` and `capture_for_handoff`
|
|
806
|
+
helpers still write `RecordingAction`s. `PLAYWRIGHT_TOOLS` matches the tools
|
|
807
|
+
of `@playwright/mcp` 0.0.83, and `browser_press_sequentially`'s text is
|
|
808
|
+
masked like `browser_type`'s. `browser_scroll`, which Playwright MCP does
|
|
809
|
+
not have, is gone.
|
|
810
|
+
|
|
811
|
+
- **Announce run and sandbox changes without their content** (`actionagent`).
|
|
812
|
+
The Action Cable messages on `agent_run_<id>`, `agent_runs_<agent_id>` and
|
|
813
|
+
`sandbox_<session_id>` are now `{ type, id, status }`; a subscriber reads
|
|
814
|
+
the run or sandbox back over the JSON API. Nothing is broadcast when the host
|
|
815
|
+
has not loaded Action Cable. A host channel or client that read `run`,
|
|
816
|
+
`sandbox`, `task`, `error` or `provider` from these messages must refetch.
|
|
817
|
+
|
|
818
|
+
- **`GenerationJob` no longer logs its arguments** (`activeagent`). They can
|
|
819
|
+
hold a paused generation's conversation and the user's answers.
|
|
820
|
+
|
|
821
|
+
- **MCP declarations that set `require_approval` run client-side**
|
|
822
|
+
(`activeagent`). On Anthropic and OpenAI Responses, a `url:` declaration whose
|
|
823
|
+
`require_approval` is anything but `"never"` is served by ActiveAgent's MCP
|
|
824
|
+
bridge rather than handed to the provider, so its calls can pause for
|
|
825
|
+
approval; `mcp_strategy: :server` with one raises `ArgumentError`. Such
|
|
826
|
+
servers now connect from your app and need `gem "mcp"`; set
|
|
827
|
+
`require_approval: "never"` to keep one with the provider.
|
|
828
|
+
|
|
829
|
+
- **Add the `awaiting_input` run status** (`actionagent`). `AgentRun` status
|
|
830
|
+
`5` is a run waiting on its input requests: neither `in_progress?` nor
|
|
831
|
+
`finished?`, never picked up again by `AgentExecutionJob`, and cancellable
|
|
832
|
+
with `cancel!`, which also cancels its pending requests. A client that treats
|
|
833
|
+
every status other than `pending` and `running` as finished must handle it.
|
|
834
|
+
|
|
835
|
+
- **Filter `answer` and `value` from request logs** (`actionagent`). The engine
|
|
836
|
+
adds them to the host's `filter_parameters`, beside `credential`, `api_key`
|
|
837
|
+
and `access_token`, matching only parameters named exactly `answer` or
|
|
838
|
+
`value`, at any depth, including under Rails 8.2, which matches a host's
|
|
839
|
+
precompiled filters against the dotted parameter path. Rails also copies
|
|
840
|
+
`filter_parameters` into Active Record's `filter_attributes`, so a host
|
|
841
|
+
model attribute named `answer` or `value` shows as `[FILTERED]` in
|
|
842
|
+
`inspect` and in logged SQL binds.
|
|
843
|
+
|
|
844
|
+
- **CI covers ruby_llm 1.16 and 2.x with explicit bundles** (`activeagent`).
|
|
845
|
+
`gemfiles/ruby_llm_2.gemfile` runs the full suite on ruby_llm 2.x beside
|
|
846
|
+
`gemfiles/ruby_llm_1.gemfile`, so the 2.x adapter keeps its coverage even
|
|
847
|
+
when the default bundle resolves to 1.x (#502).
|
|
848
|
+
|
|
849
|
+
### Fixed
|
|
850
|
+
|
|
851
|
+
- **An owner of another class reads no other tenant's rows** (`actionagent`).
|
|
852
|
+
`Ownable.for_owner` scoped by the owner's id alone, so an account-owned
|
|
853
|
+
model handed a user — the stored provider-key fallback of an agent run,
|
|
854
|
+
`ProviderKey.for_owner(agent.owner)`, when agents are owned per user — read
|
|
855
|
+
whichever account shared that user's id, and the run generated with that
|
|
856
|
+
account's key. The scope now matches the owner by class: a user handed to an
|
|
857
|
+
account-owned model maps to its tenant through `ActionAgent.tenant_for`, and
|
|
858
|
+
any other mismatch scopes to nothing. `TelemetryTrace.for_account` matches
|
|
859
|
+
the same way, and `owner=` raises `ArgumentError` for an owner that resolves
|
|
860
|
+
to nothing rather than writing its id into another class's column.
|
|
861
|
+
**Upgrading:** a multi-tenant install that configures both `account_class`
|
|
862
|
+
and `user_class` must own every model by one class or map between them.
|
|
863
|
+
Re-declare `owned_by :account, :user` on `Agent`, `SandboxSession`,
|
|
864
|
+
`SessionRecording` and `CodeSession` from `to_prepare` and set
|
|
865
|
+
`tenant_resolver`, as the install generator's template now shows; rows those
|
|
866
|
+
models stored under `user_id` with the account's id need `account_id`
|
|
867
|
+
backfilled, or the dashboard lists nothing for them.
|
|
868
|
+
|
|
869
|
+
- **Fix Anthropic structured output mapping** (`activeagent`). Preserve caller
|
|
870
|
+
`output_config` and ignore unsupported response formats.
|
|
871
|
+
|
|
872
|
+
- **Fix Anthropic JSON emulation on current models and with thinking**
|
|
873
|
+
(`activeagent`). `json_object` requests reach current Claude models
|
|
874
|
+
without a prefill, which they refuse with or without thinking, and so do
|
|
875
|
+
requests with thinking on. The JSON is read from the answer, inside a code
|
|
876
|
+
fence or not, and a retry drops an unparseable answer rather than ending on
|
|
877
|
+
it. Claude 4.5 and earlier models keep the lead-in while thinking is off.
|
|
878
|
+
|
|
879
|
+
- **Prevent Anthropic response fields from leaking into replayed requests**
|
|
880
|
+
(`activeagent`). Keep only request-supported message fields.
|
|
881
|
+
|
|
882
|
+
- **Name conflicting gems in provider load errors** (`activeagent`). Name
|
|
883
|
+
`ruby-openai` and show the Gemfile replacement only when that gem is
|
|
884
|
+
activated or defined `OpenAI`; when something else defines it, say where.
|
|
885
|
+
|
|
886
|
+
- **Start `command:` MCP servers that declare `env:`** (`activeagent`). The
|
|
887
|
+
declaration's symbolized environment keys reached the process spawn, which
|
|
888
|
+
requires Strings, so every stdio server that needed credentials in its
|
|
889
|
+
environment failed with a TypeError.
|
|
890
|
+
|
|
891
|
+
- **Return an MCP server's JSON-RPC error to the model, marked as a failure**
|
|
892
|
+
(`activeagent`). The client raises on an error envelope; the bridge hands
|
|
893
|
+
the message back as the tool's result instead of failing the generation.
|
|
894
|
+
That error, like a result the tool marks `isError`, reaches the model as
|
|
895
|
+
`{"error": message}`, with `is_error: true` on Anthropic, so it no longer
|
|
896
|
+
reads as a successful result.
|
|
897
|
+
|
|
898
|
+
- **Serve String-keyed `mcps:` declarations natively where the provider can**
|
|
899
|
+
(`activeagent`). Keys are symbolized before partitioning, so a declaration
|
|
900
|
+
loaded from YAML is no longer bridged on Anthropic or OpenAI Responses.
|
|
901
|
+
|
|
902
|
+
- **Keep credentials out of the `mcps:` declaration error** (`activeagent`).
|
|
903
|
+
The message names the declaration's keys, not its values.
|
|
904
|
+
|
|
905
|
+
- **Refuse an unknown `mcp_strategy:`** (`activeagent`) instead of treating it
|
|
906
|
+
as `:auto`.
|
|
907
|
+
|
|
908
|
+
- **Create the MCP tool cache's lock with the class** (`activeagent`), so the
|
|
909
|
+
first concurrent callers share it.
|
|
910
|
+
|
|
911
|
+
- **Apply `read_timeout:` to `url:` MCP servers** (`activeagent`). The option
|
|
912
|
+
was accepted on a remote server but neither validated nor applied, so its
|
|
913
|
+
requests waited as long as Net::HTTP allowed. It is validated for both
|
|
914
|
+
transports and sets the read timeout of the server's HTTP connection, 30
|
|
915
|
+
seconds by default, so a streamed response stays open while it keeps sending
|
|
916
|
+
events.
|
|
917
|
+
|
|
918
|
+
- **Bound a `command:` MCP server's whole answer by `read_timeout:`**
|
|
919
|
+
(`activeagent`). The wait restarted with every frame read, so a server that
|
|
920
|
+
kept sending notifications or pings without answering held the generation
|
|
921
|
+
open indefinitely. A server that misses the deadline is stopped, and
|
|
922
|
+
`MCPBridge::TimeoutError` names the server and the request. The handshake
|
|
923
|
+
keeps the `mcp` gem's 5-second bound on its `server/discover` probe instead
|
|
924
|
+
of waiting the full read timeout.
|
|
925
|
+
|
|
926
|
+
- **Use an explicit OpenRouter key before the environment** (`activeagent`).
|
|
927
|
+
An `api_key` or `access_token` passed to the OpenRouter provider wins over
|
|
928
|
+
`OPENROUTER_API_KEY` and the other OpenRouter variables, as it does for the
|
|
929
|
+
other providers. An explicit `api_key` is no longer dropped when no
|
|
930
|
+
OpenRouter variable is set. A blank explicit key is also used as given, so a
|
|
931
|
+
config value that renders as an empty string no longer falls back to the
|
|
932
|
+
environment; leave the key unset to use the environment.
|
|
933
|
+
|
|
934
|
+
- **Use an explicit OpenAI project before the environment** (`activeagent`).
|
|
935
|
+
A `project` or `project_id` passed to the OpenAI provider wins over
|
|
936
|
+
`OPENAI_PROJECT_ID`, and an explicit `project_id` is no longer dropped.
|
|
937
|
+
|
|
938
|
+
- **Stop Session Replay spinning when there is nothing to play**
|
|
939
|
+
(`actionagent`). With no recordings, a failed list request or a network
|
|
940
|
+
error, the page shows the empty state, or an error when the list failed.
|
|
941
|
+
|
|
942
|
+
- **List only the session recordings the caller can open** (`actionagent`).
|
|
943
|
+
The list uses the rule that opening a recording does, so a recording named
|
|
944
|
+
`lander_demo` is no longer listed for every caller.
|
|
945
|
+
|
|
946
|
+
- **Replay every action of a long recording** (`actionagent`). Session Replay
|
|
947
|
+
pages through `GET /api/session_recordings/:id/actions` with
|
|
948
|
+
`after_sequence`, up to 5,000 actions, instead of stopping at the first 100.
|
|
949
|
+
That endpoint's `has_more` is exact and its `limit` is clamped to 1..500.
|
|
950
|
+
|
|
951
|
+
- **Key show timeline entries `action_type`** (`actionagent`). They match
|
|
952
|
+
`/actions` entries; `type` remains as an alias.
|
|
953
|
+
|
|
954
|
+
- **Accept reasoning items in streamed OpenAI Responses** (`activeagent`). A
|
|
955
|
+
streamed response from a reasoning model raised `Unexpected Item Type`; its
|
|
956
|
+
reasoning items are now kept and sent back with the function calls they led to.
|
|
957
|
+
|
|
958
|
+
- **Send each RubyLLM tool result as its own message** (`activeagent`). A turn
|
|
959
|
+
with several tool calls sent their results merged into one message, under the
|
|
960
|
+
first call's id.
|
|
961
|
+
|
|
962
|
+
- **Fix a forced Anthropic `tool_choice` failing on a third turn**
|
|
963
|
+
(`activeagent`). Once the forced tool was used, the next turn cleared
|
|
964
|
+
`tool_choice`, and the turn after it raised `Anthropic::Errors::ConversionError`
|
|
965
|
+
reading the cleared value back.
|
|
966
|
+
|
|
967
|
+
- **A flat `json_schema` response_format under every provider**
|
|
968
|
+
(`activeagent`). The OpenAI providers and OpenRouter accept `name`,
|
|
969
|
+
`schema` and `strict` beside `type` as well as under `json_schema`, but
|
|
970
|
+
the RubyLLM provider raised "needs a schema for a json_schema
|
|
971
|
+
response_format" for the flat shape, the Anthropic provider sent
|
|
972
|
+
`output_config.format` with no schema, and an agent's `prompt` looked for
|
|
973
|
+
an `{action}.json` view instead, raising `ActionView::MissingTemplate`
|
|
974
|
+
for any provider. All three now read the flat shape, so a
|
|
975
|
+
`response_format` keeps working when `generate_with` changes provider
|
|
976
|
+
(#505).
|
|
977
|
+
|
|
978
|
+
- **Usage and stop reasons in RubyLLM streams** (`activeagent`). Streaming
|
|
979
|
+
preserves the final stop reason and token counts, including counts sent
|
|
980
|
+
across separate chunks. Cumulative counts are merged within a turn and
|
|
981
|
+
summed across tool turns. A later chunk that omits cache writes no longer
|
|
982
|
+
erases an earlier count with RubyLLM's synthetic zero. Cache-only and
|
|
983
|
+
reasoning-only usage is also preserved when input and output counts are
|
|
984
|
+
absent (#502).
|
|
985
|
+
|
|
986
|
+
- **Streamed tool calls through the RubyLLM provider** (`activeagent`).
|
|
987
|
+
An API streams a tool call's arguments as fragments of one JSON string,
|
|
988
|
+
and only the first fragment says which call it belongs to. The provider
|
|
989
|
+
took every later fragment for a call of its own with no name, so a
|
|
990
|
+
streamed tool call ran twice, once with no arguments and once under no
|
|
991
|
+
name, and two streamed calls ran on arguments joined into invalid JSON.
|
|
992
|
+
Fragments now go to the call they belong to, for OpenAI (Chat Completions
|
|
993
|
+
and Responses) and Anthropic, on ruby_llm 1.16 and 2.x. Arguments an API
|
|
994
|
+
sends whole, as Gemini does, are serialized as JSON instead of Ruby's
|
|
995
|
+
`Hash#to_s`; and Anthropic's empty opening input no longer leaves `{}` in
|
|
996
|
+
front of the arguments. Interleaved parallel OpenAI calls require
|
|
997
|
+
ruby_llm 2.x, since 1.16 discards their stream indices (#502).
|
|
998
|
+
|
|
999
|
+
## [1.8.2] - 2026-10-01
|
|
1000
|
+
|
|
1001
|
+
Releases `activeagent` and `actionagent` 1.8.2 from one tag. A patch on 1.8.1:
|
|
1002
|
+
the dashboard engine's owner scopes match an owner by class, so an
|
|
1003
|
+
account-owned record is no longer read through a user that shares the
|
|
1004
|
+
account's id. No migrations. The `activeagent` gem changes only its version.
|
|
1005
|
+
|
|
1006
|
+
### Fixed
|
|
1007
|
+
|
|
1008
|
+
- **An owner of another class reads no other tenant's rows** (`actionagent`).
|
|
1009
|
+
`Ownable.for_owner` scoped by the owner's id alone, so an account-owned
|
|
1010
|
+
model handed a user — the stored provider-key fallback of an agent run,
|
|
1011
|
+
`ProviderKey.for_owner(agent.owner)`, when agents are owned per user — read
|
|
1012
|
+
whichever account shared that user's id, and the run generated with that
|
|
1013
|
+
account's key. The scope now matches the owner by class: a user handed to an
|
|
1014
|
+
account-owned model maps to its tenant through `ActionAgent.tenant_for`, and
|
|
1015
|
+
any other mismatch scopes to nothing. `TelemetryTrace.for_account` matches
|
|
1016
|
+
the same way, and `owner=` raises `ArgumentError` for an owner that resolves
|
|
1017
|
+
to nothing rather than writing its id into another class's column.
|
|
1018
|
+
**Upgrading:** a multi-tenant install that configures both `account_class`
|
|
1019
|
+
and `user_class` must own every model by one class or map between them.
|
|
1020
|
+
Re-declare `owned_by :account, :user` on `Agent`, `SandboxSession`,
|
|
1021
|
+
`SessionRecording` and `CodeSession` from `to_prepare` and set
|
|
1022
|
+
`tenant_resolver`, as the install generator's template now shows; rows those
|
|
1023
|
+
models stored under `user_id` with the account's id need `account_id`
|
|
1024
|
+
backfilled, or the dashboard lists nothing for them.
|
|
1025
|
+
|
|
10
1026
|
## [1.8.1] - 2026-10-01
|
|
11
1027
|
|
|
12
1028
|
### Added
|
|
@@ -443,6 +1459,48 @@ output in the shape ruby_llm reads. No migrations.
|
|
|
443
1459
|
`json_object`, which ruby_llm has no mode for, and a `json_schema`
|
|
444
1460
|
without a schema now raise `ArgumentError` instead of sending a request
|
|
445
1461
|
the API rejects (#501).
|
|
1462
|
+
- **`ActiveAgent::Evals::RubyLLM`** — the RubyLLM side of an evaluation,
|
|
1463
|
+
which hosts driving RubyLLM conversations had been writing for themselves.
|
|
1464
|
+
`require "active_agent/evals/ruby_llm"` (it requires `ruby_llm`;
|
|
1465
|
+
`require "active_agent/evals"` alone still does not) gives two helpers, for
|
|
1466
|
+
RubyLLM 1.16 and later and for 2.x:
|
|
1467
|
+
- `RubyLLM.judge(label:, model:, provider:, context:, correlation:)`, a
|
|
1468
|
+
`Judge` that answers from `context.chat(...)` and, with a `Correlation`,
|
|
1469
|
+
traces each call under the kind it serves. `temperature:` and a
|
|
1470
|
+
`configure:` hook set the chat up; any other keyword must be one
|
|
1471
|
+
`RubyLLM::Chat.new` takes, and anything else raises when the judge is
|
|
1472
|
+
built rather than failing every call. `on_usage:` reports each call's
|
|
1473
|
+
model, tokens and cost.
|
|
1474
|
+
- `RubyLLM.replay(messages)`, a `Replay` from a conversation's messages.
|
|
1475
|
+
`acts_as_chat` records of either RubyLLM generation are read through
|
|
1476
|
+
their own `to_llm`, so a 2.x table, or one between 2.x's upgrade and its
|
|
1477
|
+
cleanup, replays the same as `RubyLLM::Message` values. Tool calls keep
|
|
1478
|
+
the order the model made them in, each paired with the result that
|
|
1479
|
+
answers it; one is errored when that result reports an error at its top
|
|
1480
|
+
level — JSON whose `"error"` is a non-empty string, object or array, or
|
|
1481
|
+
`true`; an MCP result with `"isError": true`; or the inspected
|
|
1482
|
+
`{ error: "..." }` RubyLLM 1.x stores for a tool's error Hash with a
|
|
1483
|
+
String error — with the error (JSON-encoded, at most 1,200 bytes) as its
|
|
1484
|
+
detail. `input_tokens` counts the whole prompt, cache reads and writes
|
|
1485
|
+
included. Cost is RubyLLM's own price, left nil when any message is
|
|
1486
|
+
unpriced. The answer is the last reply after the last user message; a
|
|
1487
|
+
conversation that stopped at a tool call or on the user's own message
|
|
1488
|
+
has no answer and says so in its `error` (on RubyLLM 1.x, pass `answer:`
|
|
1489
|
+
for a tool that ends the turn with `halt`).
|
|
1490
|
+
- **`ActionAgent::ProviderKey.credentials_for(owner)` and
|
|
1491
|
+
`.apply_to(config, owner:)`** (`actionagent`) hand an owner's API keys to
|
|
1492
|
+
code outside the engine — `{ "openai" => "sk-..." }`, or written through
|
|
1493
|
+
`<provider>_api_key=` onto a `RubyLLM.context` config block or anything
|
|
1494
|
+
shaped like one, returning the providers written rather than the keys. A
|
|
1495
|
+
key is looked up in the order the engine's runs use: the host's
|
|
1496
|
+
`provider_credentials_resolver`, then the owner's saved row. A resolver
|
|
1497
|
+
answer that sends a provider to another endpoint (`uri_base`, `base_url`,
|
|
1498
|
+
`api_base`, `host`) leaves it out, so a gateway's key never reaches the
|
|
1499
|
+
public endpoint. On an install with an owner model, the owner must be an
|
|
1500
|
+
instance of the model it keeps keys by, since rows are scoped by its id
|
|
1501
|
+
alone; anything else raises `ArgumentError`. A saved credential that no
|
|
1502
|
+
longer decrypts is skipped with a warning naming the error class, never
|
|
1503
|
+
the value.
|
|
446
1504
|
|
|
447
1505
|
## [1.7.0] - 2026-09-24
|
|
448
1506
|
|