The problem: video generation is cheap per clip and ruinous per batch. A line that renders first and checks later burns credits on scripts that were wrong at text cost.
What I built: a pipeline where every check that can run on text runs before a credit is spent, video renders last under a hard cap, and a human gate is the only way out.
The result: 4 finals from one brief for 89.2 credits (22.3 each), 1 held for revision, 2 approved, 2 exported in three ratios, 0 published.
The cost model
The order of the line is the budget. Research, angles, scripts, a claims check and an AI-tell scan all run at text cost. Only then does the run price the batch, compare it to the cap in code, and either refuse or spend. A script that invents a price dies before it can buy a clip.
SPEND PLAN: 4 videos x 22 = 88 credits + TTS (measured live, est <8). Cap 150. text gate: clean (claims + off-default copy-tells)runs/run-addplus-sep/events.jsonl, the run's own log, 2026-09-03. The cap is a constant in line.mjs; the run refuses to start a batch that would cross it.
Every artifact under runs/<id>/ is a checkpoint: a voiceover, a raw clip, an assembled final. A file that exists is never re-billed, so a run that dies at clip three resumes at clip three. The one exception is deliberate: a checkpointed voiceover made before a rule existed must still pass that rule. A stale 10.2-second file once shipped a clipped last word, so the fit gate now re-measures old files and discards the ones over budget.
The line, as it ran on 2026-09-03
Claude with web search, told to report only what a fetched page supports and to list what it could not establish. The gaps are the honest part: this run could not confirm the week's rotating drink, so no script may call a drink "this week's".
+ research.json: 11 cited findings, 8 gaps, 8 searches
The critic scores thumb-stop, specificity, believability and novelty, then keeps four with four different mechanisms. This run it kept Identity-Coded Exclusion, Anchoring & Contrast, Common Enemy, Social Proof and killed the rest as one insight wearing different labels.
+ angles.json: 12 written, critic kept 4, running the top 2
Angles 1, 2, 3, 7, and 9 are all the same 'weekly rotating menu' insight wearing different mechanism labels (curiosity, loss aversion, scarcity, liability inversion, novelty bias), they'd cannibalize each other in testing, so only the strongest survivor logic was needed and none scored high enough to beat the four kept. Angles 8, 11, and 12 are generic location/hours/local-pride copy with weak thumb-stop and low novelty, so they were cut outright. The keepers span four clearly distinct psychological levers, identity exclusion, contrast/anchoring, common-enemy framing, and social proof, while also being the highest scorers outside the duplicate cluster, giving a testable, non-redundant set.
One script per angle per visual axis. The voiceover budget is 13 to 14 words this run, because a paid voice reads about two words a second and the clip is eight. The video prompt follows the synced Veo 3.1 spec: cinematography first, one named camera move, no on-screen text, no logos.
Every number and product name in the copy must appear in the brief or a cited finding. The copy is then scanned by off-default for AI tells. A failure exits before the spend plan.
text gate: clean (claims + off-default copy-tells) SPEND PLAN: 4 videos x 22 = 88 credits + TTS (measured live, est <8). Cap 150.
Rendered, measured with ffprobe, fitted with a bounded atempo, and if it still does not fit, the writer compresses it once. This run's voiceovers landed at 6.5s, 5.8s, 7.5s, 7.8s.
Four clips, one per script, the model picked in a 42-credit bake-off against Kling 3 and Kling 3 Turbo on the same prompt. Veo won on light, product fidelity and condensation physics.
Voiceover muxed, one quiet monospace brand line burned inside the platform safe zones, no hook text on the frame. Same inputs, same bytes.
Aspect, audio and duration by ffprobe. Then three frames of each final go to a vision gate with the product list and the design spec. A block lands the variant on the revision list.
revision list: 1 of 4 held back (a10-street)
Nothing leaves without a decision. Rejecting the whole batch asks for a reason, and the reason is injected into every writer prompt on the rerun, so a rejection steers instead of deleting.
Approved variants copied out as 9:16, 4:5 and 1:1, with a manifest carrying per-variant cost and the reviewer's note. This run: 2 exported, 2 skipped with the reason recorded.
One row per run: brand, model, variants, how many the gates held, credits against the cap, and how the spend was measured. 5 earlier runs were backfilled from their manifests after this run, which is why the run's own log line below counts one.
spent 89.2 credits (cap 150), 22.3 per variant. manifest at runs/run-addplus-sep/manifest.json ledger: 1 runs, 89.2 credits on record ledger.json now: 6 runs, 446.6 credits on record
The proof
The paid half printed 28 lines over 9m 21s. The recording replays them in order with their real timestamps; only the render waits are compressed, and the frame says so. Then the reviewer's gate, driven live, and the four finals.
Not a staged run. The terminal segment is generated from runs/run-addplus-sep/events.jsonl, which line.mjs appends to on every printed line with a millisecond timestamp. A checkpointed replay would read "exists, skipped" on every line, so the recording is of a fresh brief with the critic and the live research both running.
The gates
The assembly gate is arithmetic. The vision gate is judgment: three frames of each final, the product list, and the on-video design spec, judged by Claude with an exit code. It exists because a plain iced coffee once shipped under a Biscoff Blueberry voiceover.
design-specs/ad-design.md, synced from four 2026 safe-zone sources. The gate treats any burned text inside a shaded zone as a finding, not a preference.
{ "severity": "block", "what": "The advertised product is never identifiable. All three frames show only a plain white ceramic mug held at arm's length, tilted away from camera, with no visible beverage, color, foam, garnish or cup branding, nothing distinguishes it as Shaken Cinnamon Oat, Biscoff Blueberry or Ube Latte (an ube latte would read purple). This is a generic/unreadable product per the stated intent.", "where": "frames 0, 1, 2, mug in center-right of frame", "fix": "Reshoot or re-cut to include at least one clear, in-focus top-down or side shot showing the actual drink (ube's purple layering, Biscoff crumb/blueberry, cinnamon-dusted oat foam) before the walking POV shot." }A real block from runs/run-addplus-sep/a10-street-vision.json. Brand: add+ coffee. The variant was held for revision and never reached approve.
Before this pass, a vision failure was recorded and nothing happened: the variant sat next to the passing ones with the same approve button. Now a blocked variant is held with its reasons, the approve control is disabled on the review surface, export skips it and writes why, and the ledger counts it. The reviewer still sees it, because the failure is information about the next batch.
Decisions on record


