Skip to content

Architecture Patterns

The structural patterns every .nbflow follows. Get these wrong and the file works, but the workflow is harder to variant, fan out, and maintain. Get them right and changes become row swaps instead of node surgery.

Pattern 1: Dynamic feeds template feeds generation

The fundamental pattern: every variable part of a prompt lives in a Dynamic Prompt node that feeds into a static template Prompt node. The template carries the universal structure (camera, lighting, framing); the dynamic carries what changes per scene or per variant.

flowchart LR
    DP[Dynamic Prompt<br/>4 dialogue rows] --> T[Static Template<br/>universal structure<br/>+ placeholders]
    PD[Dynamic Prompt<br/>per-scene physical] --> T
    T --> V[Veo3 / NanobananaAPI]
Dynamic node Carries
Dialogue The exact line being said. Single beat = 1 row. B-roll review batches = N rows.
Physical / pose Per-scene variable visuals: wardrobe, hand pose, accessories, product being held, body angle.

The static template references these as placeholders (e.g. {dialogue}, {wardrobe}, {pose}, {product_in_hand}).

Why this pattern

  • Variants become row swaps. Fan-out goes from "find-and-replace dialogue across N Prompt nodes" to "swap N rows in 1 Dynamic node."
  • Per-account customization is visible in one place. Looking at the dynamic nodes, you immediately see what's parameterized vs locked in the template.
  • Sheet sync is cleaner. Each dynamic-node row maps to a sheet row; the template stays a single cell.
  • Adding a new B-roll review or scene = adding a row, not building a new node + wiring it.

Pattern 2: One generation node per (template + media), not per row

A Dynamic Prompt node IS the fan-out mechanism. When N rows feed a template that feeds a generation node, the gen node runs ONCE PER ROW automatically — producing N outputs from a single node.

Wrong (4 redundant Veo3 nodes for 4 dialogue rows)

flowchart LR
    D[Dynamic: 4 rows] --> T[Template]
    T --> V1[Veo3 Scene 01]
    T --> V2[Veo3 Scene 02]
    T --> V3[Veo3 Scene 03]
    T --> V4[Veo3 Scene 04]
    SF[Start Frame] --> V1
    SF --> V2
    SF --> V3
    SF --> V4
    V1 --> A1[Approve]
    V2 --> A2[Approve]
    V3 --> A3[Approve]
    V4 --> A4[Approve]

This is 4 redundant Veo3 nodes doing identical work, just consuming different rows from the same dynamic. The dynamic already handles fan-out — parallel gen nodes are duplicate work.

Right (1 Veo3, dynamic fans out 4 outputs through it)

flowchart LR
    D[Dynamic: 4 rows] --> T[Template]
    T --> V[Veo3]
    SF[Start Frame] --> V
    V --> A[Approve]

One Veo3 node. The dynamic's 4 rows produce 4 separate generation runs through that single node. Each run substitutes one row's text into the placeholder and emits one set of outputs. The Approve node downstream collects all of them.

When the consolidation rule applies

The "one generation node" pattern applies when EVERY ROW shares the same template AND the same input media:

  • Same template prompt
  • Same start frame / input image (Veo3)
  • Same character reference image (NanoBanana)
  • Only the dynamic-substituted text changes per row

Then: one generation node, period.

When you must split into multiple generation nodes

You need separate generation nodes when the inputs differ structurally — not just textually:

Situation Same Veo3 / NanoAPI node?
4 scenes, all share same start frame, only dialogue differs YES — 1 Veo3
4 scenes, same start frame, but different dialogue AND different pose YES — 1 Veo3, with TWO dynamics wired in ({dialogue} and {pose} each with 4 rows). Rows pair by row index.
4 scenes, EACH has a different start frame NO — separate Veo3 per start frame
4 B-roll batches, same composition image and template, only product varies YES — 1 Veo3 / NanoAPI
2 scenes using different templates (outdoor template vs indoor template) NO — one Veo3 per template

Wiring detail: multiple dynamics feeding one template

When two or more dynamic nodes feed the same generation node (e.g. {dialogue} and {pose} each with N rows), row index is the join key:

  • Row 0 of dynamic A pairs with row 0 of dynamic B
  • Row 1 with row 1
  • etc.

All dynamic nodes feeding the same gen node MUST have the same row count.

outputCount semantics

outputCount=4 means 4 outputs PER ROW. So a 4-row dynamic feeding one Veo3 with outputCount=4 produces:

  • 4 rows × 4 outputs = 16 video clips total
  • All under that one Veo3 node
  • All collected by the downstream Approve node, which surfaces them as a gallery

