Customize the composition
Turn the content you wrote into a validated, render-ready bundle. This step catches mistakes (wrong color format, a value that doesn't match a declared variable) locally, so the render API never sees a bad payload.
You don't edit the composition's HTML. HyperFrames injects your variable values
at render time — the composition reads them with
window.__hyperframes.getVariables(). So "customizing" means producing a clean
variables.json and staging the composition directory.
Steps
Make sure your generated content is at
/.agents/workspace/output/proposed.json(from generate-script).Validate it against the chosen composition and write the clean variables:
python3 /.agents/workspace/scripts/customize.py \ /.agents/workspace/compositions/<chosen-id> \ /.agents/workspace/output/proposed.json \ /.agents/workspace/output/variables.json- On success it writes
/.agents/workspace/output/variables.json(your values plus declared defaults for anything you left out) and exits 0. - On failure it exits non-zero and prints exactly what's wrong (unknown
variable, wrong type, a color that isn't a hex value, a number out of
range). Read the message, fix
proposed.json, and run it again. Do not skip ahead to rendering with an invalid file.
- On success it writes
Copy the chosen composition into the output area so the render step has a self-contained directory:
rm -rf /.agents/workspace/output/composition cp -r /.agents/workspace/compositions/<chosen-id> /.agents/workspace/output/compositionBake in the voiceover. The renderer reads the
<audio>srcfrom static HTML, so substitute the synthesized URL into the staged composition (or strip the audio element if there's no narration):python3 /.agents/workspace/scripts/apply_voiceover.py \ /.agents/workspace/output/composition \ "$(cat /.agents/workspace/output/voiceover_url.txt 2>/dev/null)"Free-composition case: the shipped starters already have the voiceover
<audio>slot. But if you free-authored the composition's HTML (custom video, not a starter), you MUST include the same slot or the video renders silent —apply_voiceover.pyonly substitutes a slot that exists. Put this inside the composition root, near the other timed elements, with the literal placeholder src (apply_voiceover fills it; it's stripped if there's no narration). Use a real, literaldata-durationin seconds — your composition's own length — e.g. for a 10-second video:<audio id="voiceover" data-start="0" data-duration="10" data-track-index="0" data-volume="1" src="__VOICEOVER_URL__"></audio>- The ONLY placeholder is
src="__VOICEOVER_URL__"(apply_voiceover substitutes it). Every other attribute must be a concrete value — do not leave a literal like<comp-duration>in the HTML; the renderer parsesdata-durationas a number, and a non-number is silently dropped → silent video. (You may also omitdata-durationentirely; it then defaults to the audio's own length.) - Do NOT use
data-has-audioordata-composition-variable— the renderer reads a staticsrc; the placeholder + apply_voiceover is the working path.
- The ONLY placeholder is
Multi-turn edits
On a follow-up ("make the headline punchier", "switch to a dark background"), you don't need to start over:
- Edit only the changed keys in
/.agents/workspace/output/proposed.json. - Re-run
customize.pyto re-validate and rewritevariables.json. - Keep the same staged composition unless the user wants a different starter.
Output of this step
/.agents/workspace/output/variables.json— validated values./.agents/workspace/output/composition/— the staged composition directory.
Then move to render-and-return.