A workflow is a directed acyclic graph stored as JSON. The canvas the operator drags into shape is the exact JSON the executor walks. What the admin sees on screen fires the moment the trigger arrives.
Every step claim takes a row-level lock and skips whatever another worker already holds. Every outbound side effect carries an idempotency-key header derived from tenant, event, run, step, and action type. Every failure that exhausts retries lands in a dead-letter ledger with the operator’s inline-resolve action visible on the same page. The engine runs entirely inside Postgres. One durable primitive; one deploy topology.
Six node types, all first-class
Trigger, action, condition, delay, parallel, join. Every one is a real dispatch branch in the executor.
- Trigger — exactly one per workflow, zero inbound edges. Five surfaces: lifecycle events on any entity (record created, updated, deleted, completed; relation added or removed; invoice status changed), scheduled sweeps, row-relative sweeps (“30 minutes before the meeting start time”), HMAC-SHA256-signed inbound webhooks, and manual invocation from the record page, from the agent’s invoke-workflow tool, or from an authorised API call.
- Condition — two outgoing handles, true and false. Only the matching branch enqueues on success.
- Delay — pauses the branch. The successor queues with a next-attempt time equal to now plus the duration; the claim window admits it once the wait elapses.
- Parallel — fan-out. Every outgoing edge enqueues concurrently.
- Join — fan-in. The runtime creates the join step only once every predecessor has a succeeded step, so a slow branch holds the join open while other branches downstream of the fast ones continue.
A partial unique index on the (run, node) pair makes step creation idempotent under fan-in. Two predecessor branches racing to spawn the join step converge on one row.
Thirteen actions, one registry
One workflow-action registry holds every action class. Each one ships an execute path, a simulator, and a manifest. The picker in the canvas inspector hydrates from the same catalogue the runtime dispatches from. A new action written on the API becomes a new option in the editor after a backend deploy, with no SPA change.
The thirteen actions cover record create / update / set-field / assign-owner / bulk update, add and remove relations, invoke a sub-workflow, run an agent step, enrich a record with AI, call a first-party MCP tool, call an external MCP tool through a connector, and emit a signed outbound webhook.
Two carry the Signals narrative. The external-MCP-tool-call action reaches through the connectors layer — Microsoft 365 inbox trawl, a partner API, whatever adapter the tenant has installed. The agent-run-step action drives one or more turns of the embedded agent inside the workflow run. The runtime mints a short-lived workflow-service token with a bounded lifetime and no permanent identity row. The auth layer recognises the session kind, skips user-membership lookup, enforces tenant match, and reads the role from the token. Audit attribution stays on the workflow run itself. The ledger reconciles cleanly at month-end because every agent turn a workflow spawned rolls up to the same usage meter as a direct chat turn.
Durable execution, on the substrate you already have
The claim primitive is a row-level lock that skips already-claimed rows. Multiple API replicas compete for the next step; exactly one wins; the rest move on. Step inserts collapse on a unique (run, node) constraint, so a replayed step converges to the existing row. An event-log gate on (tenant, event id) means a scheduler tick redelivered mid-flight converges to the existing run.
Every outbound webhook carries the idempotency-key header. A downstream receiver that respects it converges to one delivery no matter how many times the retry ladder walks it. Every failure that exhausts its retry budget lands in the dead-letter ledger with a reason, the payload, and the operator’s Resolve action attached. The engine is a live, monitored control. This is the same idempotency posture the substrate enforces at every other write path.
Signals as a workflow
A canonical inbox-signals template ships as a working workflow every tenant can enable: an external-MCP-tool step reaches into an installed inbox connector, an agent-run-step classifies what matters, a record-create step lands a task on the right account. The pattern replicates for any inbound stream — the Signals page reads through the same table.
The workflow-template library is a platform-tier catalogue. New tenants seed every active template as a draft on provisioning; existing tenants apply individual templates on demand. Save as platform template promotes a tenant’s workflow upstream; Apply to tenant copies one back down. One entry per canonical revenue motion, each one an editable draft the moment it lands.
Activities to invoices, in one workflow
A canonical invoice-from-activities template reads a tenant’s Activities marked To Be Billed, groups by account, and posts to the invoice-from-records endpoint. Each line carries the source-entity and source-id of the activity that produced it so a compliance auditor traces the line back to source. When the invoice writes, every source activity flips to Billed inside the same transaction. Invoice totals recompute atomically with the line-item write.
Reverse the flow: mark an invoice deleted and a paired workflow flips the source activities back to To Be Billed. Two workflows, one substrate, one audit row per state change. A CRO reading the pipeline against the ledger sees the same numbers the CFO reads against the AR report.
The takeaway
The workflow engine is a DAG the operator can see, an action registry the agent can call, and a step claim the database refuses to double-execute. Agent-run-step and external-MCP-tool-call sit as first-class action types alongside record-create. A new action written on the API lands in the picker after one backend deploy. The durable execution story is the substrate itself.