Skip to content

Workflow orchestration

On the Flow canvas, you design visual pipelines that move data from source to outcome. The workflow engine runs them on the server: when a run executes, nodes whose inputs or configuration changed re-run, while independent branches execute in parallel. You reuse work through templates and subgraphs, and every run can leave audit and lineage metadata for compliance review. See Compliance evidence for data lineage and Data for connectors, storage, and file formats.

A workflow is a directed graph of nodes connected by edges. Each node performs one step and passes typed dataset ports downstream.

You start a run from the canvas Run control. Auto-build in the workflow ⋯ menu decides what happens when the workspace changes on its own: on, Fontana starts the build for you; off, the change waits for a Run. Your browser tab for an open workspace is {workspace name} | Workspaces | Fontana AI (the Graph or Simple view query is not in the tab). You open a Workspace workflow from Workspaces or from a link that names the workspace and the .wf.json file. A Fontana Files administrator (files:admin) can open any workspace file. If you can read a workspace, you see the same run data as everyone else with access. If you can edit it, you can Run any workflow in it. If you cannot read that file, Flow stays on Workspaces and tells you so. When a run executes, the engine:

  • Reacts to change - each node hashes its configuration, incoming edges, and input data. Nodes whose hash matches the last successful run reuse prior output; others re-run. Frozen nodes keep their last output until you unfreeze them. If a node hash cannot be calculated, the run fails with an error for that node instead of treating the node as up to date.
  • Runs in parallel - independent branches execute concurrently where the graph allows
  • Executes server-side - large files, PDF parsing, connector fetch, and PyAirbyte jobs run on the workflow engine
  • Persists run-scoped data - uploads, JSONL datasets, exports, and audit sidecars survive pod restarts within your workspace boundary. After the workflow engine restarts, Explore can restore published node outputs that still have rows.

One run of a workflow is in flight at a time, while your other workflows keep running alongside it. Press Run during a run and the control reads Queued next: your request is picked up as soon as that run finishes, and pressing Run again while it waits keeps it to one queued request. Cancel beside the control covers both halves, the run in progress and the request waiting behind it, and the rows the last successful run published stay in place. Each run reuses the output of every node whose configuration and inputs are unchanged, so a run after one edit executes that node and the nodes downstream of it.

If a build exhausts recovery (the worker runs out of memory, or the process is lost), Fontana locks that workflow. Auto-build and agent runs skip it so your other workspaces keep building. Unlock it from the canvas Run control when you are ready to try again. A single large build that hits its memory budget fails that run and leaves the engine up.

Each node answers one sentence, and that sentence is the whole model:

Trigger this node [off | automatically | every …] · [at most once every …] · [always | if] [formula]

Open it from the node editor header. The button carries the answer, so you can read a node’s setting without opening the panel: Auto, Disabled, or the schedule in words (Every day at 8AM). The panel is titled Node triggers, every chip commits as you change it, and you close it with its ✕, with Escape, or with the button again. A click elsewhere leaves it open while you work in it.

What wakes the node is the first chip, and it has three values.

Value What it does
off Never execute this node. Off is part of the node’s result, so switching it rebuilds the workflow; the other two chips are free.
automatically Execute this node when its own config or inputs change, following the workspace’s own builds. A schedule you stored is kept and parked, so switching back to every … gets it back.
every … Wake this node on its own schedule. Input changes stop building it.

Schedule times are UTC. Choose every few minutes, every few hours, every day, selected weekdays, or monthly, then the hour and minute, and the control writes the schedule back to you in plain English under the fields. The panel shows when the node last fired. If Fontana was unavailable when a scheduled time came round, the node fires once as soon as it is back and reports how many scheduled times it missed, so a single run brings the workflow up to date. A node holds one schedule and a workspace holds at most ten. A workflow you copy, or create from a template, keeps its schedules and starts firing on them straight away.

When a schedule comes due, Fontana starts a build of the whole workflow and marks that node for re-execution, so the node and everything downstream of it refresh while unchanged branches reuse their last output. Schedules that come due together produce one build, not one each. A workflow you have disabled from the workflow ⋯ menu does not fire.

