# Flows reference

How a Borago Board flow works. How its steps are analysed, where code runs, the guards, every schedule, task hooks, run records.

A flow is a few steps written in plain language, joined in order, that run on a schedule, when a task reaches a column, or when **Run** is pressed. When the flow is saved, a model reads the steps once and decides which are ordinary logic, writing the code for those, and which need a model each time. The code runs inside the project's container, never inside the app. Flows belong to a project and are reached from the project's **Flows** button.

## Why a flow is not a task

A task pays for a full agent run and does the work slightly differently each time. Most of what people want automated is one model call and a few lines of logic, so a flow splits the work by kind.

| Kind of step | What it is | Cost per run |
|---|---|---|
| Code | Deterministic. Reshaping data, arithmetic, dates, an HTTP call, reading a file, running a command | Nothing. The code is written once at analysis and re-runs identically for ever |
| AI | Needs judgement. Summarising, classifying, drafting, deciding on content | One model call |
| Data | Neither. A constant, a URL, some reference text | Nothing |

A flow is not a general workflow engine. There are no loops, no conditional branches, no parallel steps and no sub-flows. A generated code step cannot be edited by hand. A flow cannot be shared between projects.

## The analysis

| Property | Value |
|---|---|
| When it runs | In the background, after a save that changed the steps, on the desktop app. The edit lands at once and the editor follows the analysis live |
| What it does | Reads the whole flow and decides, step by step, whether a step can run as plain code, needs a model, or only carries data, and writes the code for the steps that can run as code |
| The role it uses | The project's **Flows** role in the project's AI Roles tab. Unset, its service is the pre-run review role's, then the task runner's. Its model is the review role's only when both are on that service, otherwise that service's cheap default. It is the only role that writes code which later executes |
| A step the model did not mention | Becomes an inert data step, with a warning |
| A failed analysis | Never blocks the save. The flow stays saved and usable, and the reason is shown on the flow |
| Staleness | The analysis remembers the steps it was made from. Editing a step's text or label makes it stale. Moving a card on the canvas does not |
| The buttons | **Analyse** when the flow is unanalysed or stale, **Re-analyse** otherwise. A flow edited in the browser or on the phone is saved without analysis and reads **Edited since analysis** until it is analysed on the desktop |

The list page shows one of five badges per flow: **Not analysed yet**, an analysing badge while the model reads it, **Analysis failed**, **Edited since analysis** and **Ready**. Not analysed is deliberately not the same as analysed and found nothing to do.

## Where generated code runs

Every code step runs inside the project's Docker container, through the same command path the agent's own commands take. The code was written by a model from a sentence a person typed, so it is never run in the app's own process, where it would have the person's files, database and keychain. The project's environment variables are available to it at that moment, exactly as for a task run, the terminal and a script. If the project has no running container, the code step fails and the run fails with it.

## Guards

A flow is the first thing in the app that runs with nobody watching, so the guards are the design.

| Guard | Rule |
|---|---|
| No overlapping runs | A flow that is already running does not start again. A 15-minute flow that takes 20 minutes never accumulates workers |
| A changed flow must be re-analysed | A stale analysis blocks the run. The editor greys out **Run** on the keystroke and the desktop refuses it, because code written for text that has since changed is the failure that looks like success |
| Per-step ceiling | 120 seconds. A hung step does not hold a scheduled run open all night |
| Per-run ceiling | 600 seconds, after which the run is stopped |
| Due flows start in turn | A flow starts within a minute of its due time. When several are due at once they start a few at a time |
| Off until switched on | A new flow, and a copy of a flow, arrives switched off. It runs by itself only once **On** is ticked on the list page |
| Missed windows are never caught up | A laptop shut for three days runs an hourly flow once, at the next due time, not 72 times. The one exception is the every-N-minutes schedule, which after a gap longer than one period runs once, now |
| A failed step skips what depends on it | Steps that never depended on the failed one still run. The dependent ones are recorded as skipped, which reads differently from failed |
| No steps, or an invalid graph | The run is refused and recorded as blocked |

A refusal produces a run record marked blocked rather than a bare error, because a scheduled flow that quietly declines to run is the thing nobody can debug.

## Schedules

The trigger is chosen on the flow's page under **When it runs**. Quick choices come first, then **Or set it exactly** with a kind and its fields.

| Quick choice | What it sets |
|---|---|
| **Manually** | Only when **Run** is pressed |
| **Every 15 minutes** | Every 15 minutes |
| **Hourly** | Every hour at minute 0 |
| **Daily at 09:00** | Every day at 09:00 |
| **Weekly on Monday** | Every Monday at 09:00 |
| **Monthly on the 1st** | The first day of every month at 09:00 |
| **When any task is Done** | When any task in the project reaches Done |
| **When any task is Blocked** | When any task in the project reaches Blocked |