Pattern 3: Dynamic vs plain Prompt

A Dynamic Prompt node has overhead. Use it only when needed.

flowchart TD
    Q[Does the prompt feed multiple rows<br/>or get fan-out?]
    Q -->|YES| D1[Dynamic - multi-row]
    Q -->|NO| Q2[Does the text get swapped<br/>during Lvl 1/2 variants?]
    Q2 -->|YES| D2[Dynamic - 1 row, variant-swap target]
    Q2 -->|NO| P[Plain Prompt]
    P --> Q3[Does the gen node need substitution<br/>from multiple source nodes?]
    Q3 -->|YES| T[Keep the template, plain Prompt feeds one slot]
    Q3 -->|NO| TD[Drop the template, plain Prompt feeds gen directly]
Use case Node type
Batch fan-out (4 rows → 4 outputs) Dynamic Prompt (multi-row)
Single dialogue line that gets swapped during Lvl ½ variants Dynamic Prompt (1 row, variant-swap target)
Per-scene image composition specifics Plain Prompt
Single-scene pose / motion description Plain Prompt
Voice character description Plain Prompt
Scene-specific framing notes Plain Prompt

Implication for templates: if a generation node has only ONE prompt source AND no {placeholder} substitution is needed, the template node is also unnecessary. The plain Prompt connects directly to the generation node's prompt input slot. Saves one template node per generation.

Pattern 4: Minimal templates beat anchored templates

Templates can be {scene} alone (no universal anchors text). Putting universal anchor text (camera/lighting/aesthetic boilerplate that "should apply to every scene") in the template dilutes the avatar reference adherence on NanoBanana 2.

The model spends attention budget on the boilerplate instead of the scene content + the wired-in reference image. Keep all per-scene specifics in the Dynamic node's JSON (or in the plain Prompt feeding the gen node directly). Leave the template minimal — ideally just the placeholder.

Apply to image gens (NanoBanana 2)

If you find yourself writing a template like:

Cinematic 9:16 vertical, natural daylight, semi-pro UGC quality. {scene}

Strip everything but {scene}. The dynamic JSON already carries those anchors per scene; restating them in the template hurts ref-image lock.

Apply to video gens (Veo3)

The universal talking-head template is the exception — it carries the speaking-pattern structure (delivery, micro-pacing) that genuinely is universal. But for B-roll / non-speaking video templates, the same minimal-template rule applies: {scene} alone, anchors in the dynamic.

Pattern 5: variableName must match the template placeholder

When a Dynamic Prompt feeds a template with {placeholder} substitution, the dynamic's variableName property is what PatchWork keys on for substitution. Not the node's title.

Template placeholder Dynamic variableName Used for
{scene} scene Per-scene image gen specifics (subject, setting, composition)
{dialogue} dialogue Per-scene Veo3 spoken line
{pose} pose Per-scene Veo3 motion / micro-gesture
{broll} broll B-roll prompt text

Convention: every Dynamic Prompt node's variableName is a SHORT lowercase identifier matching the placeholder in its target template. The node's title stays human-readable for the UI ("Dynamic: Group A — Scene 01 specifics" is fine for the title). variableName is the wiring contract.

Why this matters

If the template references {scene} and the dynamic has variableName: "Dynamic: Group A — Scene 01 specifics" (the human-readable title), substitution silently fails — the literal string {scene} is sent to NanoBanana as the prompt, NB falls back to whatever boilerplate sits in the template, and the output is unrelated to what the dynamic carries. See the pre-generation sanity check for the validation snippet that catches this class of bug.

Refactoring a legacy .nbflow

When you encounter a .nbflow that doesn't follow these patterns:

  1. Identify the parameterized parts in each Prompt node (dialogue, wardrobe, hand pose, accessory, product detail).
  2. Pull them out of the static prompt and write them into a Dynamic Prompt node.
  3. Add {var} placeholders into the template at the extraction points.
  4. Wire the dynamic node into the template node.
  5. Collapse parallel generation nodes that share the same template + same input media. The dynamic's row count IS the fan-out — one generation node per (template + media) combination, never one per row.
  6. Re-test one tab end-to-end before fan-out to the others.

Summary checklist

Every .nbflow should pass these checks:

  • Every variable part of a prompt is in a Dynamic node, not hardcoded in a template
  • No parallel generation nodes share the same template + same input media
  • No Dynamic Prompt is used when a plain Prompt would do (single-row, non-swap content)
  • No template node exists when there's only one prompt source and no substitution needed
  • Templates are minimal — anchors live in the dynamic, not in the template
  • Every Dynamic's variableName matches its target template's placeholder, as a short lowercase identifier