Skip to content

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/flank or fold into fennec) is an open decision. Today it lives in apps/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 over FlankExecutionService — 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:

  • output shapes an arbitrary result map (output_mapping: key → value). It’s generic plumbing — useful for GENERAL_PURPOSE workflows or subworkflows that hand structured data back to a caller. It does not create servable media.
  • media_output declares the workflow’s media result. It maps the produced asset onto the well-known fields a Generation needs — media_type (required: IMAGE / VIDEO / CAROUSEL) plus image_path / video_path / image_paths. On completion, brain’s GenerationMappingService reads this node and writes those values onto the Generation row, 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 (real media_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_GENERATION workflows. 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

Local Commands

  • cd apps/flank && pnpm typecheck
  • cd apps/flank && pnpm lint
  • cd apps/flank && pnpm test
  • cd apps/flank && pnpm dev (Vinxi dev server on port 3100)