ban) is the action’s identity. It’s referred to elsewhere as user.actions.ban.
Running
run receives:
record: the record, freshly fetched withget.input: the form values, validated againstinputand typed from it.actor: who ran it, withname,type(user,agentortriage) and their role.item: the id of the item it was run from, if any.
Forms
input is a schema, and Pult builds the form from it: text fields, numbers, dates, switches, single and multiple choice from enums, and multi-line boxes for strings longer than 200 characters. .describe() adds a hint under a field. An action without input runs straight away (unless it needs confirmation).
defaults(record) prefills the form from the record:
pick turns a field into a searchable record picker, and choices fills a dropdown from your app:
{ value, label } pairs. The history shows a picked record by its title.
When an action applies
when(record) hides the action unless it applies right now: Ban only for users who aren’t banned, Unban only for those who are. Your app checks it again when the action runs, so an action that stopped applying in the meantime is refused with a clear message.
Guardrails
danger: "confirm" asks for confirmation first. danger: "reauth" also asks the person to sign in again if they haven’t in the last ten minutes. Dangerous actions are shown in red.
approval: true means one person asks and someone else approves. The request waits under Approvals; the person who asked can’t approve it themselves, and approving a reauth action needs a fresh sign-in too. The history shows who asked, who approved and what ran.
preview shows what would happen before anyone commits:
Background actions
Long work (exports, migrations, bulk changes in your own system) can run in the background:progress takes a fraction between 0 and 1 (or null when you don’t know) and an optional note.
Actions without a record
Some work isn’t about one record: purging a cache, re-running last night’s payouts, sending a digest. Define those on their own:when, and record is null when they run. Grant them in a role like other actions. People run them from ⌘K, from pages and from any block with b.actions(purgeCache).
On a schedule
An action without a record can also run by itself:schedule is a cron expression, or an object with one and a timezone. Without a timezone it runs in UTC. Each environment runs its own schedule, and every run is in the audit log under “Schedule” with its result. If your app can’t be reached when a run is due, that run is recorded as failed instead of piling up for later.
People can still run a scheduled action by hand. pult check makes sure scheduled actions need no input without a default and no approval, since nobody is there to give them.
Triage and agents
Triage rules can run actions that are harmless (danger: "none") or that you mark automatable: true. They never run actions that need an approval. Agents run actions through their role like people do, but can’t run reauth actions, since they can’t sign in again.