Inspiration

Tools like Grok Imagine's Agent Mode and other generative creative canvases have shown that AI can produce genuinely impressive media — ideas become images, clips, and videos through a sequence of generations on a visual canvas.

Grok Imagine Agent Mode dashboard — a canvas of generated clips

Grok Imagine Agent Mode — impressive generation, but no map of how the pieces relate.

But actually using them to make something longer than a few clips, we kept asking the same questions:

  • Which clip belongs to which scene?
  • Why did the character suddenly change appearance?
  • Which prompt generated this shot — and where did it come from?
  • If I change the story, which assets need to update?

The problem wasn't generating media. It was directing a production. Past a handful of clips, you're managing disconnected images, videos, and prompts with no map of how they relate — so iteration is painful and consistency is nearly impossible.

That's why we built CineForge.

What it does

CineForge is a creative operating system for AI filmmaking. It transforms a single idea into a complete film by orchestrating stories, characters, scenes, shots, and AI models within a dependency-aware production graph. Every creative decision is connected, every asset is traceable, and every edit intelligently updates only the parts of the production that need to change.

It's built to answer the exact questions that break down once a production grows past a few clips:

"Which clip belongs to which scene?" Everything is a node on one graph, and every node carries its parents. A shot knows the keyframe it was animated from; the keyframe knows its scene; the scene knows its cast and location. Nothing is a loose file — the final cut is assembled by walking those links (shot → keyframe → scene) so every clip lands in story order, even after you've regenerated half of them.

"Why did the character suddenly change appearance?" Because in CineForge, it can't drift silently. Character and location reference sheets are locked once, then fed as image-conditioning into every keyframe that features them — so the model is composing against the same face, not hoping for it. An autonomous QC agent reviews every asset with identity as a critical criterion: a frame of the wrong person fails on its own and re-renders, within a bounded budget, without you asking.

"Which prompt made this — and where did it come from?" Click any asset. Its provenance is right there: the provider, the model, the exact prompt, a SHA-256, and the Genblaze manifest hash — persisted in Backblaze B2 next to the media itself. Regenerations carry a parent link, so a take knows the take it descends from. Nothing in the film is unaccountable.

"If I change the story, which assets need to update?" This is the heart of it. Say "make the ending more emotional," and CineForge doesn't blindly re-render — it proposes: it shows you the field diff and the exact set of downstream assets that would change, before you spend a cent. Approve, and it updates only those. Rename a character and it's free and instant — one write propagates to every scene, every voiceover line, and every stored prompt without re-rendering a single frame. Edit a shot you haven't generated yet and the change is baked into its prompt for when that pass runs. The graph always knows what a change breaks.

And because the whole thing is a graph the director drives rather than a queue that runs away from them, the pipeline is built in four gated passes — write the film, render the references, render the keyframes, animate the shots — with a stop-for-a-human gate after each. You approve words first (the only creative pass); everything after is mechanical execution of what you approved. The entire flow is demoable with zero API keys, then swaps to a real paid run by setting credentials and one flag — because mock and real go through the same code paths.

How we built it

The thesis — directing a production, not generating media — is an architecture decision, not a UI one. Everything below exists to keep the graph's answers true.

One graph, parent-linked. Every artifact — story, character, environment, scene, keyframe, shot, timeline — is a Node carrying parent_ids (backend/models.py). That single choice is what lets an upstream change mark everything downstream stale, lets impact_of dry-run a change's blast radius before you pay, and lets resync_stages reopen exactly the passes a late edit invalidated — no more, no fewer. The graph isn't a view of the production; it is the production.

An ai/ layer of specialist agents, orchestrated by a director. story turns an idea into a full bible plus every prompt the later passes will spend; camera builds 5-layer shot prompts; route reads a free-text note and picks its target; entities runs the token/rename layer; qc reviews every asset; gate rolls a pass's verdicts into one open/hold decision. A director orchestrator runs the four passes one gate at a time and streams progress to the canvas over SSE.