The finals
Variants differ on named axes, never on a re-roll of the same prompt: a counter macro and a street walk per angle, recorded in the manifest, so distinctness is auditable. The decisions shown are the ones made in the recording session.
hook lands, product reads
keep, the walk sells it
VO promises three drinks, frame shows two mugs and neither is on the menu
held for revision: vision: The advertised product is never identifiable. All three frames show only a plain white ceramic mug held at arm's length, tilted away from camera, with no visible beverage, color, foam, garnish or cup branding, nothing distinguishes it as Shaken Cinnamon Oat, Biscoff Blueberry or Ube Latte (an ube latte would read purple). This is a generic/unreadable product per the stated intent.
undecided

The export step writes each approved variant three ways and a manifest with the reviewer's note and the credits it cost. The files are named to upload; no ad account is connected, by design.
Measured in the files, 2026-09-03. Three of the runs on the ledger are fictional demo brands (Araw Botanicals, Gising!, Himbing) built to exercise the line; their spend was attributed from one batch, so the clean numbers below come from the add+ coffee runs only.
credits for this run, measured as the balance delta, 22.3 per final
runs on the ledger, 24 finals, 5 held for revision by the gates
credits across the 2 runs with a clean, single-run balance delta
cited findings and admitted gaps from live research, before a word was written
angles written to angles kept, four mechanisms, no near-duplicates
ads published by the line. Every batch stops at a human.
Claude wrote the research findings, the twelve angles and the critic's verdict, the scripts and the video prompts, and judged the frames in the vision gate. Veo 3.1 rendered the footage; ElevenLabs read the lines. I owned the spec and its locked decisions, the cost model and the cap, the gate order, the text gates, the on-video treatment, the review surface, the export, the ledger, and the verdicts. The brief is my own cafe, which is why the research gaps are real: the line could not find this week's drink because I had not posted it.
Maria Angelika AgutayaAI Engineer · mariaangelika.com · Video Ad Line, 2026