Skip to main content

Agent Automations

Automations run a configured agent without a human in the loop — on a cron schedule, or in response to a business event. Every automated run flows through the same durable headless run path as an API-initiated run, so it inherits:
  • Durability — a headless_agent_runs row you can poll or stream.
  • One-run-per-agent — a second dispatch while a run is executing is skipped (schedules) or 409 AGENT_BUSY (API/run-now).
  • Lifecycle webhooks — agent_run.pending_approval, agent_run.completed, agent_run.failed (see Webhooks).
  • The approval loop — risky (require_approval) actions pause the run at pending_approval instead of executing unattended.
Triggers live under an agent and are managed with the agents.manage permission and the ai suite.

Trigger types

Supported event types today:
  • Scheduling: shift.started, shift.completed, shift.ncns, shift.first_approaching
  • Workforce / HR: staff.onboarded (a new staff member is created or an applicant/candidate is hired)
  • CRM: lead.qualified (a lead transitions to qualified)
  • Compliance: credential.expired (a credential expires, from the watchdog or a manual verification)
eventFilters are dot-path equality checks against the event payload (an empty filter matches everything) — e.g. { "facilityId": 3 } or { "type": "license" }.
A trigger’s failure streak (consecutiveFailures) now reflects run outcomes, not just dispatch failures: a run that errors mid-flight increments the streak, and a run that completes clears it. Once the streak hits the threshold the trigger is auto-disabled (audited as agent_trigger.auto_disabled). This means an agent that repeatedly errors — not merely one that fails to enqueue — is quarantined.

Create a schedule trigger

The dispatcher polls once a minute, computes the next occurrence in the trigger’s timezone (DST-aware), and writes lastRunAt / nextRunAt. A schedule trigger that raises 5 consecutive dispatch failures is auto-disabled (isActive: false).

Create an event trigger

Run identity and privilege-escalation guard

Each run acts as runAsUserId — this drives the tool scope and RBAC, so an automation can never do more than that user is permitted. It defaults to the caller. Binding a different user is only allowed for internal operators; otherwise the API returns 403 RUN_AS_FORBIDDEN.

Governance for unattended writes

Because nobody is at the keyboard, a scheduled/event agent that performs risky writes will pause at pending_approval and emit agent_run.pending_approval. Wire that webhook to an approver workflow (resolve via POST /api/approvals/{approvalId}/approve or /reject), or give the agent auto_execute autonomy if the writes are safe to run unattended. Every automated run start and every trigger mutation writes an audit entry. Approving over that API is a two-part authorization, and the scope alone is not enough:
  • Approve — needs the agents:approve scope and the ai.approve_actions RBAC permission on the credential’s bound user. Resolving an approval requires owning the run that raised it, which makes it self-approval; without ai.approve_actions you get 403 SELF_APPROVAL_FORBIDDEN.
  • Reject / withdraw — needs only the agents:approve scope. Rejection is ungated on purpose: withdrawing your own pending action can only reduce what runs, so it stays available as a fail-safe.
For a fully unattended fleet this means the approving identity should be a different one from the running identity: unless its bound user holds ai.approve_actions, the credential that fires the automation cannot wave its own writes through. See Agent Runs → Approvals and Governance.

Other operations

Run history for the fired runs is available via the headless run API (GET /api/agents/{id}/runs, agents:run scope).