| Exact kind | Fields |
|---|---|
| **Only when I press Run** | None |
| **Every N minutes** | **Every (minutes)**, at least 1 |
| **Every hour** | **Minute** |
| **Every day** | **Hour** and **Minute** |
| **Every week** | **Day** (the weekday), **Hour** and **Minute** |
| **Every month** | **Day of the month (1-28)**, **Hour** and **Minute** |
| **When a task moves to a column** | **Column** (any of the board's six columns) and **Which task**, either **Any task in this project** or one task named by its number and title |

Times are the local time of the machine that runs the flow, because that machine is the desktop and the person who wrote "every day at 09:00" is sitting at it. What is stored is a real instant, and a daylight-saving change lands on the right wall-clock time. A machine that changes time zone re-derives its next run from the new local calendar. A task-column trigger and the manual trigger have no next time on the clock. The list page shows the next due time beside a switched-on flow.

## Task hooks

A task reaching a column can start a flow from two directions, and both are one mechanism.

| Direction | Where it is set | What starts |
|---|---|---|
| The flow watches the board | The flow's trigger **When a task moves to a column** | Every switched-on flow watching that column, narrowed to one task when **Which task** names one |
| The task names its hooks | The task's hooks field, on the create form, the inline editor and the task's edit mode. Collapsed by default | The flows and project scripts the task names for Done or Blocked. A task's own hooks are limited to those two columns and to 10 entries per column |

| Rule | Detail |
|---|---|
| What counts as a move | A person dragging the card, and a run ending. Both count the same way |
| No change, no hook | Saving a task's title while it sits in Done fires nothing |
| Once per flow | A flow both watching the column and named by the task runs once |
| Only where it can run | Hooks fire on the desktop, which has the model and the container. The cloud applying a synced status change starts nothing |
| Hooks follow the task | The task's hooks sync with the task. A named flow is identified by an id that is the same on every device |
| A deleted script | Its entry stays on the task, so the hook reads as pointing at a removed script rather than as never having existed |

## What a run records

| Field | Value |
|---|---|
| Status | Running, ok, error, blocked or canceled. Blocked means the run could not start, and is a different thing from a step failing |
| What started it | Manual, or schedule. A task hook counts as schedule |
| Per step | Its label, its kind, ok or error or skipped, how long it took, its output and any error |
| The output | The last step's output |
| The error | The first failing step's error, or the reason the run was refused |
| Cost | The estimated cost of the run's model calls |
| History | The newest 50 runs per flow. Older ones are deleted for good |

Run records stay on the machine that produced them. The flow itself syncs, so every device sees the steps, the schedule, the switch and the last outcome with its time. Only the transcripts stay local. That is the one place a flow differs from a task, whose runs do sync, and the reason is that a scheduler produces rows nobody asked for. **Stop** on the flow's page ends a running flow, which is recorded as canceled.

## The failure notification

A run that ends in error or blocked raises a Flow failed notification, titled with the flow's name and either "failed" or "could not run", with the error as its body. A click opens the project's flows page. It is its own kind because a flow has no other surface while it fails every hour with nobody watching. See [Notifications reference](/docs/reference/notifications/).

## Which model a flow uses

| What | Model |
|---|---|
| The analysis | The project's **Flows** role. Unset, the service is the pre-run review role's, then the task runner's. The model is the review role's only when both are on that service, otherwise that service's cheap default |
| An AI step | The same, unless the step names its own service and model in its **Project default** and **Service default** pickers. A step that names a service and no model gets that service's default model |
| A model from the wrong provider on a step | Discarded rather than sent, so a stale override left behind by a service change cannot fail the step on every tick |

## Surfaces

| Surface | Can |
|---|---|
| Desktop app | Everything. Create, edit, analyse, switch on, run, stop, read history, delete |
| Browser on the local network | The same, since it is served by the desktop |
| Cloud browser and phone | Create, edit, switch on and off, copy, delete, and read the schedule and the last outcome. **Run** and **Analyse** are shown and disabled with the reason, since both need the container and the model on the desktop. A run is not relayed to the desktop |

Related: [Flows](/docs/guides/flows/), [A nightly job with a flow](/docs/tutorials/nightly-flow/), [Notifications reference](/docs/reference/notifications/), [Containers](/docs/reference/containers/), [The terminal, scripts and the file browser](/docs/guides/terminal-scripts-and-files/).
