Workflows (visual automation canvas, in development)
An in-development n8n/Zapier-style automation builder: a workflow is a trigger plus a graph of steps, edited on a canvas beside the projects sidebar, with a step picker, a settings drawer, live validation, Build with AI that turns a sentence into a graph, a split view that watches an agent build it, retries and on-failure paths, and a wait-for-me approval step.
What it is
Workflows is an in-development n8n/Zapier-style automation builder inside
Omniscio. A workflow is a trigger plus a directed acyclic
graph (DAG) of steps: deterministic actions (HTTP request, send email, send a
message, run a recipe), a branching if/else node, and AI steps. When the
trigger fires — a manual "Run" click, a cron schedule, or an incoming message
matching a filter — Omniscio walks the graph in order and runs each node, passing
data forward between steps via {{ NodeName.path }} template references.
Every workflow starts off and cannot run until a human turns it on — nothing
executes silently. (The "lands in the inbox for approval" flow this page used to
describe is NOT built: turning a workflow on is done from the canvas bar, and no
inbox card is raised.)
This page covers the visual canvas editor and Build with AI surfaces — the sidebar row that docks a graph-editing panel beside the projects sidebar, the same way Flowcharts and Decks do. The underlying execution engine (trigger types, node registry, the DAG executor, the approval gate) is documented in workflow-engine-contract.md — read that first for anything touching how a workflow actually runs.
Where to find it
Workflows ships hidden. Turn it on under Settings → Lab → Workflows, then open the Workflows row in the sidebar to dock the graph-editing panel beside the projects sidebar. The saved-workflows list itself lives in the Pane-2 rail — the sub-sidebar beside Omniscio's main nav — in its Workflows tab, beside the Sessions tab (AI build sessions) and the Runs tab (run history).
How it behaves
How it is gated
Workflows is an in-development feature registered as 'workflows' in
src/shared/unreleased-features.ts (settingKey: 'workflowsEnabled'). It is
visible when any one of the following is true:
- The feature is marked
'shipped'in the registry (releases to everyone). - Omniscio is launched with the feature's reveal env var set.
- The user flips the Settings → Lab → Workflows toggle on.
No code reads settings.workflowsEnabled directly to decide sidebar
visibility — the gate goes through
isUnreleasedFeatureVisibleInRenderer('workflows', …) in
project-visibility.ts (UNRELEASED_PROJECT_GATES).
Using the canvas (build → configure → validate → turn on)
The Phase-2 canvas editor is built. Reveal via Settings → Lab → Workflows, then open the Workflows sidebar row.
The saved-workflows list lives in the Pane-2 rail (the sub-sidebar beside Omniscio's main nav), not inside the panel. That placement is load-bearing rather than cosmetic: the panel yields to the session chat while a build is open, so a rail rendered inside it would vanish exactly when the user needs a way back to their workflows. The rail carries three tabs — Workflows (the saved list), Sessions (AI build sessions), and Runs (run history; selecting a run shows its detail in the panel) — plus the split-view toggle. The node palette and the React Flow canvas stay in the panel:
- Create — "New workflow" makes an empty workflow and opens it.
- Build — press "Add step" (or double-click empty canvas) to open the
step picker: a search-first palette browsed app-first — your Favorites and
Recent steps first, then one group per app (Jira, Notion, Slack…), each owning its own
triggers and actions. Type to filter — it matches names, apps and synonyms, so "api" finds
HTTP request — arrow-key through the results and press Enter to drop the step into the middle
of your view. A side pane previews what the highlighted step does and what it hands to later
steps, and a Trigger badge marks the steps that can start a workflow. On a brand-new
workflow the picker shows triggers only, under a "Choose how this workflow starts" title,
since a workflow always begins with its trigger. You can still add a step from the left rail,
which browses the same app groups (a single-step app is a chip, a multi-step app expands) and is
the place to drag a step onto the canvas by hand. A ⭐ on the rail pins an app or a step
to the top of both, and your pins and recents are remembered across restarts. A workflow needs
one trigger, then actions / an if-branch / AI steps. Drag between node handles to connect them; a branch node has separate
true/falseoutput handles (labeled Yes / No on the canvas so the two paths are distinguishable). "Tidy up" auto-lays-out the graph (elk, lazily loaded). Move/delete freely; every edit is undo/redo-able and autosaves. - Configure — click a step to open the right-hand settings drawer. Each
node type ships a form descriptor (its
formon theNodeDefinition, surfaced overWORKFLOW_NODE_TYPES), so the drawer renders the right fields (text / number / select / toggle / value-ref) bound straight to the node's config. In a value-ref field, "+ Insert a value from an earlier step" lists the outputs of upstream steps only (topo-bounded) and splices a{{ Step.field }}token into the field. - Validate — a banner above the canvas shows the live graph-validation
issue count (via the shared
validateWorkflowGraph); a workflow with no trigger, a cycle, an unknown type, or a forward reference is flagged. - Turn it on — the bottom bar is the workflow's on/off switch: it reads
Off with a Turn on button, or On with Turn off. Turning it on
flushes the pending autosave and makes the workflow live, so it reacts to its
trigger. "Turn on" is disabled while the graph is invalid; turning off is
always allowed — a live workflow whose graph later broke must still be
stoppable. The wording is on/off but the underlying gate is unchanged
(
approvalStatus); there is no separate approver, because the person building the workflow is the only one who could approve it.
Not yet in the canvas (documented as gaps in the engine contract): a
run-history/observability viewer, the inbound webhook trigger (Phase 4), and
chip-rendering of {{ }} tokens (they show as literal text today).
Build with AI (describe → graph)
Instead of hand-placing every node, "Build with AI" turns a plain-English description into a whole workflow graph:
- From the Workflows list, click Build with AI and describe what you want in a sentence or two ("every morning at 9, check my unread Gmail and send me a summary in Slack").
- Omniscio creates an empty workflow, then spawns a normal Omniscio AI session (you keep your usual model picker) with your description pre-filled as the draft message — it is never auto-sent, so you can edit it first.
- Behind the scenes the session is briefed with a hidden build primer that
points it at the
/workflowCLI (the same REST surface the CLI-server contract documents) and the live node catalogue viaGET /workflow/node-types— which returns each node'sformdescriptor (fields, options, outputs), so the agent always builds against the real registry, never a hard-coded node list. The agent builds the graph and then stops — it does not run the workflow (running is a separate, human-approved, billable step). - The build session lands in the rail's Sessions tab, so you can watch it work or ask follow-up questions.
- On desktop the canvas opens beside that chat and fills in live as the agent writes — see the next section. You no longer wait for the agent to finish and then go looking for the result.
- When it's done the canvas is yours again; review the graph like any hand-built workflow and click Turn on when you're happy.
If the spawn itself fails, Omniscio deletes the orphan empty workflow it created in step 2 rather than leaving a dead entry in your list.
Watching a build live (the split view)
While an AI build chat is open on desktop, the workflow canvas renders side-by-side with the chat instead of the chat replacing it, so the graph is visibly assembled as the agent works. Four behaviours make that readable, and each exists to answer a question the user would otherwise have to guess at:
- The canvas is watch-only while the agent owns it — no drag, connect,
select, delete, palette drop, or "Tidy up". Pan and zoom deliberately stay on:
the point is to inspect the graph being built, not stare at a frozen picture.
The lock is derived from live session status on every render and never
latched, so a crashed, errored, stalled, paused or vanished build session
hands the canvas back by itself. A session parked on account capacity
(
waiting) keeps the lock — it resumes on its own, and it resumes writing. - Nodes the agent just touched are briefly tinted (~1s, then it fades), so the eye is pointed at the change rather than hunting a re-laid-out canvas for it. Position is deliberately excluded from the "did this change?" comparison: the live arrangement is never persisted, so every push re-lays the whole graph out and a position-aware diff would strobe the entire canvas every time.
- The viewport follows the growing graph until the user pans or zooms, and then never takes it back. Closing the split is the reset.
- A status strip says who owns the canvas — "Agent is building…", or "Build complete — the canvas is editable again." The strip and the canvas read the same lock value, so the strip can never promise the canvas is editable while it is still read-only.
Why edits are suppressed rather than merged. While the lock is held the store refuses both to queue and to flush a save for that workflow, so a stale local graph can never overwrite the nodes the agent just wrote over the CLI. There is one honest cost: the lock engages on a rising edge that can straddle the 500 ms autosave debounce, so an edit made in the instant before a build starts is discarded by design rather than persisted. In the steady state the user is not editing; across that edge a real edit can exist and is dropped, with the strip flipping to "Agent is building…" as the only signal.
Mobile keeps the chat-replaces-canvas behaviour (too narrow to split), and the desktop split can be collapsed from the rail's toggle — the agent keeps building either way.
Where a live workflow shows up
Turning a workflow on makes it live, but the places that reflect that are spread across three surfaces — worth knowing, because only one of them is inside Workflows:
- The saved-workflows list marks each live row with a small dot (reading the
same
approvalStatusthe canvas bar and the engine gate use), so the list can be scanned without opening each workflow. - The Cron panel, for a
trigger.scheduleworkflow:syncWorkflowSchedulecreates a backing cron job namedWorkflow: <name>, and the cron sidebar does not filter by type — so it appears alongside recipes and scripts. ⚠ Nothing currently stops that job being disabled or deleted there, which would silently stop the workflow firing while its own bar still reads On. Two switches for one thing; reconciling them is an open design question. - The Runs tab in the rail, once it has actually fired — each run with its per-step breakdown.
- The inbox, only if the graph ends in a node that puts something there
(
action.create_alert). A workflow without one runs and produces nothing the user would notice.
When a workflow stops part-way and waits
A workflow does not have to finish in one go. A step can stop the whole workflow part-way through and wait — for you to answer something, for a moment in time, or for something to happen elsewhere. While it waits, the run shows as Paused in the Runs tab, with the step it is sitting on.
Three things are worth knowing about a waiting run:
- It survives closing the app. A waiting run is written down, not held in memory. Quit Omniscio, restart your Mac, come back tomorrow — the run is still there, still waiting, still knows everything the earlier steps produced.
- Carrying on does not repeat what already ran. When the workflow picks up again, every step that already finished is reused as-is rather than run a second time. An email that was already sent is not sent again; a message that was already posted is not posted again. Only the step that was waiting, and the steps after it, actually do anything.
- It never waits forever in silence. A waiting step can carry a time limit. If nothing answers before the limit runs out and the step has a Timeout path drawn, the workflow carries on down that path; if it has none, the run is marked failed, saying it was waiting on a step that wasn't answered in time — including when the limit ran out while the app was closed, which is noticed the next time you open Omniscio. A step with no time limit waits indefinitely, but it always shows as Paused rather than looking finished.
Asking a person to approve (the "wait for me" step)
The first step that actually pauses a run is Wait for me — it stops the workflow and asks a person to approve or reject before it carries on. Add it like any other step; it sits on the canvas with a short question, an optional bit of detail, and up to a handful of yes/no or short-answer questions to collect along with the decision.
- It reaches you in the inbox. When a run hits the step, a card appears in the inbox with your question and an Approve and a Reject button. Answering it there — or on a paired phone, the card is the same — unblocks the run. On the canvas the step glows amber-orange while it waits, the app's "waiting" colour.
- Three ways out, three paths. The step has three outgoing paths you can wire to different next steps: Approved, Rejected, and Timeout. Rejecting is a real answer, not an error — the workflow simply takes the Rejected path (or just stops, if you drew nothing there). Timeout is taken only if you set a time limit and nobody answers in time.
- Its answer is usable later. Any short-answer questions you asked are readable by the steps that follow, the same way one step reads another's output.
- You cannot answer it on the workflow's behalf from a script. The approval is a person's decision, so it is answered only from the inbox card (yours or your phone's), never from the command-line control server — an automation answering its own approval would defeat the point of asking.
- Turning the workflow off or editing it cancels a pending answer. If you turn the workflow off, or change its steps (which sends it back for approval), a run that was waiting on it will not carry on when answered — it re-checks that the workflow is still on and approved before spending anything, exactly as starting or re-running it does.
When a step fails (retry, and an "on failure" path)
A step that fails no longer has to take the whole workflow down with it. There are two things you can do about it, and they combine.
Retry — try the same step again. Open a step's settings and turn on Retry this step if it fails. You choose how many attempts (up to 10) and how long to wait between them — either the same wait each time, or a wait that doubles (1s, 2s, 4s…), which is the polite way to treat a service that is briefly busy. A step with retry turned off behaves exactly as it always has: one attempt.
Two deliberate limits:
- A waiting step is never retried. Pausing is not failing, so a step that stops to wait for you is left alone.
- A step whose settings don't make sense is never retried either. That is a mistake in how the step is set up, not bad luck, so trying it four more times would only waste time.
If a step can send something (an email, a Slack message) or create something (an issue, a page), the settings panel warns you: the first attempt may already have gone through before it reported failure, so retrying could send it twice. Whether that is acceptable is your call — which is why it is off by default.
An "on failure" path — carry on down a different route. Every step except
the trigger now has a second output on its right-hand side, marked in red. Drag
a connection from there to another step, and that is where the workflow goes if
this step fails. The run carries on down that path and can still finish
successfully. The step that failed is still recorded as failed, so the Runs tab
tells you the truth about what happened — and the step you connect can read the
error text with {{ StepName.message }}, which is what makes "post the failure
to Slack" or "create a ticket describing what broke" possible.
If a step has no "on failure" connection, nothing changes: it fails, the run is marked failed, and the steps after it are skipped.
Retry happens first. A step set to 3 attempts uses all three; only if all three fail does the "on failure" path open. So the two work together — retry for a blip, the failure path for when something is genuinely broken.
Two things retry deliberately will not do. An AI step cannot be retried at all — a failed attempt still costs real money that the run has no way to account for, so repeating it would quietly spend more than the run reports. The Retry control simply is not offered on those steps. And an HTTP step comes with no suggested setting: a request that times out may already have reached the other end, so whether repeating it is safe depends on what it does — which is a judgement only you can make. If you do turn it on for anything other than a plain read, the panel says so.
Not built yet: the canvas does not yet show a live "retrying 2 of 3" label while it happens — the run history records the outcome either way.
Re-running a past run from a step
When a run went wrong at step 4, you usually don't want to start the whole thing over — steps 1 to 3 already did their work, and repeating them could send the same message twice. Open the run in the Runs tab and each step has a Replay from here button.
What it does:
- It starts a NEW run. The original stays exactly as it was, so you never lose the record of what actually happened. The new run remembers which run it re-ran.
- It repeats that step and only what depends on it. Steps that don't depend on the one you picked are reused as-is, not repeated — so a message that already went out doesn't go out again just because it happened to sit on a different branch.
- It costs money, because the steps it re-runs really run again. It asks you to confirm first, and it is a desktop-only action — you cannot trigger it from your phone.
- It uses the workflow as it was when that run started, not as the canvas looks today.
It refuses in three cases, with a reason: the step isn't part of that run, an earlier step is still waiting for an answer (answer it first), or the workflow has since been turned off or un-approved — re-running is not a way around the approval gate.
Trigger types
A workflow starts with exactly one trigger node. The engine supports six:
- Manual (
trigger.manual) — fires when a user clicks Run (UI or CLI). - Schedule (
trigger.schedule) — fires on a cron schedule. - Message (
trigger.message) — fires when an incoming message (channel, Gmail, Pushbullet) matches a channel-scope and optional keyword filter. - Jira issue (
trigger.jira_issue) — polls every 2 minutes, fires when an issue is created, transitions status, or is updated. Requires Jira credentials. - Arij issue (
trigger.arij_issue) — real-time WebSocket subscription, fires on issue changes, board changes, comments, or agent-state events. - KMS note (
trigger.kms_note) — fires when a note in your KMS vault is created or updated and matches a tag and/or folder filter. Push-driven (no polling) — the KMS file watcher emits the event. Only fires on the transition from not-matching to matching (a note saved repeatedly while it already matches does not re-fire). A 5-minute cooldown per note prevents rapid flap re-fires.
Create alert (action.create_alert) is a general-purpose action node that
creates an inbox item with a title, content, and optional dedup key — useful as
the "notify me" step at the end of a KMS or any other trigger chain.
HTTP request auth (action.http) — the HTTP node can send saved
authentication: in the step's Authentication field, pick an existing
credential or add one inline (a bearer token, an API key sent as a custom
header, or basic username/password). The secret is stored encrypted in the
shared automation-credential vault; the workflow only references it by id, and
the auth header is applied at run time inside the engine port — so an author
never pastes a token into the graph, and every decrypt is audit-logged. This is
the first step of connector convergence: the Workflows canvas reusing the
same credential store + auth logic the v2 Automations engine already has, rather
than a parallel one. (Custom request headers are carried by the node but not yet
UI-editable — a documented fast-follow.)
Message + page connectors — three credential-backed action nodes ride the
same seam: Send Slack message (action.slack_send_message), Create Notion
page (action.notion_create_page), Send Telegram message
(action.telegram_send_message), and Send Discord message
(action.discord_send_message, via an incoming webhook). Each has an account field that picks (or
inline-adds) the saved Slack bot token / Notion integration token / Telegram bot
token; the token is resolved + applied inside the engine port at run time (never
in the graph), and every decrypt is audit-logged. The inline add-credential form
reuses the shared credentials editor, so it renders the right inputs for any
kind. (The v2 Automations actions for these providers still keep their own copy
of the send logic — migrating them onto the shared helpers is a documented
follow-up.)
What an AI step is allowed to do (permissions)
An AI step (ai.agent_step) — and a Run a recipe step — starts a real Claude
session, and that session asks you before it does anything your permission level says
needs asking. A workflow runs on its own, so the session it starts inherits your global
level (Settings → Workflow → Agent Permissions), with one deliberate exception:
- A run started by an incoming message or email — a Message trigger, or an email
that matched an email trigger — runs its AI steps at the Guarded floor instead
of your global level, and shows them in your sidebar rather than hiding them. Someone
else's text must never end up handled as if you had asked for it, so on those runs
the sensitive-file net over
~/.ssh,~/.aws,~/.npmrc,~/.bashrcand your GPG key rings stays on, and each command or config write asks for your approval first — even if you normally run at Full trust. - Every other trigger — Manual, Schedule, Jira issue, Arij issue, KMS note — is something you wired up yourself, so its steps keep running exactly as they always have: at your level, and out of the way.
When an email-triggered workflow's AI step needs a command, that run's session waits for you in the sidebar under Needs You; approving it lets the step carry on.
Status
The Workflow Engine (trigger types, node registry, DAG executor, approval
gate, IPC + CLI) shipped dark behind workflowsEnabled. The Canvas editor
(Phase 2 — build / configure / validate / request-approval, above) and
AI-build (Phase 3 — describe → graph, above) are both built on top of it,
also gated dark. Pausing and carrying on (above) is built and tested, but no
step uses it yet — the first one ships with the "wait for me" approval step. The
integration/webhook expansion (Phase 4) is not built.
Reveal via Settings → Lab → Workflows.
For agents
How it is mounted
Workflows is an amc-builtin virtual project (__workflow__, defined in
src/shared/virtual-project-ids.ts) registered once in
src/shared/integrations/workflow.ts and assembled into
INTEGRATION_REGISTRY by npm run integrations:reindex — mirroring how
Flowcharts (__flowchart__) is wired.
Like Flowcharts, the Workflows panel now hosts AI build sessions: __workflow__
is spawnable and resolver-mapped (resolveProjectWorkDir() maps it to
<userData>/workflow-agent, and it is in SPAWNABLE_VIRTUAL_PROJECT_SENTINELS).
It is still deliberately not in SESSION_HOSTING_VIRTUAL_PROJECT_PATHS — that
set drives things like Ctrl+T "jump to a hosting project's sessions"; Workflows
owns its own session-reveal path (the panel's Sessions tab, via
useWorkflowsSessionHost) instead. A workflow's own AI step nodes still run
inside the engine's executor (see the engine contract), not as a user-facing
chat session — only the build flow below spawns a real chat session.
Key files
| What | Where |
|---|---|
| Virtual project id | src/shared/virtual-project-ids.ts (WORKFLOW_PROJECT_ID) |
| Integration manifest | src/shared/integrations/workflow.ts |
| Gating registry | src/shared/unreleased-features.ts (workflows) |
| Canvas editor (renderer) | src/renderer/src/features/workflows/ (WorkflowEditor, WorkflowCanvas, NodePalette, WorkflowListSidebar, NodeSettingsDrawer, InsertValueButton, ValidationBanner, ApprovalBar, workflows-store.ts) |
| Node form descriptors | each src/main/services/workflow-engine/nodes/*-node.ts (form) + shared types in src/shared/workflows/types.ts |
| Build with AI (renderer) | src/renderer/src/features/workflows/ (BuildWithAiModal.tsx, useWorkflowsAiBuild.ts, workflows-spawn-prompt.ts, WorkflowSidebarTabs.tsx, workflows-session-host.ts) |
| Live build split view | src/renderer/src/features/workflows/ (WorkflowBuildPane.tsx, WorkflowBuildStatusStrip.tsx, workflows-build-lock.ts, useWorkflowsBuildLock.ts, workflow-graph-diff.ts, workflows-build-session.ts) + features/dashboard/workflows-split.ts |
| Engine + data model + canvas + AI-build invariants | see workflow-engine-contract.md |
Related
Workflows is the graph-building sibling of the older automation surfaces: Use recipes covers single-path multi-step runs, and Agent trigger recipes covers recipes an agent can fire. The credential store the HTTP, Slack, Notion, Telegram and Discord steps reuse is documented on Automation credentials. To have an agent walk you through building one, see Workflow Coach.
Last verified 2026-09-30