whilesmart/eloquent-agent-actions

DB-tracked agent action ledger with a handler registry and scheduler for Laravel applications.

Maintainers

Package info

github.com/whilesmartphp/eloquent-agent-actions

pkg:composer/whilesmart/eloquent-agent-actions

Transparency log

Statistics

Installs: 53

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

1.0.0 2026-07-15 17:58 UTC

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.