Skip to content
Kelly Ferrone edited this page Sep 12, 2026 · 2 revisions

Flows

A flow is a list of tool calls saved under a name. run_flow replays the whole list server-side, in order, in the browser you already have.

The saving is not bandwidth. It is decisions: a twelve-step form becomes one call instead of twelve, and it replays a sequence somebody already got right instead of re-deriving the selectors every time.

Flows are off unless the server has somewhere to put them. Set FLOW_DATA_DIR — see Deployment.

A step is a tool call

name: sign-up
description: Create an account on the demo site
steps:
- tool: navigate
  params:
    url: https://demo.example.com/join
- tool: write
  id: email
  params:
    css: "#email"
    text: a@example.com
- tool: interact
  params:
    action: click
    css: button[type=submit]
- tool: extract
  params:
    css: h1
  return: true

params is exactly the arguments you would pass that tool directly. Nothing is renamed and nothing is added. A step may also carry:

Key Does
id a name for the step, shown in the report
note a comment for the next reader
onError abort (default) or continue past a failure
return include this step's full result in the report

Not steps: open_session and end_browser. A flow runs in the browser the caller already holds, which is what lets the same flow run on Firefox unedited. Never put session_id in a step either — the run supplies it.

flow_schema lists every tool that may be a step and what each takes. It is derived from the live tools, so it cannot describe a step that would not run. save_flow validates against the same schemas and reports every problem at once, so a broken flow is refused when you save it rather than halfway through a form.

Parameters: what varies between runs

Declare them as JSON Schema and point a step at one with value_from:

parameters:
  type: object
  required: [email]
  properties:
    email: {type: string}
steps:
- tool: write
  params:
    css: "#email"
    value_from:
      param: email

Then run_flow(name="sign-up", params={"email": "a@example.com"}).

There is no templating. Nothing like {{email}} is ever substituted into a string: a value arrives through value_from or not at all, and value_from names exactly one source.

An action opens exactly one argument to a binding — the one the step is about:

Action Fills
write text
navigate url
upload_file path

A binding satisfies that argument, so giving it literally as well is refused rather than one of the two quietly winning.

Mark a parameter writeOnly: true when the caller supplies it but it must not come back out — it is typed as usual and hidden from the report.

Typing a secret you are never shown

For a password, do not use a parameter at all. Name a secret where the value would go, and the server reads it and types it:

- tool: write
  params:
    css: "#password"
    value_from:
      secret:
        name: the-internet
        key: password

The value never passes through the caller, and the step's line in the report reads text=<hidden> rather than the characters. A parameter shows its length instead — text=18 chars — which is the difference between a value that is merely varying and one that is guarded. See Secrets.

Whose flows you see

This depends on your session name, and it is not guessable from any schema.

You are You save into You can run
named (?session= / X-Session-Key) your own library yours, plus the shared library
stdio its own library its own, plus the shared library
unnamed, or named global nowhere — you cannot save the shared library

global is read-only. Every session lists and runs what is in it and no session may change it: save_flow and delete_flow refuse. That is not tidiness — the shared library is live, so a flow one caller rewrote or deleted would change or vanish underneath another part-way through running it. Only an operator moves a flow into global, from the admin UI.

So name your session before you save anything. Without a name you have no library of your own, and save_flow says so rather than writing somewhere you would never look again.

Where a name exists in both, yours wins, and list_flows marks each entry shared: true or false so you can tell which one will run.

Running one

Open a browser first — a flow never opens one. To run the same flow on Firefox, open_session(browser="firefox") and run it again, unchanged.

The report has one line per step plus where the browser ended up. It stops at the first failing step unless that step says onError: continue, and says which step stopped it, the error, and the page it was on — so check that page with extract before deciding the selector is wrong.

A run answers with the steps that said they were the answer. Mark each one return: true — any number may, and nothing else in the report carries a result. A flow whose point is its closing extract needs the flag on that extract; without it the step is reported as having run and its result is discarded.

Running a flow you cannot edit — one from the shared library — pass verbose and read every step instead.

url_redacted: true means the page it ended on carried a guarded value, so the URL shown is scrubbed and is not a real address. Do not navigate back to it.


Actions · Files · Secrets · Sessions

Clone this wiki locally