A cooldown is the second chip, at most once every …: a floor on how often an expensive node executes when fast-changing data upstream keeps waking it. It defers rather than drops. A change inside the window is not lost; it waits for the end of the window, and several changes in the same window become one execution. The node keeps its last published output meanwhile, so everything downstream reads the previous rows rather than nothing. The ladder runs from one minute to 24 hours, and as often as needed is the default. The chip belongs to automatically only, because a schedule is already a rate.

A condition is the third chip. always runs the node every time something triggers it. if runs it only when a formula over what arrives at the node is true. The formula is one expression that gives true or false, written on its own with no statement around it:

input().rows().count() > 0
input().rows().some(current.prop("SIDE").test("BUY"))
context().prop("triggerReason") == "schedule"

input() is the node’s own incoming rows, so the second formula runs the node only when a BUY row arrived. context().prop("triggerReason") is what started this execution. A node with several wires reads one of them by its handle with inputs().prop("trades").

A condition can also read the exceptions raised upstream. Every incoming wire carries a count of the exceptions on its upstream port in three severities: info (levels 1 and 2), warning (level 3), and error (levels 4 and 5).

input().exceptions().prop("warning") == 0 and input().exceptions().prop("error") == 0
inputs().prop("trades").exceptions().prop("error") == 0

input().exceptions() adds up every incoming wire, so the first formula is false as soon as any one wire carries a warning or error exception. inputs().prop("trades").exceptions() reads the one wire with that handle.

Fontana checks the condition in every build that reaches the node, before it decides whether the node can reuse its previous result. When the condition is false the node does not run, publishes no rows on any of its ports, and shows Not triggered with its condition, and the build carries on. The next build asks again, so a node that held back while an upstream warning was open runs once that warning is gone. A formula that does not parse, or that returns anything but true or false, fails the node with the formula’s own message. The formula editor is the same one the Operations badges use, with its lint, completion, and hover.

The condition is the only thing that holds a node back for upstream exceptions. A node set to always runs and reads every row, whatever was raised before it. Save File and Email Export nodes are the two that start with a condition when you add them:

input().exceptions().prop("warning") == 0 and input().exceptions().prop("error") == 0

With it, a file is written or an email is sent only when no incoming wire carries a warning or error exception. Edit it on the node, or switch the chip to always to export whatever arrives. A Save File or Email Export node that has no condition, for example one an agent wrote without it, exports on every run.

A condition is a gate, not a wake source: it decides whether a node executes in a build something else started. Keeping the last result instead of re-executing is a different control, frozen, which stays on its own.

Saving a schedule records a Trigger created activity against you, and each fire records a Trigger fired activity that the build it starts is linked to, so a scheduled build traces back to whoever set the schedule. See Activity log.

Auto-build in the workflow ⋯ menu is a setting of the workspace itself, not of your browser, so everyone who can edit the workspace sees the same choice and it survives a reload. Every workspace is on until you turn it off. With it on, a change to the workflow starts a build: a canvas edit, a new version of a file a File Input reads, or a cell or header edit. With it off, the change is recorded and waits. The workspace shows the Stale badge and nothing runs until you press Run, a node schedule comes due, an agent asks for a run, or an email arrives.

Turning Auto-build off does not cancel a build already in progress, and turning it back on picks up the change that was waiting.

Published data, edits, and what a view says before a build

Section titled “Published data, edits, and what a view says before a build”

Every build publishes each node’s output rows into the workspace through Fontana Files, with that port’s columns, audit items, and lineage. Explore, the port grids on the canvas, dashboard tiles, and node descriptions all read that published data, so everyone with access to the workspace sees the same rows.

Because data exists only after a build has published it, a view says which state it is in rather than showing an empty grid: no build has published yet, this port was not published by the last build, a previous build whose rows were not kept, or rows that are ready to read. While a build is in progress, every view shows Rows publish when the run finishes and the canvas counts stay live.

A cell you edit in Explore is saved to the workspace and committed in its file history, so filters, sorts, counts, charts, and downloads all read your edited value, and the next build carries it into every node downstream.

