Automations
The workflow app — real triggers, a server-side executor, per-run history, and a first-class native surface. Automations run without a human in the loop.
Still gated in production
The Automations app is now a full native surface with a working runtime (triggers, executor, run history). It's currently visible in dev only while the last UI pass lands; published automations are live regardless of the app's visibility — the executor cron and the webhook receiver are running in prod. See Canvases and widgets → Hidden apps in production.
Automations is the workflow app. It's a native, off-canvas surface with an automation list on the left and a full-screen React Flow editor on the right. Each automation is a graph of typed nodes that runs end-to-end without a human in the loop — a webhook fires, a scheduler dispatches, a record changes, and the server walks the graph.
Where an Agent is a conversation-driven pipeline, an Automation is a standalone workflow you trigger from outside: schedule, webhook, platform event, or a delegate call from another agent.
Layout
Automations opens as a native shell surface — full-bleed, no React
Flow canvas underneath. AutomationsSurface composes:
- Full-height left panel (
AutomationWorkspace) — the same elevated sidebar treatment Data and Pages use. The header carries the app title and a primary New button; below it, a search input filters the list. Each row shows a status dot (draft / active / paused / error), the automation name, and a relative last-run timestamp. Hovering a row surfaces a delete icon behind a confirm dialog. A count footer sits at the bottom (3 automations). The panel is resize-persisted viausePanelResizeand mounts inside the sharedNativeSurfacechrome, so the arrangement matches Data / Dial / Calendar. - Right column — the flow editor for the selected automation,
mounted via
EditorShellinpageMode: the same React Flow canvas the legacy fullscreen editor used, sized to fit the pane instead of covering the app. Empty state (no automations yet) renders aNew automationprimary CTA in the pane.
The editor's floating top bar carries the status dot + name + Run (real server-side execution), Publish / Unpublish, Undo, Redo, Test (server dry run), and Exit. Validation gates Publish — see Publish validation.
See Canvases and widgets → native apps vs canvases for the shared native-app pattern.
Triggers
A trigger is a node at the top of the graph that starts the flow.
Every trigger stamps automations.trigger_type +
automations.trigger_config on save (via extractTriggerMeta), so
the server can find matching automations without parsing pipeline
JSON — the webhook router, schedule dispatcher, and event matcher
all select on those columns.
The runtime supports four trigger types today (a fifth,
campaign, is wired for the Campaigns
mode but not dispatched yet). Two legacy trigger nodes remain in the
palette for backwards compatibility (heartbeat, websocket) but have
no server runtime — publish validation rejects them with a
message pointing at the supported set.
| Trigger | Fires when… |
|---|---|
| Webhook | An external HTTP request hits the automation's per-automation URL. The receiver is a deployed edge function (automation-webhook v1) with a revocable token shown on the trigger node. |
| Schedule | An interval (Every N minutes / hours / days) or a cron expression (0 9 * * MON) elapses. A per-minute dispatcher walks next_run_at and enqueues runs. |
| P88 Event | A record is created/updated/deleted, a tag is assigned/removed, or a calendar event is created/updated/deleted. DB triggers fan into an automation_events outbox; a per-minute matcher enqueues runs with 10s dedup and a 30 runs/min circuit breaker. |
| Manual | A user clicks Run in the editor. |
Trigger detail — inspector fields, filters, secure-token flow — lives on Triggers.
The node palette
Six buckets in the palette. Executable nodes carry a per-node Play icon on hover for isolated testing (see Execution logs).
Execution
- Prompt — send a prompt to a model
- Process — generic processing step
- Tool Call — invoke a specific tool
- Delegate — call another agent or automation (
run_once) - Response — terminal node, return a result
Integration
- REST API Request — full HTTP client with inspector
- Code — run JavaScript inside the workflow
Records
- Insert Row — insert into a data table (server-side handler)
- Update Row — patch a row via the atomic
automation_patch_row_dataRPC, schema-whitelisted fields, soft-delete semantics; also supportsaddTags/removeTags - Delete Row — delete a record (server-side handler)
- Query Rows — fetch rows by table + filter
Tags
- Add Tag — writes a real
tag_assignmentsrow via the server - Remove Tag — removes an assignment
Flow control
- Condition — pill-style True / False split with branch labels;
deep false-branch pruning emits
skippedaudit events - Loop — repeat over an array
- Wait — sleep for a duration (fixed infinite-loop bug in the executor)
- Change Variable — set a workflow variable
Config
- Model, Identity, Sandbox, Tools, Sub-agents — five inline-editable config nodes that pin per-step settings.
Shared
- Start, Note — compact start node and free-text annotation.
The palette used to expose six broken nodes that never executed; they've been removed and the real action nodes (send-sms, send-email, tag ops, row ops, HTTP) are exposed instead.
Run: server-side execution
Publishing an automation flips status to active. From that point
on, its trigger is live — the runtime dispatches without further
input.
The Run button
The floating top bar's Run button executes the graph on the
server, not in the browser. It writes an automation_runs row,
walks the pipeline node by node, records per-node events with
timing and output, and refreshes the editor's Runs tab when the
run settles. Real run stats (last-run status + timestamp) flow back
into automations.last_run_at / last_run_status and drive the
list-panel row indicators.
An idle pending-drain cron (migration 154) polls for any queued
run the dispatchers may have missed and executes it via the same
edge function, so a run can never sit in pending forever.
Test as a server dry run
The Test button on the editor's top bar is now a server dry
run through the real executor — same code path as a live run, but
sends (SMS, email) and row writes are stubbed. Per-node results
stream back into the editor's Logs panel with the same status icons
as production: ✓ success, ✗ failed, ↷ skipped (a pruned
condition branch). This replaced the divergent client-side simulator
that used to walk the graph in JS — there is one execution truth
now, and Test / Run share it.
Runs tab
The editor's left panel gains a Runs tab once the editor is bound to a persisted automation. It lists recent runs with:
- Overall status, duration, and start time
- Trigger source (webhook / schedule / event / manual)
- Per-node event timeline — one row per node with status, duration, output snippet, and any error
- A refresh button (auto-refreshes after a Run or Test completes)
Backed by useAutomations().listRuns / listRunEvents — reads from
automation_runs + automation_run_events, both pruned by a nightly
retention cron (migration 157).
Publish validation
Publish is gated on validateAutomationForPublish — an editor lint
that blocks the transition with a human-readable list of issues:
- No trigger picked on the start node
- Schedule trigger with neither an interval nor a cron expression
- P88 Event trigger with no source (or Records with no table picked)
- Heartbeat / WebSocket triggers (no runtime — points at Webhook, Schedule, or P88 Event)
- Orphan enabled nodes with no incoming edge (they'd never run)
- Missing required fields per node type: an SMS with no body, an email with no subject/body, a Tag op with no tag name, HTTP with no URL, a Row op with no table, a Change Variable with no name
Issues surface in the editor's Logs panel as a checklist. Fix the issues, click Publish again.
One execution truth across Test and Run
The client and server used to disagree — the editor's Test simulator
could green-light a flow that the executor would fail on. That's
gone: Test hits the real executor in dry-run mode, tag nodes write
real tag_assignments, row nodes go through the atomic
automation_patch_row_data RPC (migration 156), and templating —
both {{binding}} upstream references and {token} payload lookups
— is unified server-side. Archived rows are excluded from row ops
by construction.
Editor truth: reliable saves
The editor used to silently drop node-config edits: hasUnsavedChanges
missed several edit paths and inspector changes could unmount before
the debounced save fired. Both paths are fixed — every edit path
flips the dirty flag, and unmount flushes the pending save so a
tab-close or navigation can't drop config.
Transient test results (per-node output the editor kept in data.lastResult)
no longer round-trip through the DB; they stay in memory and disappear
on unmount so a saved graph carries only the fields the executor reads.
Variables
Workflow variables live in the Variables tab of the left panel. Each variable has:
- Name, folder (for grouping), and description
- Type (text, number, boolean, json, …)
- Default value and current value
- Toggles for "scope" behavior
Set them from a Change Variable node anywhere in the workflow.
Reference them with {{varName}} in any bindable input via the
InputBindingModal.
Bindable inputs
Most node inputs are bindable. Click the ƒ icon to open the
InputBindingModal:
- Actions tab — upstream nodes plus their test results.
- Variables tab — workflow variables and built-in stores.
- Functions tab — 7 categories of formula functions: OPERATORS,
CONDITIONAL, MATH, TEXT, ARRAY, OBJECT, TYPE. Hover for inline help.
String-literal operators are now safe against overzealous rewrites,
and
>=/<=compile correctly.
The rich editor renders {{refs}} as colored badges and function
calls as purple pills. Click anything to insert it.
REST API Request inspector
The REST API node gets a full inspector panel:
- Method dropdown — GET / POST / PUT / PATCH / DELETE / OPTIONS
- URL field (bindable — use
{{var}}references and formulas) - Key-value builders for headers, query string, and body fields
- Content-type selector
- Toggles for server-side proxy, credentials, and streaming
Hit Test on the node to run it against live endpoints; the Logs panel shows the built request, response headers, and a JSON-tree preview of the body.
Runtime & storage
automations— the flow row (name, status,trigger_type,trigger_config, pipeline JSON,webhook_token,last_run_at,last_run_status).automation_runs— one row per execution, with status + timing + error.automation_run_events— one row per node execution inside a run; drives the Runs tab timeline.automation_events— the outbox fed by DB triggers on records, tag assignments, and calendar events. Service-role only, org-gated, change-gated. Consumed by the per-minute event matcher.automation-executor— the edge function (currently v11) that walks a pipeline. Auth uses a verified service-role JWT claim; CORS is wired for the editor's Test dry runs.automation-webhook— the edge function (v1) that receives external POSTs. Per-automation revocable tokens.- Migrations 154 – 157 — event triggers, outbox, atomic row-patch RPC, nightly retention. All applied to prod.