The brief that gets the right output. Nine fields, every model.

The model is rarely the problem. Veo, Midjourney, Runway, Sora. They are all good enough that the first thirty drafts of any project look polished. The trouble starts on draft thirty-one, when you need the same blade in a different alley, with the same hilt geometry, with the light coming from the same direction.

That kind of consistency is what separates a generation that ships from one that gets re-run six times. Consistency comes from the brief, not the model. We learned this the slow way, project by project, regeneration by regeneration. Every image and video we hand to a model now passes through the same nine-field template. It takes ten minutes to fill in. It saves hours on every shot.

Shape

A brief is a typed call

In our studio, a brief looks like this:

brief("sparkblade_hero_01")
  .subject ({ source: "asset://sparkblade/source.png", geometry: "exact" })
  .world   ("cyberpunk_alley.rain.v2")
  .light   ({ key: "magenta_left", rim: "cyan_right", top: null })
  .camera  ({ focal_mm: 35, height: "eye", angle_deg: -8 })
  .compose ({ subject: "thirds_left", neg_space: "top_right" })
  .mood    (["tense", "neon_soaked", "kinetic"])
  .clamp   (["blade_silhouette", "led_rgb_full", "hilt_geometry"])
  .deny    (["humans", "extra_weapons", "studio_light"])
  .output  ({ ratios: ["16:9","9:16","1:1"], variants: 4, model: "veo-3.0" })
  .signed_by(art_director)
  .submit();

What follows are the nine fields. Each one locks one degree of freedom. Each one fails a specific way when you leave it blank.

Spec

The 9 fields

01 · subject_lock

.subject({ source: <asset_ref> | <spec>, geometry: "exact" | "loose" })

What must not change between generations. The truth of the product. The cut of the dress. The geometry of the building. If we have a real photograph of the subject, that photograph is the lock and every other field is a stage we build around it. If we do not, we write the subject lock as a list of properties: count, shape, material, finish, weight in the hand. Anything not on the list, the model is allowed to invent. Anything on the list, it is not.

02 · world

.world(<scene_id> | <two_sentence_description>)

Where the subject lives. A cyberpunk alley. A sunlit kitchen. A black studio backdrop. We pick one and write it in two sentences. "An eclectic mix of moods" is not a world; it is the absence of one. If you cannot describe the world to a friend on a phone call without pausing, the brief is not ready.

03 · light

.light({ key, rim?, top?, color_temp_k?, hardness })

Time of day, hardness, colour temperature, direction. Hard noon sun from camera left. Soft overcast from above. Practical neon, magenta and cyan, low and behind the subject. Light is the single biggest reason a generation looks AI rather than photographic, so we never leave it implied. We name it.

04 · camera

.camera({ focal_mm, height, angle_deg, distance_m })

Focal length, distance, angle. Tight 85mm at portrait distance. Wide 24mm at the subject's hip. Top-down at arm's length. Most teams skip this field because they are not photographers. It is the field that does the most work. A bad camera spec produces uncanny output even when every other field is right.

05 · composition

.compose({ subject: <thirds|center|edge>, neg_space, focal_path? })

What is in frame, what is out. Where the subject sits on the rule of thirds. Where the negative space lives for a future headline. We sketch it on paper or in Figma when the layout matters. The model does not read sketches; our team does, and it makes the second-pass selection sharper.

06 · mood

.mood([adjective, adjective, adjective])     // length === 3, no exceptions

Three adjectives, no more. Quiet, warm, lived-in. Tense, neon-soaked, kinetic. If we cannot land it in three words, we do not understand the mood yet. Put the brief down. Come back tomorrow. More than three adjectives confuses the model and confuses the team reviewing the dailies.

07 · brand_clamps

.clamp([clamp_id, ...])     // 1..3 items, non_negotiable

Palette. Typography, if compositing comes later. The signature element that has to survive. For Sparkblade, the blade silhouette and the LED glow were clamped on every frame. For a jewelry house, the stone count and the claw setting. Clamps are usually small. Two or three things. They are also non-negotiable. If a frame breaks a clamp, the frame is not in the shortlist, no matter how beautiful it is.

08 · deny (the no_list)

.deny([forbidden, ...])     // grow this list across the whole project

What we never want to see. Extra fingers. Mushy logos. Overcooked rim light. Plastic-looking skin. A no_list is the field that prevents the most regenerations, because you only have to write each prohibition once and reuse it across the project. We keep a master no_list per client and edit it as we go.

09 · output

.output({ ratios: string[], variants: int, model: string, downstream?: cut[] })

Ratio, count, format, downstream cuts. 16:9 for the hero, 9:16 for social, 1:1 for the carousel, an email-friendly width too. The number of variants at first pass. The model we are sending it to. Skip this field and you spend an afternoon in upscalers and crops downstream.

Return

Every brief returns a receipt

When the call resolves, the studio logs a small record per generation. We use it to compare passes, attribute cost, and decide when to stop iterating.

brief.submit() => {
  brief_id:    "sparkblade_hero_01",
  pass:        3,
  variants:    4,
  selected:    1,
  elapsed:     "00:42",
  cost_eur:    1.40,
  model:       "veo-3.0",
  reviewer:    art_director.id
}

Worked example

One brief, one frame

The brief above (sparkblade_hero_01) produced the frame that opens the Sparkblade case study on the third generation pass. It would have taken thirty without it. Fifteen lines of typed brief, three passes, one selected variant, 1.40 EUR of model time. That is the whole story of one hero frame.

Failure modes

What breaks when a field is missing

One common failure mode for each blank field. None of these are theoretical; we have written every one of them on a postmortem at some point.

missing(subject_lock)   => geometry drifts. Frames stop matching.
missing(world)          => generic stock_image land. Pretty, useless.
missing(light)          => the AI tell. Skin waxy, metal plastic.
missing(camera)         => uncanny perspective. Hands wrong.
missing(composition)    => uncroppable into the formats you need.
missing(mood)           => team disagrees on what to keep. Project slows.
missing(brand_clamps)   => beautiful and unusable.
missing(deny)           => regenerate the same prohibitions twenty times.
missing(output)         => upscale, crop, recolour. Half a day gone.

The template

Below is the template we keep open as a Notion duplicate. Take it, edit it, make it yours. The point is not the template itself; it is the discipline of treating an AI brief as a typed call, because that is what it is.

→ Download the template (Notion duplicate)

If you want help adapting this to a specific stack (Veo for product film, Runway for stunt pieces, Midjourney for stills, a custom fine-tune for a catalog) start a brief and we will walk through the right shape together.

Ready when you are

Want this dialled in for your stack?

Send a rough idea and the three outcomes you care about. We come back with a plan, not a pitch.

Start a brief