Flank
Responsibility
Section titled “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.
Onboarding funnels
Section titled “Onboarding funnels”Flank also provides the Sirloin-backed onboarding funnel builder. See Onboarding Funnels for RI/VI setup, strict mode, previews, publishing and metrics.
Runtime
Section titled “Runtime”Flank runs on TanStack Start / Vite 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 requires ADMIN for workflow authoring and administration.
Selected discovery/read endpoints and published execution allow CREATOR and
above, including workflow-family list/get and execution polling by ID or generation.
Controller decorators and the role guard define the complete endpoint policy.
Non-admin execution remains
limited to published MEDIA_GENERATION and CREATOR_UTILITY workflows.
Legacy, retired (removed): 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— has been fully removed. Theproto/flank/v1package, flank’s server-side engine (apps/flank/server/engine/,grpc-server.ts), and sirloin’s flank storage/MCP surfaces are deleted; flank’s server is now only the host for the editor UI and its TanStack Start server functions (apps/flank/server/entry.ts). All workflow operations go through brain’s HTTP API.
What it is used for
Section titled “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 and{{= ...}}expressions — see Workflow Template Expressions for the syntax, the editor’s completion and the publish-time checks). - 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
Section titled “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
Section titled “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.CREATOR_UTILITY— an internal helper that CREATOR-level (and above) users may run directly, e.g. the fennec workflow generator’s variables extraction. LikeGENERAL_PURPOSEit is never surfaced as a generation type and never creates a Generation; unlike it, brain lets non-admins execute the published version (sandbox/draft runs stay admin-only).
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
Section titled “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
Section titled “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.
Beside pricing, an author can state the workflow’s estimated duration (seconds) — the number sirloin serves as the user-facing generation timer instead of its per-media-type defaults. It is a display hint, never an execution budget; clearing it restores the defaults. Details in brain’s service doc.
Publishing also retires the previously published version of the same name to SUPERSEDED — full status semantics (one-published-per-name invariant, what superseded versions can still do, archive/delete rules) are documented in brain’s Lifecycle and versioning.
4. Run vs. test run (sandbox)
Section titled “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)
Section titled “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.
6. Examples: presets on top of workflows
Section titled “6. Examples: presets on top of workflows”An example is a curated preset on top of a published MEDIA_GENERATION workflow: a display media (the hero image/video users browse on the explore page) plus the exact inputs that produced it. Selecting an example in brisket opens the workflow’s generation form pre-configured with those inputs, so the user starts from a known-good result instead of a blank form.
Coexistence note: workflow-backed examples currently live side by side with legacy examples (the pre-T-Bone kind with a single free-text prompt and client-side pricing). The same brain endpoint returns both; brisket branches per kind. Legacy examples keep working unchanged until the retroactive migration (FOXY-427) retires that path.
How to create one (promotion-based — you never author an example from scratch):
- Generate the source media. Run the workflow (in fennec’s Media Generator, or as a real user generation) until you have a result worth showcasing. The example will carry a copy of the inputs that execution actually ran with, so the source generation is the authoring step.
- Promote it in fennec’s Generation Gallery. Find the generation and use Add to examples. The promote flow copies the workflow name (+ version, kept as provenance only), the trigger inputs, and the display media, and lets you set the example type (basic / signature / video) and tags. Explicitness is inherited from the source workflow’s variant — an SFW workflow cannot produce an NSFW example.
- Choose the anchoring — workflow or family. If the source workflow is a member of a generation family, the promote flow offers a choice:
- Pin to this workflow — the example always runs exactly this workflow. The FE form renders this workflow’s contract, no axis pickers.
- Expose the family — the example carries the
family_key. The FE form additionally renders the family’s axis pickers (e.g. model, 18+), defaulted to the source variant’s tuple; the user can toggle to sibling variants, and the resolved variant drives the field definitions, pricing, and dispatch.
- Configure per-input visibility (optional). Each workflow has a Workflow Example Config (fennec → workflow example config page) with per-input
visible/editableflags. This config is per workflow, not per example — for a family-backed example, the entry matching the variant the user’s axes resolve to governs the form. A workflow with no config saved is fully visible + editable.
What the flags do on the brisket form:
visible: false— the field is removed from the rendered form entirely. Its authored value is treated as IP: sirloin strips it from the example payload before it reaches the browser and re-injects it server-side at generate time (keyed byexample_id), so the value never round-trips through the client.editable: false— the field renders locked (disabled) with the authored value prefilled; user edits are ignored and the server enforces the authored value at dispatch.- Hidden or locked inputs are excluded from the required-field gate — the server owns their values, so they never block the generate button.
- Everything else renders as a normal contract-driven field, prefilled with the example’s authored value where one exists.
Because examples resolve latest-published by workflow name (the stored version is provenance, not a pin), a republish changes what an example runs — the additive-only contract compatibility check at publish time is what keeps existing examples from breaking.
Primary Source Paths
Section titled “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
Section titled “Related Flows”Related Standards
Section titled “Related Standards”Local Commands
Section titled “Local Commands”cd apps/flank && pnpm typecheckcd apps/flank && pnpm lintcd apps/flank && pnpm testcd apps/flank && pnpm dev(Vite dev server on port 3100)