Consistency by conditioning, not by luck. The identity lock is real wiring: a scene's locked founding sheets and environment plate are passed as Genblaze external_inputs conditioning images into every keyframe render — on both the first pass and every regen — and each approved still is animated image-to-video, so the frame the director said yes to is the frame that starts moving.

Genblaze as the generation spine. One module owns all provider talk (genblaze_client.py). Real renders run Pipeline(...).step(provider, ...).run(sink=B2), using external_inputs for conditioning, Step.seed for reproducible regens, and fallback_models for resilience — then read asset.url, asset.sha256, and the run manifest back out. A single PROVIDER_STACK flag swaps the entire GMICloud ↔ OpenAI provider set with no other code changes.

B2 as the canonical, traceable record. Rendered media streams straight to Backblaze B2 via Genblaze's ObjectStorageSink(S3StorageBackend.for_backblaze(...)); projects, Genblaze manifests, and final-cut exports all land in the same bucket, and the library reads back from it. Persistence is 3-tier — memory → local disk (atomic writes) → B2 — so a mid-generation disconnect never loses paid work.

Providers/models: GMICloud seedream-5.0-lite (image) + seedance-1-0-pro (image-to-video) + claude-sonnet-4.5 (planner + vision QC), ElevenLabs eleven_v3 (voiceover), Sync.so lipsync-2.0.0, with an OpenAI stack (gpt-image-1 / sora-2 / gpt-4o) behind the same switch. Frontend is React + Vite + React Flow — nodes bloom live over SSE, with an Inspector, QC panel, stage-board Rail, Timeline/Cinema mode, and a conversational composer.

Challenges we ran into

One at a time, or all at once? The moment a film has a cast of six and forty coverage units, "generate the characters" stops being one call and becomes a scheduling problem. Sequential is simple and easy to reason about, but a judge watching six characters render one-after-another is watching a progress bar crawl — and most of that time is spent waiting on the network, not computing. All-at-once is fast but reckless: fire forty renders in parallel and you trip the provider's own concurrency and rate caps, and a burst failure can cost you real money with nothing to show. We landed on bounded concurrent fan-out — a small thread pool (GEN_CONCURRENCY, default 3) renders independent units together and commits each one to the graph the instant it lands, so a client that disconnects halfway keeps every unit that already finished. Video got its own, lower cap (VIDEO_CONCURRENCY, default 2), because an animation is minutes and dollars where a still is seconds and cents, and providers police the expensive endpoint harder. The hard part wasn't the threading — it was deciding what's even safe to run in parallel, which forced the next question.

Let the human drive, or let the agent run? The tempting build is: type an idea, walk away, come back to a finished film. It demos like magic — until you realize the agent just spent a hundred renders animating a character whose face was wrong in the very first sheet. Each stage of a film inherits the mistakes of the one before it, and the stages get progressively more expensive, so a single bad decision at the top compounds into a whole film of the wrong person at the bottom. So we deliberately refused one-pass autonomy and built four gated passes — write the film, render the references, render the keyframes, animate the shots — where every gate stops for the director. The subtle part: only the first pass is creative. Synthesis writes the bible, the cast, the breakdown, and every prompt the later passes will spend, so once you approve it, the three generation passes are pure mechanical execution of what you already signed off on. That's what makes the cheap gate the important one — you're approving words before you pay for pixels. QC still has autonomy where autonomy is cheap: it reviews every asset and spends a bounded regen budget on its own — it just never opens a gate. Balancing "the director is in control" against "the director isn't clicking approve four hundred times" was the central UX-vs-cost tension of the whole project.

What actually drives a shot? The question underneath everything was: what's the unit of generation, and what anchors consistency across it? We modeled a keyframe as a composed still of the scene's happening — the moment, staged, at a specific framing — which then becomes the image-to-video source that a shot is animated from. That single decision is what lets coverage work: the same staged moment can be emanated as different camera angles — a wide, a reverse, an insert — each a shot with its own framing and move, rather than forty unrelated generations. But it opened a real architectural fork we had to resolve, and honestly resolved twice:

In the main pipeline, each coverage unit gets its own keyframe, independently composed against the locked character/environment reference sheets — so identity is pinned at the pixel level by conditioning every frame on the same face, not by hoping a shared frame stays on-model. That's why the sheets gate is the hardest one: everything downstream is composed against those sheets, so they're the thing most worth stopping to approve. In the "call for another angle" path (add-shot), we do the opposite on purpose: a new setup reuses the scene's approved master frame and just re-animates it at a different framing — no re-composition, no story rewrite, no downstream staling. One frame, many angles. Deciding when consistency should come from a shared master frame versus from locked reference sheets — and making the mock actually demonstrate the difference (it ffmpeg-crops one still to the requested shot type so a close-up genuinely reads as a close-up of the same frame the wide came from) — was the piece that took the most iteration, because it's the difference between "coverage" being a real feature and being three identical clips with different labels.

Accomplishments that we're proud of

We made "which change breaks what" a solved question. The dependency graph — parent links, stale propagation, impact preview, and stage resync — means the production always knows what an edit invalidates. Editing the story reopens exactly the affected passes and nothing else. That's the feature the whole category is missing, and it's real, not a mockup.

We made every frame a version, and nothing ever gets overwritten. Generation in CineForge behaves like version control for a film production. Every render appends a take — a regeneration you dislike never destroys the frame you already paid for; it just adds a candidate beside it (push_version). node.asset always points at the accepted take, so everything downstream reads one field and never has to know history exists — but the history is all there: A/B compare any two takes, and accept an earlier one for free, because those pixels already exist and were already paid for (select_version). Crucially, each take carries its own QC verdict — a review belongs to the pixels it judged, so rolling back to take 2 brings take 2's verdict with it instead of inheriting take 4's. Lock a take and it's pinned: every regeneration path, including scene-wide cascades, skips it. And "undo" is honest — it's last-in-first-out, it restores the exact pre-edit graph and prompt state, and it creates a compensating take rather than deleting anything or rewriting provenance. You can always get back to a frame you liked, and the record of how you got there stays intact.

We made edits propose before they spend. Nothing renders on the instant a note is typed. CineForge shows the field diff and the precise cost first, re-derives the impact at approval time, and can even bake an edit into a not-yet-rendered prompt for zero cost. The director stays in control of every dollar.

We made a QC agent with real authority. Not a label — an agent with per-kind criteria, a bounded regen budget it spends on its own, frame-sampled clip review that catches a face morphing mid-shot, honest SKIPPED verdicts instead of rubber-stamps, and an auditable ledger that answers "is this production-ready?" with a number.

We made provenance non-optional. Every frame carries provider, model, prompt, SHA-256, and a verifiable Genblaze manifest hash, stored in B2 alongside the media. Nothing in a CineForge film is a mystery clip.

What we learned

Orchestration is the product; generation is a commodity. We started thinking the hard part was getting a good clip. It wasn't — anyone can get one good clip. The hard part is everything around the clip: which scene it belongs to, what it was composed against, what breaks if you change the story, how you get back to the version you liked. The value we shipped lives in the graph, the gates, the takes, and the provenance — not in the model call. That reframed how we think about generative-media apps entirely: the model is a stateless function you rent, and the product is the state, the structure, and the record you keep around it. Which is exactly why Backblaze B2 and Genblaze aren't bolted on — they're the durable spine that makes the orchestration real.

The right unit of work is a design decision, not a detail. Deciding that a "generation unit" is one staged moment → one still → one animation, and that consistency is anchored by conditioning every frame on locked reference sheets, is what made coverage, parallelism, targeted regeneration, and free renames all fall out cleanly. When we got the unit right, the features got simple. Every place the system felt awkward traced back to the unit boundary being in the wrong place.

