How Qweek keeps characters consistent across shots
Clip tools forget your cast between generations. Qweek treats the script as source of truth and stores Cast, Worlds, and shots in a real production graph — so every frame inherits identity instead of hoping the prompt sticks.
Why this exists
Most image and video models are stateless. You write a prompt, get a frame, write another prompt, get another frame. Say the same character name twice and you still get drift — different face, wardrobe, vibe.
That is fine for one-off clips. It is broken for serialized story.
Qweek Studio is built around a different bet: the screenplay is the source of truth, and generative models are just renderers. Identity lives in durable records (your Canon — Cast + Worlds), not in whatever you typed into the last prompt box.
What we store
For each project:
| Piece | What it is | |-------|------------| | Cast | Characters — look DNA, references, optional voice | | Worlds | Locations / environments | | Script lines | Ordered story beats | | Shots | The storyboard frames tied to those lines |
When you type @Ada in a line, we resolve that to Ada’s Cast entry — exact name match, no inventing a cast member you did not define. Soft retrieval can pull related context, but it does not override an explicit @ reference.
How a frame gets built
Roughly:
- You edit a script line (or set a location).
- We wait a short debounce so we are not firing on every keystroke.
- We compile conditioning: line text + Cast DNA for
@mentions + World plate if linked + optional retrieved notes. - Static mode generates a still (fast iteration).
- Motion mode can lock off a still once you are happy — more expensive, slower, intentionally gated.
Quotas only tick up when a job succeeds. Failed provider calls should not burn your month.
The graph, without the jargon
Under the hood this is a typed production graph in Postgres: workspaces own projects; projects own Cast and Worlds; lines mention characters and sit in locations; shots render lines. Foreign keys keep structure honest. Appearance consistency is enforced at generate time by compiling from those records — SQL alone cannot make two faces look the same.
Jobs go through a Redis-backed worker queue with retries and clear statuses (queued → running → succeeded / failed). That is boring infrastructure. It is also what makes “edit a line → board updates” feel like a studio, not a chat toy.
Why Static and Motion are separate
Static is for thinking: try framing, wardrobe, blocking. Motion costs more and takes longer. We treat them as two operating points you choose with your plan quotas — iterate cheap, commit expensive — not as one magical “make video” button that burns budget while you are still figuring out the scene.
How we will know if it works
On real projects we care about:
- Same Cast member still looks like themselves across shots
@mentions resolve the way a human reader would expect- Time from edit → ready frame feels usable
- Successful frames vs billed generations (do we waste quota?)
Useful ablations later: no Cast, no retrieval, no debounce, Static-only.
What still bites us
- Provider model swaps change looks even when our conditioning is fixed
- Re-embedding Cast/Worlds if we change the embedding model
- Aliases and nicknames (exact
@Nameis strict on purpose today) - Asset URL / bucket policy mistakes when reviewing frames in-browser
Bottom line
We are not trying to win “best prompt engineering tips.” We are trying to make state craft the default: build Canon once, reference it from the script, regenerate a line without losing the person.
If the board forgets Ada between shots, we failed — no matter how pretty the individual frame is.