Flow Anatomy
A flow is a saved document. Knowing what is inside it makes the editor predictable, explains why some things are reusable and others are not, and tells you what actually moves when you export a flow to another machine.
Purpose
This page describes the parts of a flow and how they relate. Read it before your first non-trivial flow, and again when you start splitting work into Sub Flows.
The parts
Everything on that diagram is stored in the flow. A flow you export carries all of it.
Nodes
A node is one typed step. Every node has:
| Field | Meaning |
|---|---|
id | Stable identity — never changes, survives rename |
type | What kind of node it is (action.click, detect.image, …) |
name | The label you see on the canvas — free text, rename freely |
params | The node's settings, specific to its type |
retryPolicy | Optional per-node retry, or none |
outputMappings | Optional writes from the node's output into variables |
ui | Canvas position, colour, collapsed state |
The important consequence: id is identity, name is a label. Renaming a node never breaks a connection, and two nodes may share a name.
Nodes do not own their routing. A node reports which port it left through; the flow's edges decide where that leads. This is why you can rewire a flow without touching any node's settings.
Ports
A port is a named exit on a node. Most node types have a fixed set:
success,failure,timeout,cancelled,fatalfor most nodestrue/falsefor a simple Ifmatched/notMatchedfor Memory Matchsolved/unsolved/failurefor Captcha
Three node types compute their ports from their own settings instead:
| Node | Ports depend on |
|---|---|
| If in Multiple Branches mode | one case1…caseN per branch, plus default |
| If in Text-match mode | match / noMatch |
| Find Image in Branch-by-template mode | one port per selected template, plus notFound |
| Branch (Random Branch) | branch1…branchN from the branch count |
When you lower a branch count, the trailing ports disappear and the editor prunes the orphaned edges for you.
Required versus optional ports
Some ports must be wired for the flow to validate; others may be left open on purpose.
- Required — leaving it unwired is a validation error.
successon most nodes. - Optional (lenient) — leaving it unwired is a legitimate "give up here" choice. The run ends gracefully if it is taken.
Branch is the strict case: every branch port must be connected, because the runtime may select any of them and an unwired branch would randomly dead-end the run.
Edges
An edge connects one node's port to another node's input. Edges carry:
| Field | Meaning |
|---|---|
fromNodeId + fromPort | Where it leaves |
toNodeId | Where it goes |
priority | Ordering when several edges share a port |
condition | Optional guard evaluated during resolution |
Multiple edges may leave the same port. They are tried in priority order; see Edge resolution.
Regions
A region is a named rectangle stored in percentages of the screen, not pixels.
That percentage storage is the reason a flow built on one resolution keeps working on another. A region that covers the top-right quarter of a 1280×720 screen covers the top-right quarter of a 1920×1080 screen too.
Regions have an id (identity) and a name (label). Nodes bind to the id, so renaming a region never breaks the nodes using it.
Regions can be saved to the Region Library and reused across flows.
Assets — template images
An asset is a captured template image the Find Image node matches against. Each one records:
- the image file and its checksum,
- its pixel dimensions,
- the resolution it was captured from,
- a quality assessment — entropy, edge density, dominant-colour ratio, and whether it is recommended.
The quality assessment is why the app can warn you that a template is too plain or too cluttered to match reliably.
A template that is mostly one flat colour has low entropy and will match many places on screen. A template with lots of background will stop matching the moment anything behind it changes. Capture a tight crop of the distinctive part.
Groups
A group is a named frame drawn around part of the canvas, optionally with a note, optionally collapsed.
Groups are pure authoring furniture. They have no ports, no settings, and never execute. Membership is geometric — a group contains whatever cards its frame encloses — so moving a node into a group is just a drag, and no node stores a parent id that could go stale.
A group with no members and only a note is your comment box. There is no separate comment concept to learn.
Settings
Flow settings are grouped into four blocks.
Target
Which emulator this flow expects: preferred instance name, ADB serial, expected resolution and orientation.
Defaults
The fallback values nodes use when they do not specify their own:
| Setting | Range | What it covers |
|---|---|---|
timeoutMs | 100–120000 | Default node timeout |
pollIntervalMs | 50–5000 | Default detection poll interval |
waitAfterActionMs | 0–60000 | Default settle time after an action |
imageThreshold | 0.5–0.99 | Default match confidence |
Runtime
The guardrails and execution policy:
| Setting | Range | Effect |
|---|---|---|
maxNodeExecutions | 1–100000 | Hard cap on total nodes executed |
maxRuntimeDurationMs | 1000–86400000 | Hard cap on wall-clock run time |
maxConsecutiveFailures | 1–1000 | Hard cap on failures in a row |
captureFailureScreenshots | boolean | Save a screenshot when a node fails |
inputMode | adb / native | How input is delivered |
frameSync | object | Frame freshness rules (below) |
Features
Per-flow feature gates: ocrEnabled, nativeInputEnabled, multiScaleImageDetectionEnabled.
Read Text will not run in a flow whose ocrEnabled feature gate is off. If a Read Text node fails immediately with no output, check this setting before debugging the region.
Frame synchronisation
Detection has to read a current picture of the screen, not a stale one. The frameSync settings control that:
| Setting | Range | Meaning |
|---|---|---|
requireFreshFrameAfterInput | boolean | After sending input, wait for a genuinely new frame |
maxFrameAgeMs | 16–5000 | How old a frame may be and still count as usable |
freshFrameTimeoutMs | 100–30000 | How long to wait for that fresh frame |
visualStabilityFrames | 0–10 | How many consecutive stable frames to require |
A frame is usable when now - frame.capturedAt <= maxFrameAgeMs.
This is what stops the classic automation bug where you tap a button and immediately "see" the pre-tap screen.
What identity means for reuse
Because several things are identified by stable ids rather than names, the following are all safe:
- Renaming a node, a region, or a template.
- Moving nodes around the canvas.
- Reordering branches (ports are positional but edges follow).
And the following are not:
- Deleting a template that a branch-by-template Find Image routes on — its port disappears with it.
- Lowering a branch count below a wired branch — the trailing edge is pruned.
Best practices
- Name nodes for intent, numbered in reading order.
1. Find a gem on screen,2. Swipe it to the centre. The reference automation in this documentation set does exactly this, and it makes a 13-node flow readable at a glance. - Use groups to mark phases, then collapse the ones you are not working on.
- Put reusable rectangles in the Region Library rather than redrawing them.
- Set flow defaults once instead of setting the same timeout on twenty nodes.
- Keep one flow to one job. If you cannot describe a flow in one sentence, it wants splitting into Sub Flows.
Performance notes
- Region-scoped detection is materially faster than whole-screen detection, because the matcher works on a crop. Setting a search region is the single cheapest speed-up available.
- Template count multiplies matching cost. A multi-template Find Image with 16 templates does roughly 16× the matching work of one template against the same frame.
- Flow defaults do not cost anything at run time — they are resolved at compile time.
Memory notes
- Template assets are loaded and cached for matching. A flow with many large templates has a larger working set than one with a few tight crops.
- Groups, regions and node
uidata are authoring-only. They are stored in the flow but never loaded into the runtime's hot path.
Common mistakes
| Mistake | Consequence | Fix |
|---|---|---|
| Storing coordinates as pixels in your head | Flow breaks on a different resolution | Use regions — they are percentage-based |
| Capturing a template with background | Stops matching when the background changes | Tight crop of the distinctive part |
| Relying on node names for wiring | No effect — names are labels | Wire ports; ids are identity |
Leaving ocrEnabled off | Read Text does nothing | Enable it in flow settings |
| One giant flow | Unreadable, untestable | Split into Sub Flows |
Troubleshooting
My flow validates but a branch is greyed out or missing. The port set is computed from settings for If (multi-branch / text-match), Find Image (branch-by-template) and Branch. Check the branch count or template selection.
A template stopped matching after I changed emulator resolution. Template matching is pixel-based. Either re-capture the template, or enable multi-scale detection in the flow's feature gates.
Read Text returns nothing at all.
Check features.ocrEnabled in flow settings first.
FAQ
Does renaming a flow break Sub Flow calls to it? No. Calls bind to the flow id; the name is a display cache.
Are groups exported with the flow? Yes. They are part of the flow document.
Can two nodes have the same name? Yes. Names are labels, not identity.
Where do regions live — the flow or the library? Both are possible. A rectangle drawn inline belongs to the flow; saving it to the Region Library makes it reusable across flows.
Related pages
- Execution Model — how these parts run
- Variables and Memory — the declarations block
- Sub Flows — splitting large flows
- Node Reference — every node type