discourse/plugins/discourse-ai/spec/models/ai_tool_action_spec.rb
Rafael dos Santos Silva fe5e4a27e9
FEATURE: Add human-in-the-loop approval queue for AI agent tool actions (#38446)
## Summary

AI agents have 13 moderation tools (close_topic, delete_topic,
edit_tags, edit_post, etc.) that currently execute immediately without
human oversight. This adds an optional approval queue that routes these
tool actions through Discourse's review queue for moderator approval
before execution.

- **New `require_approval` toggle** on AI agents — when enabled,
moderation tool calls are intercepted and sent to the review queue
instead of executing immediately
- **Review queue integration** — moderators see the agent name, tool
name, parameters, and a rendered snippet of the triggering post, then
approve or reject
- **Loop prevention** — approved tool execution is wrapped in
`DiscourseAutomation.set_active_automation` to prevent automation
re-trigger loops (e.g., `edit_tags` → `topic_tags_changed` → automation
fires again)

### New files
- `AiToolAction` model — stores tool name, parameters (JSONB), agent/bot
user refs, and triggering post ID
- `ReviewableAiToolAction` — Reviewable subclass with approve (executes
tool) and reject (discards) actions
- `ReviewableAiToolActionSerializer` — serializes target tool data and
payload context
- Review queue frontend component — displays tool action details and
post snippet
- Two migrations: `ai_tool_actions` table and `require_approval` column
on `ai_agents`

### Modified files
- `Tool` base class gains `requires_approval?` (default `false`),
overridden to `true` on all 13 moderation tools
- `Bot#invoke_tool` — intercepts tools when both tool and agent opt in
to approval
- Agent admin editor — new "Require approval" checkbox
- Agent REST model — `require_approval` added to attribute whitelists
for save payloads
- Serializer, controller, plugin.rb — wired up for the new field and
reviewable type
2026-03-13 12:46:59 -03:00

37 lines
1,008 B
Ruby

# frozen_string_literal: true
RSpec.describe AiToolAction do
fab!(:ai_agent)
before { enable_current_plugin }
it "validates presence of tool_name" do
action = AiToolAction.new(bot_user_id: -1, ai_agent: ai_agent)
expect(action).not_to be_valid
expect(action.errors[:tool_name]).to be_present
end
it "validates presence of bot_user_id" do
action = AiToolAction.new(tool_name: "close_topic", ai_agent: ai_agent)
expect(action).not_to be_valid
expect(action.errors[:bot_user_id]).to be_present
end
it "creates a valid record" do
action =
AiToolAction.create!(
tool_name: "close_topic",
tool_parameters: {
topic_id: 1,
closed: true,
reason: "test",
},
ai_agent: ai_agent,
bot_user_id: -1,
)
expect(action).to be_persisted
expect(action.tool_name).to eq("close_topic")
expect(action.tool_parameters).to eq("topic_id" => 1, "closed" => true, "reason" => "test")
end
end