Visual flow editors are wonderful right up until they aren’t. Wiring twelve nodes by hand is faster than typing. Wiring a hundred, or refactoring a subgraph, or diffing what changed between two versions — that’s where a canvas starts to fight you. So we gave every board a second face: FlowScript, a textual representation you can read, edit, and apply straight back onto the graph.
Open the FlowScript panel in the workflow view and your flow is suddenly source code. Edit the text, press ⌘S, and the changes reconcile back onto your nodes. It’s the same board, seen as code.
What it looks like
FlowScript reads like TypeScript, because that’s the flavor your muscle memory already has. Structs are interfaces, variables are typed const/let, and node calls look like function calls with a named-argument object:
interface NodeDBConnection {
cache_key: string;
}
const embeddingModel: CachedEmbeddingModel = {"cache_key":""}
@category("Sub-Agents/Librarian")
const files: NodeDBConnection = {"cache_key":""}Logic reads like code, too. Here’s a real function from a test board — a for-loop over a controlForEach node, registering database tables into a query session:
queryItems(tables: string[], query: string, payload: Struct) {
const createDataFusionSession = dfCreateSession({ sessionName: "default", batchSize: 8192 })
for (const forEach of controlForEach({ array: tables })) {
const openDatabase = openLocalDb({ name: forEach.value, userScoped: true, batchSize: 1000 })
dfRegisterLance({ session: createDataFusionSession.session, database: openDatabase.database, tableName: forEach.value })
}
const sqlQuery = dfSqlQuery({ session: createDataFusionSession.session, query: query })
return sqlQuery.rows
}Node outputs are just field access (forEach.value, sqlQuery.rows). Branching nodes open a block per execution pin. And there’s syntactic sugar for the thing everyone does constantly — building up a struct field by field:
let record = structSet({ structIn: {}, field: "files", value: listPaths.paths }).structOut
record.folders = listFolders.folders // desugars to another structSet on applyThat record.folders = … line is real FlowScript. On apply it expands back into a structSet node and rebinds the variable — so the text stays clean while the graph stays correct.
How it stays honest: three invariants
A text ⇄ graph round-trip is easy to get subtly wrong, so FlowScript is built on three hard rules.
1. Every node has an anchor. In the editable view, each statement carries a trailing comment that is the node’s stable identity:
const now = utilsDatetimeNow() //@n:j37yxnwykc08y10reikmija1
attachedFiles = {} //@n:xal689cwuihke7lvj801euk9Those //@n: anchors (and //@v: for variables, //@l: for layers) are how the reconciler knows an edited line is the same node rather than a new one. Keep the anchor, and you’re editing; drop it, and you’re deleting — which brings us to rule two.
2. Deletions are guarded. Applying FlowScript defaults to no deletions. If your edit would remove existing nodes — because an anchor went missing, say — the apply is blocked and nothing changes:
if !allow_deletions {
let destructive = destructive_flowscript_command_summaries(&reconcile.commands);
if !destructive.is_empty() {
// block: return zero commands, surface a diagnostic
}
}You have to explicitly ask to delete. An incomplete edit fails safe.
3. Secrets never enter the text. A secret variable renders its @secret decorator but never its value. Rendered FlowScript gets shown in editors, copied to clipboards, and sent to LLMs — so secret values are stripped before any of that can happen. On the web, the board is filtered for secrets before it’s ever serialized.
A real editor, not a textarea
The FlowScript panel is Monaco — the editor from VS Code — with a real language behind it. You get syntax highlighting, autocomplete that knows your board’s variables and functions and the shape of every catalog node, dot-completion that follows a variable’s type to offer its output pins and struct fields, hover, and signature help. Completions are typed against embedded declaration files, which is how the editor solves what the code calls “the ~1,200-function problem” — knowing the exact signature of every node in the catalog.
Linting runs on two levels. There’s instant, structural checking in the browser as you type — unknown arguments, type mismatches, misused multi-output results. And in the desktop studio there’s an authoritative Rust-parser lint via a dedicated command, debounced so it’s safe to run on every keystroke, that catches anything the quick check can’t.
The architecture, briefly
The pipeline is Board ⇄ BoardAst ⇄ Text. The “language half” — the model, the renderer, the parser, the linter, the signatures — lives in a deliberately dependency-light Rust crate, flow-like-ast, that doesn’t even depend on the flow engine, so it stays fast and could one day back a standalone language server. The “board half” — lowering a board into the AST, reconciling text into edit commands, and applying them — lives in the core. The whole round-trip is guarded by snapshot tests that render every fixture board to text and assert it matches byte-for-byte.
Why it matters beyond the editor
Here’s the part that ties the last few weeks together: FlowScript is how FlowPilot edits your flows. When the AI changes a workflow, it doesn’t manipulate nodes one at a time — it reads the current FlowScript, writes a new version, and applies it as one reconciled, undoable batch. The same anchors that keep your edits safe keep the model’s edits safe. The same deletion guard that protects you from a fat-fingered delete protects you from an overeager model.
We’ll be honest about the edges: FlowScript is a projection of a board, not a standalone programming language — the flow engine still does the executing — and a few round-trip corners aren’t perfect yet (some multi-pin array arguments, comment preservation). But for reading a flow, refactoring it, reviewing what changed, and letting an AI edit it safely, it’s already the fastest way in.
Open a board, open the FlowScript panel, and read your work as code.
Get automation insights delivered
Sign up for our newsletter to receive the latest updates on Flow-Like, automation best practices, and industry insights. No spam — just valuable content.
