Kolbo.AIKolbo.AI Docs
Kolbo Code

Visual DNA & @ References

How Visual DNA binding works in prompts, and how to use @ references to control which asset plays which role in a generation.

Visual DNA is how Kolbo keeps a character, style, product, or scene looking the same across many generations. The agent creates a DNA profile from a few reference images, gives it a name, and then references it by @<name> in future prompts.

This page explains how the binding actually works on the kolbo-api side, so the agent (and you) can use it precisely.

How @<name> is resolved

When you submit a prompt that contains @<name> together with a visual_dna_ids array, the backend:

  1. Parses every @<token> out of the prompt.
  2. Looks up the matching Visual DNA by its name field (case-insensitive) inside your library (your personal DNAs plus any shared ones).
  3. Replaces the @<token> in the prompt with the DNA's stored systemPrompt — the full character/style description that was baked in when the DNA was created.
  4. Sends the rewritten prompt + the DNA's reference images to the generation model.

That last step is the important one: @<name> is the mechanism that injects the DNA's description into the prompt. If the prompt has no @<name>, the DNA's systemPrompt is never injected — the visual_dna_ids slot becomes a near-no-op, and the model has to guess what you wanted the DNA to control.

The cardinal rule

Whenever you pass a visual_dna_ids array — even just one id — the prompt MUST reference each DNA by @<name>.

Wrong:

prompt: "אסתר wearing a gold necklace, half-body portrait"
visual_dna_ids: ["vdna_abc"]   // a DNA whose name is "esther_model"

The engine sees plain Hebrew text "אסתר" and has no idea it should bind to vdna_abc. The DNA's systemPrompt is never injected.

Right:

prompt: "@esther_model wearing a gold necklace, half-body portrait"
visual_dna_ids: ["vdna_abc"]   // esther_model

The engine swaps @esther_model for the DNA's full systemPrompt and the character stays consistent across generations.

Naming Visual DNAs

name is matched case-insensitively against the literal @<token> in the prompt.

Strong recommendation: pick a single-token, lowercase, no-space name. esther_model, dana, tokyo_neon, brand_red — these are the names that travel cleanly across multilingual prompts and never need defensive punctuation. Reason: the parser stops the @<token> match at the first space (also at .,!? punctuation, see Termination rules), so a name with spaces forces every prompt that mentions it to either add explicit punctuation or eat the rest of the sentence as part of the lookup. The fallback truncates progressively, but the failure mode is silent — a mis-bound name simply drops, and the DNA never injects.

Other rules:

  • Any-language characters are allowed (name: "אסתר" → write @אסתר). Whatever string you stored is the string you must type.
  • Spaces are technically permitted but discouraged — use _ between words (old_town, not Old Town). The Kolbo Code agent (and the create_visual_dna MCP tool's description) collapses user-provided multi-word names to underscores automatically for this reason.
  • Names cannot equal Image1, Video2, etc. — those are reserved (see below).
  • Max 100 characters.

Termination rules

The parser keeps consuming words after @ until it hits any of:

  • punctuation ., ,, !, or ?
  • two or more spaces in a row
  • another @
  • end of the prompt

So @Sarah Johnson, wearing red matches the DNA named Sarah Johnson. A prompt like @Maya holds a hammer would try to look up Maya holds a hammer, fail, and then fall back to progressively shorter prefixes (Maya holds a, Maya holds, Maya) until one matches.

That fallback is best-effort — write punctuation or a double-space between the @name and the next word whenever you can:

@maya, wearing a leather jacket, walks through @tokyo_street.

@Image1 / @Video1 / @Audio1 — positional reference tags

A separate namespace, used for plain reference assets that you pass in reference_images, reference_videos, reference_audio, or source_images / source_videos. These are positional:

  • @Image1 = reference_images[0]
  • @Image2 = reference_images[1]
  • @Video1 = reference_videos[0]
  • @Audio1 = reference_audio[0]
  • …and so on.

The parser excludes @Image\d+, @Video\d+, @Audio\d+ from Visual DNA lookup, so they never collide with a DNA name. Validation on Visual DNA creation also rejects names matching Image\d+.

Mixing Visual DNAs and positional references

The two namespaces compose freely in the same prompt:

prompt: "@maya, wearing the leather jacket from @Image1,
         walks through the coffee shop in @Image2"
visual_dna_ids:    ["vdna_8f2c"]          // maya
reference_images:  ["jacket.jpg",         // @Image1
                    "coffee_shop.jpg"]    // @Image2

Here @maya injects the DNA's character description; @Image1 / @Image2 tell the engine which uploaded image is the wardrobe vs. the location.

For video/audio references the same pattern applies — name the asset's role explicitly:

prompt: "Lipsync @Video1's speaker to the dialogue track @Audio1,
         keep the room tone of @Video1; ignore @Audio2 (music bed)."
reference_videos: ["takes/take-03.mp4"]   // @Video1
reference_audio:  ["dialogue.wav",        // @Audio1
                   "music_bed.mp3"]       // @Audio2

{USER_PROMPT} placeholder

A DNA's systemPrompt may contain the literal placeholder {USER_PROMPT}. When the backend replaces @<name> with the systemPrompt, it substitutes {USER_PROMPT} with the user intent — i.e. the original prompt with all @mentions stripped. This lets a DNA template say:

<character description> ... {USER_PROMPT} ... <consistent style notes>

…and the user's actual instruction ("walking through the rain at golden hour") slots into the middle. You normally don't need to think about this; it's worth knowing only because it explains why prompts feel like they "come out longer" when a DNA is bound.

Unresolved mentions

If a @<name> doesn't match any DNA in your library (and the truncation fallback also fails), the mention is silently removed from the prompt. This protects against typos producing garbage prompts, but it means a mistyped name is a silent failure mode — the DNA simply doesn't apply.

Verify before generating: call list_visual_dnas, confirm the DNA exists, copy the exact name into the prompt.

Common failure modes

SymptomCauseFix
DNA passed but character looks genericPrompt referenced the character in plain text, never wrote @<name>Replace the plain-text mention with @<name>
@Maya resolves to the wrong characterTwo DNAs share a name; lookup is ambiguousRename one DNA via create_visual_dna with a more specific name
@<name> followed by text gets over-capturedNo punctuation or double-space between @<name> and the next wordAdd a comma, period, or two spaces — @maya, walks…
Two reference images blend togetherPrompt didn't name them with @Image1 / @Image2Tag each asset's role explicitly
Visual DNA "doesn't apply" silentlyMention was unresolved (typo or DNA not in library) and got strippedlist_visual_dnas first; copy the exact name

When to use Visual DNA

  • Same character across multiple images or a campaign → generate_image / generate_creative_director with visual_dna_ids.
  • Animating a character with elements models (Seedance 2, Kling O3 Reference, Grok Imagine, Veo 3.1, …) → generate_elements with visual_dna_ids. Note: generate_video does not apply DNA — use generate_elements for character-consistent video.
  • Consistent brand style across a campaign → generate_creative_director with visual_dna_ids + a moodboard.
  • Place a character in a specific environment → create one character DNA and one environment DNA; reference both with @<name> syntax.

Reference

The parser lives in kolbo-api/src/utils/visualDnaMentionParser.js — see parseVisualDnaMentions, resolveVisualDnaMentions, and replaceVisualDnaMentions for the exact behavior.