Apps

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 via usePanelResize and mounts inside the shared NativeSurface chrome, so the arrangement matches Data / Dial / Calendar.
  • Right column — the flow editor for the selected automation, mounted via EditorShell in pageMode: 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 a New automation primary 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.

TriggerFires when…
WebhookAn 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.
ScheduleAn 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 EventA 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.
ManualA 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_data RPC, schema-whitelisted fields, soft-delete semantics; also supports addTags / removeTags
  • Delete Row — delete a record (server-side handler)
  • Query Rows — fetch rows by table + filter

Tags

  • Add Tag — writes a real tag_assignments row via the server
  • Remove Tag — removes an assignment

Flow control

  • Condition — pill-style True / False split with branch labels; deep false-branch pruning emits skipped audit 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.

Where to next

On this page