Flank
Flank
Responsibility
Flank is the visual workflow editor for the brain-owned workflow engine. It is the React/ReactFlow canvas where an admin builds and edits the node graphs that brain executes — adding nodes, wiring edges, configuring node inputs, running test executions, and inspecting per-node logs.
Flank no longer owns workflow data or execution. Under T-Bone, the workflow engine and storage moved into brain: brain holds workflows, subworkflows, contracts, adapters, and executions, and serves them over an HTTP API. Flank is re-pointed to read and write brain over HTTP — it is a thin authoring surface on top of brain, alongside the other surfaces (fennec, brisket, the agent tools).
The editor’s long-term home (remain in
apps/flankor fold into fennec) is an open decision. Today it lives inapps/flank/app.
Runtime
Flank runs on TanStack Start / Vinxi with ReactFlow in the UI (port 3100). Its TanStack Start server functions call brain’s HTTP API for all workflow data — list/get/save workflows, fetch a workflow contract, execute or sandbox a workflow, and read executions with their per-node traces. The brain client lives in apps/flank/app/lib/brain-http-client.ts.
Clerk provides authentication; brain authorizes workflow operations behind the ADMIN role.
Legacy, being retired: the original model — workflow/adapter/secret/execution records stored in sirloin via the gRPC
FlankStorageService, and executions run by flank’s own in-process engine overFlankExecutionService— is superseded by brain. Where those gRPC paths still exist they are transitional; new work targets brain’s HTTP API.TODO(@law): remove the retired gRPC storage/execution wiring once no flow depends on it.
What it is used for
- Authoring workflows and subworkflows: drop nodes from the palette, connect them into a graph, and configure each node’s inputs (including
{{nodes.X}}/{{trigger.X}}references). - Multi-node editing: select, copy/cut, and paste a chunk of a graph across workflows.
- Testing: run a draft as a sandbox execution (no published version required) and inspect the per-node execution trace (status, timing, inputs, outputs, errors).
- Publishing: promote a draft to a numbered, immutable version that external executors may use.
See the T-Bone glossary for the canonical definitions of Workflow, Subworkflow, Node, Engine, Execution, and Contract.
Getting started: a media-generation workflow
This is the end-to-end mental model for building a workflow that produces a piece of media (an image, a video, or a carousel) a user can generate.
1. Workflow purpose — what the workflow is for
Every workflow has a purpose:
MEDIA_GENERATION— the workflow is a user-facing generation type. Only these are picked up by sirloin’s generation catalog and shown to end users in brisket; each one becomes a “thing a user can generate.”GENERAL_PURPOSE(the default) — an internal/utility workflow. It runs and is editable, but it is not surfaced as a generation type to users. Use it for building blocks and experiments.
So the first decision is: set the purpose to MEDIA_GENERATION if you want this flow to appear as a generation option.
2. output vs media_output — how the result leaves the graph
Both are terminal nodes, but they do very different things:
outputshapes an arbitrary result map (output_mapping: key → value). It’s generic plumbing — useful forGENERAL_PURPOSEworkflows or subworkflows that hand structured data back to a caller. It does not create servable media.media_outputdeclares the workflow’s media result. It maps the produced asset onto the well-known fields a Generation needs —media_type(required:IMAGE/VIDEO/CAROUSEL) plusimage_path/video_path/image_paths. On completion, brain’sGenerationMappingServicereads this node and writes those values onto theGenerationrow, turning the result into a stored, servable asset.
A media-generation workflow must end in a media_output node. Without one, the workflow exposes no image/video output — sirloin can’t derive a media contract for it, and it won’t work as a generation type. Reach for media_output for anything user-facing; use plain output only when you genuinely need a non-media data shape.
3. Drafts, publishing, and versions
A workflow is a draft while you edit it. Drafts are fully runnable from flank and fennec for testing, but they are invisible to external services (sirloin/brisket can’t see them).
Publishing freezes the current graph as a numbered, immutable version. External executors run the latest published version by workflow name. Editing after publish creates a new draft on top — the published version keeps serving until you publish again, and a republish (e.g. a pricing change) affects only new executions, never in-flight or past ones. Subworkflows follow the same model: a workflow pins a specific subworkflow version, so updating a subworkflow never silently changes already-published workflows.
4. Run vs. test run (sandbox)
There are two ways to execute, and the difference matters:
- Run (execute) — runs the latest published version by name. This is the real thing: it produces exactly one
Generation(realmedia_id, persisted media, moderation enforced). This is the path sirloin uses for actual user generations. - Test run (sandbox) — runs a draft by id, at any publish status, without Generation wiring. It executes the graph so you can inspect the per-node trace, but by default it creates no Generation and persists no media (you can opt in with a “create generation” flag). Sandbox is how you iterate on a draft before publishing — fast feedback, no side effects, no charge.
Typical loop: build the draft → sandbox it until the graph and outputs look right → publish a version → it goes live for run.
5. Impact on fennec (and brisket)
The same workflow contract brain serves drives the admin and user surfaces, so what you build here shows up downstream:
- fennec (admin) renders the workflow’s input form from the contract, and — unlike brisket — can execute DRAFT workflows (the sandbox path) for pre-publish testing, supply internal-only inputs (input gallery, character reference picker), and inspect per-node execution logs. Moderation outcomes from the
media_output/inference path surface in fennec’s moderation dashboard. - brisket (user-facing) only ever sees published
MEDIA_GENERATIONworkflows. It renders the form and live pricing from the same contract; sirloin re-evaluates pricing and is the charge authority.
Practically: a flow isn’t “real” for users until it is MEDIA_GENERATION, ends in media_output, and has a published version — until then it lives only in the flank/fennec authoring-and-testing world.
Primary Source Paths
apps/flank/app/routes/— editor and execution routes (e.g.workflows.$id.tsx,executions.$id.tsx)apps/flank/app/components/flow/— the ReactFlow editor (FlowEditor, NodePalette, NodeConfigPanel, nodes)apps/flank/app/lib/— client utilities and the brain HTTP client / server functions
Related Flows
Related Standards
Local Commands
cd apps/flank && pnpm typecheckcd apps/flank && pnpm lintcd apps/flank && pnpm testcd apps/flank && pnpm dev(Vinxi dev server on port 3100)