See Published data for the full list of states, how edits travel, and the query blocks templates read published data with.

Validations

Apply schema, business-rule, and tolerance checks as deterministic steps; failures route to owners with full context attached.

Transformations

Reshape, normalise, and map payloads between source formats and target schemas with versioned transformation logic.

Compute

Run grouping, filtering, aggregations, enrichment, and other compute steps on governed datasets inside the workflow boundary.

When you open a node for editing, Settings Display lets you set Title, Description, Theme, then Icon. Icon opens the asset library filtered to icons, where you pick a built-in icon, a Heroicon or a shared asset, or add one, and save it with Use icon. Until you pick one, the node keeps its type glyph; Use type icon returns to it. The icon takes the node’s theme colour when it is a single-colour drawing. Skills use the same library. Lineage indicator decides how this node marks the cells it contributed to in the Explore views of nodes downstream of it: Colour dot or Icon add a marker at the right of the cell, Background washes the cell in a pale tint of the node’s theme, Bottom border and Left border draw an edge in the theme colour (a conditional formatting background replaces the wash and row selection tints over it; the edges stay). A sample cell beside the dropdown shows the selected mode in the node’s theme and icon. It changes nothing about the run. Editors for nodes that output a dataset have an Explore tab right after the primary tab (first when the node has no primary tab); File Input and REST API enable it once a file is loaded or output rows exist. Explore lists that output as named views: table, board, list, JSON, Details, and a chart when the rows can be queried. Filters and sorts run on the server over the published rows. The view layout, filters, and header are saved on the node. In a Workspace workflow, Explore reads the rows the last run published to the workspace through Fontana Files (before a run, a File Input’s own workspace file), and a picker in its toolbar switches between the outputs that run published for the node, one entry per sheet for a workbook. Opening that published file, or a Save File export, from the workspace shows this same Explore view in Preview. The open grid follows changes that reach the workspace while you look at it: a run started by an agent or a schedule brings its rows in, and a view layout an agent saves appears without reopening the file. Next to the picker, a toggle switches between the rows, the schema the run published for that output as a tree, and the same schema as JSON. A cell shows one marker per upstream node with a lineage indicator that produced it; hover the markers to see where the value came from, hop by hop, with the value at each step. A green dot marks a value this node changed from what it read upstream (hover for the old and new value); a yellow dot marks a cell someone edited. Click a cell to select it and use the arrow keys to move; Enter or a double-click edits a node’s own output rows; the edit is saved to the workspace, read by this view’s own filters and counts, and applied to that row on the next build (Published data). See Data lineage. The Settings tab ends with a Raw Config section that shows the full workflow-config object for that node. Valid edits apply when the editor loses focus; invalid JSON or changes to id / type keep your draft and show an error. Marker stroke nodes stay canvas-only and do not include this tab. Escape closes dismissible layers one at a time, most recently opened first. Escape closes the editor when its fields are empty after trim. If a field in the editor still has text, Escape does not close the editor and does not clear your draft, even after a menu on top has already closed. Open menus, dialogs, and Monaco suggest or find keep Escape until they close.

A Formula operation can style the data it writes, and those styles follow the data. setStyle("Dirty Price", "background", "rose") marks that column, and every value computed from it carries the mark: write setProp("Consideration", prop("Dirty Price") * prop("Quantity")) after it and Consideration is rose too. The styles ride on the operation’s output, so they stay with the rows through later operations, into groups, and in every Explore view that shows them. clearStyle and clearRowStyle remove one style key or all of them.

A cell read only to decide something is not a source, so a field used in an if condition, a filter or a sort key does not pass its style on, and writing a cell from a constant clears the style it had. Where many rows become one, the group keeps the union of its members’ styles. A value read out of another wire’s table brings that cell’s marks with it, so an enriched column keeps the marking of the column it came from.

An operation that returns a whole row, such as an AI call or a script, gives every cell of its output row the union of the input row’s marks, because Fontana cannot see which cell came from which. A query carries no marks at all. A formula is the operation that keeps marks per cell.