Human control and cost control are the same problem. We kept framing "keep the director in the loop" as a UX concern and "don't burn money" as an infra concern, until we realized they're one decision. The gate that stops for a human is the gate that stops the spend before a bad decision compounds. Autonomy is cheap where mistakes are cheap (QC re-rendering one frame) and expensive where mistakes are expensive (a pass that inherits a wrong face). Putting the stop at the boundary between those two is the whole design.

Non-destructive is a feature you feel, not one you see. Making every render append a take instead of overwriting one sounds like a footnote. In practice it changed the entire emotional register of using the tool — "try it another way" stopped being a gamble, because the frame you already paid for is never gone. Iteration without fear turned out to be the difference between a demo and something you'd actually direct a film in.

Real integrations live in the gaps the docs skip. Cloudflare 403s on a default user-agent, model catalogs that live in a different namespace than the chat catalog and drift on their own, adapter signatures that silently truncate a large JSON and fall back to a fixture without erroring, conditioning images that route off Step.inputs and not a prompt string — none of these are in a quickstart. We learned to make failures loud (surface the provider's own message instead of degrading to a mock) so "correct-looking" never masquerades as "working."

Build your mock to be adversarial to yourself. A mock that renders real playable MP4s, crops them to the requested shot type, and fails QC ~1 in 5 first attempts caught layout, coverage, and regen-loop bugs a "green everything" placeholder would have shipped straight to the demo. The best thing our mock did was refuse to lie to us.

What's next for cineforge

Prove the paid round-trip. Everything runs through the same code paths in mock; the single most important next step is smoke_real — one real still → image-to-video → B2 round-trip — to confirm end-to-end that Genblaze routes the locked reference sheets as conditioning (pixel-level face-locking), that seedance animates the approved still, and that the result lands in B2 with a real, verifiable manifest hash.

Dialogue and lip-sync. The Sync.so windowed mouth-drive is already wired — a line "plugs into" a shot's window without replacing its camera move — and voiceover per scene is in. Next is proving lip-sync on a paid run and giving each character a consistent voice tied to their entity, so dialogue is as identity-locked as the face.

Multi-user studio. The auth layer (Supabase) is scaffolded; the next step is per-user libraries, shared public galleries, and the collaboration primitives that turn a solo tool into a studio — where the provenance record also becomes a chain of custody across contributors.

Ask the film a question. The graph already knows structure — which shot belongs to which scene, which nodes depend on a character. The next leap is letting a director query the production in plain language: "where is Chidi seen holding a cup?" — and CineForge resolves the entity, walks every scene, keyframe, and shot connected to it, and presents the exact nodes back, on the canvas, with their provenance. Part of this falls out of what we've built: the entity/token layer already maps entity → every node and field that depends on it (back_references), so "everything about Chidi" is already a solved query. The new part is semantic/visual recall — indexing what each frame actually contains (our QC judge already looks at pixels; the same vision pass can tag "holding a cup," "night," "two-shot") so a question about content, not just structure, returns the right handful of nodes instead of making you scroll forty clips. This is the graph, the entity layer, and the vision judge converging into a single answer: your production becomes searchable by what happens in it.

Surgical control — fix one beat, don't rebuild the film. The most common director's note is the most expensive one today: "I don't like the animation right here." Right now that re-animates a whole shot. Next is making that note cheap and local: re-animate just the sub-window that's wrong while keeping the rest of the clip; swap in a different take you already paid for (non-destructive history already makes this free); nudge the motion or timing without a full re-render; or re-frame off the same approved master frame instead of re-composing from scratch. We already prove the windowed-edit primitive elsewhere — lip-sync drives the mouth over a slice of a clip without disturbing its camera move — so the groundwork is there. The goal is a control surface where the cost of a change scales with the size of the change, not the size of the shot: a small note should cost a small render, and only a structural note should cost a structural one. That's the difference between AI you regenerate-and-pray with and a production you can actually edit.

Deeper coverage intelligence. The coverage agent recommends the missing shot today; next is letting it reason about editing rhythm and screen direction across a whole scene — suggesting not just what to shoot but how it cuts — pushing more of the film-craft into the graph.d

Built With

Share this project:

Updates

Submission history