whilesmart / eloquent-agent-actions
DB-tracked agent action ledger with a handler registry and scheduler for Laravel applications.
Package info
github.com/whilesmartphp/eloquent-agent-actions
pkg:composer/whilesmart/eloquent-agent-actions
Requires
- php: ^8.2
- laravel/framework: ^11.0|^12.0
- rlanvin/php-rrule: ^2.6
- whilesmart/eloquent-owner-access: ^1.0
Requires (Dev)
- fakerphp/faker: ^1.24
- laravel/pint: ^1.22
- orchestra/testbench: ^9.0|^10.0
This package is auto-updated.
Last update: 2026-07-15 18:00:30 UTC
README
A DB-tracked ledger for actions an AI agent proposes or performs. Each action is
persisted with a lifecycle (proposed, confirmed, executed, rejected, expired,
failed), scoped to an owner (a workspace, organization, user) through
whilesmart/eloquent-owner-access,
and executed by a handler resolved on its action_type. Actions can run
immediately on confirm, or be scheduled for later with optional RRULE
recurrence.
Install
composer require whilesmart/eloquent-agent-actions
php artisan migrate
Routes register automatically under the api prefix with auth:sanctum. Set
AGENT_ACTIONS_REGISTER_ROUTES=false to mount them yourself.
Owning model
Add the trait to whatever owns actions:
use Whilesmart\AgentActions\Traits\HasAgentActions; class Workspace extends Model { use HasAgentActions; } $workspace->agentActions()->create([ 'action_type' => 'send_mail', 'payload' => ['to' => 'a@b.com', 'subject' => 'Hi'], 'metadata' => ['agent_run_id' => 42], 'summary' => 'Email the customer', 'risk' => 'medium', ]);
payload is the execution input; metadata is a separate free-form column for
an originating agent-run id, tags, or cost. An idempotency_key is generated
per action when omitted.
An action can point back to whatever triggered it (a chat message, an inbound
email, a webhook event) through the polymorphic source, alongside the owner
it acts for and the executed_resource it produces:
$action->source()->associate($chatMessage)->save(); $action->source; // the chat message, resolved back through morphTo
source carries no database foreign key (like owner), so cleaning up an
action when its source is deleted is the host's choice, e.g. a model observer.
Handlers
The package is host-agnostic: it does not know how to send mail or call a
webhook. The host supplies that by registering handlers keyed on action_type.
use Whilesmart\AgentActions\Contracts\ActionHandler; use Whilesmart\AgentActions\Models\AgentAction; class SendMailHandler implements ActionHandler { public function type(): string { return 'send_mail'; } public function execute(AgentAction $action): mixed { // ... send the mail, optionally return the created record } }
Register handlers in config/agent-actions.php:
'handlers' => [ App\AgentActions\SendMailHandler::class, ],
The package ships one built-in handler, NullActionHandler (type noop), which
marks an action executed without side effects. Return an Eloquent model from
execute() to record it as the action's executed_resource; throw to mark the
action failed with the message. Each run fires AgentActionExecuted or
AgentActionFailed for the host to bridge.
Endpoints
| Method | Path | Purpose |
|---|---|---|
| GET | /api/agent-actions |
List (filter by status, action_type, owner) |
| POST | /api/agent-actions |
Create a proposed action |
| GET | /api/agent-actions/{action} |
Show |
| POST | /api/agent-actions/{action}/confirm |
Confirm, then execute now or arm for the scheduler |
| POST | /api/agent-actions/{action}/reject |
Reject |
| GET | /api/agent-actions/batches/{batch} |
Show a batch's members |
| POST | /api/agent-actions/batches/{batch}/confirm |
Confirm every pending member |
| POST | /api/agent-actions/batches/{batch}/reject |
Reject every pending member |
Action batches
When an agent proposes several actions from one instruction (say the line items of a receipt, or a set of transfers), batch them so the user confirms or rejects the set in one call instead of one round-trip per action. Batch with the trait:
$actions = $workspace->proposeActionBatch([ ['action_type' => 'record_transaction', 'payload' => ['amount' => 12.5]], ['action_type' => 'record_transaction', 'payload' => ['amount' => 40.0]], ], ['risk' => 'low']); // second arg: attributes shared by every member $batch = $actions->first()->batch;
A batch is just a set of ordinary actions sharing a batch id. Confirming the
batch applies the normal per-action rule to each member: an immediate one runs
now, a future-scheduled one is armed. It is partial-safe and idempotent, one
member failing does not block the rest, already-resolved members are skipped on
a retry, and confirm returns a tally:
{ "executed": 2, "armed": 0, "failed": 0, "skipped": 0 }
Scheduling
Set scheduled_at / next_trigger_at in the future and the confirm endpoint
arms the action instead of running it. The host schedules the sweep command:
// routes/console.php Schedule::command('agent-actions:process-due')->everyMinute();
agent-actions:process-due runs every due action (AgentAction::query()->due())
through its handler. An action with a repeat_rule (RRULE, e.g. FREQ=DAILY)
is rescheduled to its next occurrence after each run.
Status & risk
ActionStatus: proposed, confirmed, executed, rejected, expired,
failed. ActionRisk: low, medium, high. Both are stored as plain
strings and cast to enums on the model.