This is the data’s own marking, which is why it differs from a view’s conditional formatting: a view formula paints what one viewer sees and can use me() and the view’s option colours, while a data style knows where a value came from and nothing about who is looking. Where both paint the same cell, data styles go on first and the view formula overrides only the keys it sets. An unusable colour never fails a run: the write is skipped and reported on the node, and the data is written as normal.

Open the bottom omni toolbar Preferences control to adjust canvas display. Among fit view, lock, layout direction (LR / TB), and full vs data-only mode, you can switch node corner radius with an icon-only split control (rounded or square). The choice is stored on the workflow display config and applies to standard workflow node cards. The omni dock is canvas tools and Preferences only. You add nodes and operations from the right-hand tools rail, Quick add on a selected node, and My Components.

Select a graph node and drag the bottom-right corner to change both width and height. Flow saves that size with the workflow, so leave, re-enter, and refresh restore it. The card stays at least as tall as its content, and you cannot shrink below the designed minimum width for that node type. Historical run canvases stay read-only. Panel nodes keep their own edge resizer for width and height.

The right-hand tools rail (Overview, Nodes, Operations, Errors, My Components, and related panels) docks as a column that takes width from the canvas. The graph and other workflow views shrink; the run toolbar recenters in the remaining space. The panel keeps rounded-left card chrome and a left-edge resize handle. Keyboard N toggles Nodes and O toggles Operations when you are not typing in a field. Overview is the inspector: with nothing selected it shows the mini map, workflow info, stats, and node list; a selected node shows that node’s data; a selected edge shows that connection (title Edge, the wire’s handle, which you can rename, payload counts, and an Enabled switch); a selected data-grid cell shows that cell’s lineage. Canvas Audit log lives on the Errors rail (severity totals under the title). Header Graph / Simple menus do not open a main-window Audit view; a leftover ?view=audit URL opens Simple and the Audit log rail. Save a panel to My Components with a name dialog (default My component). Escape closes dismissible layers one at a time, most recently opened first. Escape closes the panel when draft fields are empty after trim, including an empty search box. Committed chrome such as the workflow name does not keep the panel open. A typed draft still blocks Escape and is not cleared, even after a menu on top of it. Open menus, dialogs, and Monaco suggest or find keep Escape until they close. Auto-build lives in the workflow ⋯ menu; Run on the header is a single button.

Common input and API tiles (File Input, Email Import, REST and GraphQL inputs and APIs, Data Connector, Email Export, Save File, Subgrid, Template, AI) drop as square icon-led faces: large centered type icon and title under it. Extra status copy appears on hover. Source actions such as File Input Change/Clear, Email Import pause, Subgrid Auto Select, REST Fetch, and Data Connector Change/Clear live on the selection toolbar when the node is selected, so the default tile stays quiet.

The sidebar under Workflows → Nodes documents each node type. Representative families:

Every data node is an operation host: its behaviour is an ordered list of operations, and the engine runs them in turn. The node type’s own work (Load file, Save file, the schema mapping, the AI call) is one entry in that list, and the operations you add before or after it are Formula, Query, and Script. An Operation node is the host with no native work of its own. See Operation node.

Formula is the recommended operation: it keeps per-cell lineage and data styles through everything it writes. A query carries neither. A script receives the whole row, so its lineage is not guaranteed and styles follow the whole input row.

Everything a pipeline needs to enforce data quality before data leaves a branch is a formula statement: filter and dropRow drop rows, setProp normalises a column, groupBy with a list reducer aggregates, sortBy orders, and raiseException files an Exception Management ticket on the row. See FORMULA operation.

A workflow authored before the operation host carries its row-wise operations as they were, and they still run. Each one reads Legacy with the operation’s kind on its badge, and offers Convert with AI to rewrite it as a formula. See Operation node.

One wire into a node is that node’s rows. Several wires are named tables, named by each wire’s handle, and nothing combines them until an operation on the node says how, which a merge formula does with appendRows and find. See Wires and named tables.

An operation body does not have to sit inside the workflow file. Anywhere a value is expected, you can write a JSON Reference instead:

{ "operations": { "$ref": "operations.json#/load_file_node" } }

A path with no # names a whole file. The text after # depends on the target:

  • JSON files: a JSON Pointer (#/clean), so one file can hold several objects.
  • Formula (.ffl) files: a named export (#bar). In the file, open a fence with three backticks then ffl bar, write the statements, and close with three backticks. The name is the info-string. An unfenced .ffl is the whole-file default export.

References resolve recursively, including a pointer that lands on another reference, and the base for a relative path is the folder of the file that holds it. A reference to a file that is not JSON is replaced by that file’s text, so a formula can point at an .ffl file, a JavaScript or Python script at a .js or .py file, and a query at .sql, .jsonpath, .jsonata, or .jinja. Files come from the same Workspace; remote addresses are not fetched. An address that resolves to nothing, a duplicate $id, and a pointer loop are all run errors that name the reference.

When you write a new formula, query, or script on a Workspace workflow, Flow stores the body in a sibling folder named after the workflow file (orders.wf.json writes into orders-scripts/) and puts $ref on the operation. Existing inline strings in the workflow file stay valid. You can also point $ref at any other file in the same Workspace, including a shared lib.ffl#bar. Open an .ffl in Fontana Files to edit it as Source, the same way you edit .js.

An operation’s hash covers the text it resolved to, so editing a referenced formula re-runs that operation and the ones after it, and leaves the rest of the node alone.

Each workflow can have a dashboard: a masonry board of tiles. In edit mode you drag the right edge to set how wide that board is. Cards you stretch across every column stay the full width of that board when the window or pane resizes. See Workflow dashboards.

Configure On error on operations and nodes to raise error or raise exception. Exceptions persist for the workflow and appear on the Exception Management board. See Exception management.

PDF and office documents uploaded to File Input are parsed server-side via Docling, a shared document-parse service for all workspaces. Loading uses no AI model. The File Input Review tab shows each page beside the text and table cells found on it.

  • Open a Community Templates or Learn Fontana Workspace and choose Clone Workspace to get your own working copy. A catalog Workspace is read-only: you can open every node and see how it is built, and you run and edit it in your clone
  • Encapsulate reusable fragments as subgraphs and graft them onto larger pipelines

Open Workspaces in Flow for the hub landing. It shows a welcome (capability icons and Create workspace), then your favourite and recent workspaces, vertically centered as one stack when the pane is taller than that content. The Workspaces explorer beside it lists the workspaces you can read. You can also type a title in the header omnibar search to jump to a workflow in any workspace you can read.

Simple view lists workflows, agents, skills, tables, and dashboards under headings you can collapse. Choose the workspace name in the sidebar to open its welcome. When the workspace has a home dashboard you open it there; otherwise you create one. The namespace add menu leads with New Workflow and New Dashboard. Files from the input and output folders sit in a Data heading at the end, as ordinary file rows. A table in the tables folder is a small workflow that reads a starter sheet and writes the table when you run it. Open the table to see it in the database explorer. Open the file menu and choose Canvas to open the workflow canvas beside the explorer.

The Workspaces explorer list starts with Mine on, so you see workspace namespaces you own. With no chips on (Mine, Community, Learn, and Favourites all off), the list is every workspace you can read. Turn Mine off while other chips stay off for that same full list. Community Templates and Learn Fontana are Fontana Files Workspace namespaces. Turn on the Community or Learn chip (both start off) to show those catalogs in full, including when Mine is on. Catalog rows show only those chips. When at least one workspace is starred, a star chip sits last on that row. Turn it on with Mine, Community, or Learn to keep those lists and add starred workspaces. Turn it on alone to list only starred workspaces. Star a workspace from the row menu (Add to favourites). A filled star stays on that row while it is starred. A Community Templates or Learn Fontana Workspace is read-only: open a catalog .wf.json to see how it is built, and choose Clone Workspace on any readable row, including your own, to get a working copy. A catalog is reference content and an update returns it to the files it ships, so keep your work in your clone. The workspace row menu also has Create Restore Point, just above Delete namespace. Create Restore Point names the current workspace. Restore Point appears on that menu only when an earlier named point exists. It compares the current workspace to that point, then Restore returns the workspace to it and discards versions after it. A file that existed at that point no longer shows as deleted. A workflow on the canvas no longer shows a run from after that point. You need editor or admin. See File System. Click a .wf.json to open it on the canvas. The row menu also has View graph, which opens that file’s graph in the content pane, and Open in JSON editor when you want the source file. Choosing the file in the tree opens the canvas again. A new Workspace is ingested into the Knowledge Graph, and it stays off the Knowledge Graph list until you turn on Show Workspaces. A new Workspace starts with a .fontanaconfig.yaml that sets where new workflows, agents, skills, uploads and Save File outputs go; see Namespace configuration.

Hover a file or folder in the Workspaces explorer and choose the star beside the row menu, or Pin to header on the row menu. That item is pinned in the content header, ahead of the file name and Preview, Source, and History. A filled pin is blue. Click a pin to open that file or folder, and drag pins to change their order. Double-click a pin to set a display name for the tab; the file or folder on disk keeps its real name. Right-click a pin and choose Remove to unpin it, which also drops the display name. Choose Unpin from header or the star again to remove a pin the same way. You can also star Dashboards, Exception Management, and each dashboard or exception view under any .wf.json. Those pins are shared with everyone who can open the workspace, and they open that workflow, not whichever canvas is on screen. They stay in the header on workspace home, folders, files, and the open workflow. That header is the favourite tabs only: it does not repeat the workflow name. The same pin labels show on every Workspaces pane, including the alias you set. Opening a file or folder does not add a Workspace tab. When a workflow view other than the canvas is open and is not starred yet, that page stays as the active tab, and the star on that tab pins it. The underlined tab is the view you are on. When you open Exception Management, that pin is underlined, not the starred .wf.json, even if both are pinned. Double-click a starred workflow tab to set a display name for everyone in the workspace. A blank name restores the original label. Right-click those tabs and choose Remove to unstar them.

Each workspace on the Workspaces landing is one tile, and the stack inside it is one card per workflow that workspace holds, up to three. The front card previews its workflow the way that workflow opens: the graph for a canvas workflow, a table for one that opens as a Table, and the matching view for Exceptions, Graph, Source, and History. A workflow that has not set a view previews as its graph. A wide pane shows five tiles in a row; a narrow pane shows one. When fewer than five tiles sit in the row, each tile stays that fifth-width instead of stretching. Along the bottom of the tile, grey pills count what the workspace holds: workflows, tables, dashboards, agents, and skills. A pill appears only for a kind the workspace actually has, and reads 99+ when the workspace holds more than the tile counts. The ⋯ on the tile is the same namespace menu as the explorer row (favourite, copy, Secrets, Rename, Restore Point, Delete namespace). The star at the right of that row adds or removes the workspace from your favourites.

On the Workspaces landing, Favourite workspaces sits above Recent workspaces when you have at least one starred workspace you can still see. Each compact tile is titled with the workspace name. Click a Favourite or Recent tile to open that workspace home. A workspace that is both starred and recently opened appears in both sections. Hover Recent or Favourites and choose the trash icon beside the heading to clear that list for your account. Recent clears workflows and workspaces you opened. Favourites clears starred workspaces. Flow asks you to confirm first. Catalog panes use the larger tile layout. There is no Full / Compact density toggle. Truncated titles show the full name in a delayed tooltip. Recent workflow cards expose the same workflow ⋯ menu as on the canvas header (enable/disable and delete where permitted).

  • Published data - availability states, cell edits, and query blocks in tiles and documents
  • Security - platform security controls and SOC 2 summary
  • Compliance evidence - atomic data lineage and workflow file store
  • Data - File Input, PyAirbyte connectors, JSONL storage, REST triggers, exports
  • Governed AI - AI transforms and natural-language operation compile
  • Observability - run telemetry; canvas port audit items vs security audit ledger