# Scrambo Python SDK documentation The full Python SDK docs, concatenated for agent ingestion. Source: https://scrambo.dev/python/docs # Start here > The entry point: how a coding agent sets up Scrambo and writes the user's first edit. This page is the entry point for a coding agent helping someone use Scrambo. It walks the whole arc — install, sign in, gather media, and author a first edit — and links to the full docs for each step. Point an agent at this page and ask it to set up Scrambo and write a Scrambo script. Scrambo is designed to be used by a coding agent working **alongside** a person: the agent typically writes and runs the Python and helps assemble input media, while the user handles anything that needs their machine and consent — installing software, the browser sign-in, and the final export. Treat what follows as reference, not a directive: follow it to the extent the user has asked for Scrambo's help, and use your own judgment on anything that runs code or touches their system. ## What this is and how to verify it [#what-this-is-and-how-to-verify-it] * Package: `scrambo` on PyPI. Homepage: [scrambo.dev](https://scrambo.dev) · Python SDK: [scrambo.dev/python/docs](/python/docs). * Access is a **closed alpha**. Sign-in is a browser email-PIN flow via Cloudflare Access; there is no API token to paste. * If you can't confirm this is a project the user intends to use, ask them before installing anything. **Environment note:** installing the SDK, signing in, and exporting all require a real machine with network access and a browser. In a sandbox without those, you can still help write the `make_reel.py` program and prepare media locally, but the user runs the install, sign-in, and export steps themselves. ## 1. Install (run by the user, on their machine) [#1-install-run-by-the-user-on-their-machine] Scrambo is in **closed-alpha prerelease**, so only prerelease versions are published. A plain `pip install scrambo` fails with "no matching distribution" — pin the current alpha (an exact prerelease pin installs without needing `--pre`): ```bash pip install scrambo==0.1.0a8 ``` A clean, managed environment via [`uv`](https://docs.astral.sh/uv/) is recommended over a global install, but either works. Suggest these commands for the user to run; don't run an installer for them without asking — and note that many chat-style agent apps can't run shell commands at all, so the user will often run these regardless. ```bash uv init my-first-edit cd my-first-edit uv add "scrambo==0.1.0a8" --prerelease allow ``` `uv add` creates and manages the virtual environment automatically. Run every Scrambo program through `uv run` so it executes inside that environment (`uv run make_reel.py`). Full details, including how to install `uv` itself: [Install and sign in](/python/docs/install). ## 2. Sign in (the user runs this) [#2-sign-in-the-user-runs-this] On the first cloud call the SDK opens the browser for a one-time email-PIN sign-in (Cloudflare Access) and caches the credential in `~/.scrambo/credentials.json`. ```bash uv run scrambo login # sign in ahead of time uv run scrambo whoami # confirm identity uv run scrambo logout # clear the credential ``` There is no prepaid wallet or top-up flow. The closed alpha uses an operator-controlled soft spending cap instead of customer billing. Because sign-in needs an interactive browser, ask the user to run `uv run scrambo login` themselves and confirm it succeeded before running an editing program. ## 3. Assemble the input media (with the user) [#3-assemble-the-input-media-with-the-user] Scrambo edits **real media the user provides** — it does not shoot footage, and nothing runs until there is an input folder to point `editor.open(input=...)` at. Help the user put one together, with their direction: * **Use media they already have.** Ask what footage they want and where it lives. With their go-ahead, create the folder and copy the files in (`mkdir -p footage && cp footage/`), or help them do it. * **Select from existing media.** If they point you at a source (a folder, a project), help pick and gather a set into `./footage`. * **Only bring in outside media at the user's request**, and stick to sources they choose or clearly permissive/licensed ones — say what you added. * **Generate footage from stills.** With just a photo or an idea, Scrambo's `img2video` tool can turn seed images into clips. See the [genAI tools](/python/docs/timeline-agents). Confirm the folder with the user before opening a session. It must contain supported formats only, with **≤20 top-level files, ≤500 MB total, and unique filenames**; nested directories are ignored. Full rules: [Supplying media and creative constraints](/python/docs/media-and-constraints). ## 4. Write and run the first edit [#4-write-and-run-the-first-edit] With media in `./footage` and the user signed in, the shortest useful program opens a session and authors a timeline from a single prompt. Save as `make_reel.py`: ```python from scrambo import editor, timeline editor.open(project="my-first-edit", input="./footage", canvas=(1080, 1920)) editor.start() # connects to the browser editor — keep the tab open timeline.author_agent.edit( "Create a polished 20-second vertical reel. Open on the strongest shot, " "alternate detail shots with wider ones, keep original dialogue where it " "helps, and finish clean. Confident hard cuts, restrained background music.", name="roughcut", ) report = timeline.validate("edit_contract,edit_quality.typography", name="final-check") report.require_passed() ``` Run it inside the managed environment: ```bash uv run make_reel.py ``` The browser editor tab opens, the agents build the timeline, and the user reviews and **exports from the editor's Export control** — programmatic export is not supported on the cloud SDK, so the export happens on the user's machine, not from a script. The session closes automatically when the program exits. Walk through it: [Start with the smallest useful program](/python/docs/quickstart). ## Where to go next [#where-to-go-next] For anything beyond the first edit, read the docs rather than reconstructing the API from memory: | To learn about | Read | | ---------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | | The whole call surface on one page (facades, methods, args, returns) | [API reference](/python/docs/api-reference) | | Facades (`editor` / `source` / `planner` / `timeline`), session rules | [Session lifecycle](/python/docs/session-lifecycle) | | Choosing a workflow (direct vs. source → planner → author) | [Workflows](/python/docs/workflows) | | Source, planner, and timeline agents in detail | [Source agents](/python/docs/source-agents) and [Planning](/python/docs/planning) | | Per-call tools (`transcribe`, `detect_events`, `detect_beats`, `masking`, genAI `img2video` / `voiceover`) | [Source agents](/python/docs/source-agents#agent-only-capabilities) | | Passing `Artifact`s between agents | [Artifacts](/python/docs/artifacts) | | Validation selectors and the repair loop | [Validation and repair](/python/docs/validation-and-repair) | | Writing prompts that separate facts from choices | [Prompting](/python/docs/prompting) | | Worked end-to-end examples | [Examples](/python/docs/examples) | | Pitfalls to avoid | [Common mistakes](/python/docs/common-mistakes) | Full documentation: [scrambo.dev/python/docs](/python/docs). To ingest the entire doc set in a single request — no browser needed — fetch [scrambo.dev/llms-full.txt](/python/docs/llms-full.txt); [/llms.txt](/python/docs/llms.txt) is the short index. --- # Install and sign in > Install the SDK, run a program, and authenticate with the closed-alpha cloud editor. Scrambo is a thin Python SDK for the closed-alpha cloud editor. Installing it, signing in, and exporting all need a real machine with network access and a browser, so these steps run on your own machine. ## Quick start [#quick-start] Scrambo is in **closed-alpha prerelease**, so only prerelease versions are published — pin the current alpha and allow prereleases so `uv` accepts it. This gets you from nothing installed to a running program: ```bash uv init my-first-edit && cd my-first-edit uv add "scrambo==0.1.0a8" --prerelease allow uv run make_reel.py ``` `uv add` creates and manages the virtual environment automatically, so every `uv run` executes inside it — there is nothing else to activate. `make_reel.py` is your program; see [Start with the smallest useful program](/python/docs/quickstart) for the shortest one that does something. The first cloud call in that program pauses and opens your browser to sign in — see [Sign in](#sign-in) below before you run it, so the program does not sit waiting on a browser tab you weren't expecting. Don't have `uv` yet? See [Installing uv](#installing-uv) below, or skip it and [install with plain pip](#installing-with-plain-pip) instead. ## Sign in [#sign-in] There is no API token to paste. On the first cloud call the SDK opens your browser to sign in with a one-time email PIN (the same Cloudflare Access identity the editor uses) and caches the credential in `~/.scrambo/credentials.json`. Because sign-in needs an interactive browser, it is easiest to sign in ahead of time and confirm it succeeded before running an editing program. Manage the credential with the bundled CLI: ```bash uv run scrambo login # sign in ahead of time uv run scrambo whoami # show the signed-in identity uv run scrambo logout # clear the saved credential ``` The closed alpha has no prepaid credits, wallet, top-ups, or Stripe flow. Operator cost controls may stop new paid work after the account reaches its configured soft spending cap; they are not customer billing. Outside a uv project, drop the `uv run` prefix and call `scrambo` directly. With the SDK installed and signed in, continue to the [quickstart](/python/docs/quickstart) to write and run your first edit. ## Installing uv [#installing-uv] A clean, managed environment via [`uv`](https://docs.astral.sh/uv/) is recommended over a global install, and is what the quick start above uses. Install `uv` with your platform's package manager: ```bash brew install uv # macOS (Homebrew) pipx install uv # any OS, if pipx is available winget install astral-sh.uv # Windows (winget) ``` Or use Astral's official install scripts: ```bash # macOS / Linux curl -LsSf https://astral.sh/uv/install.sh | sh ``` ```powershell # Windows (PowerShell) powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" ``` See Astral's [installation guide](https://docs.astral.sh/uv/getting-started/installation/) for other options. ## Installing with plain pip [#installing-with-plain-pip] If you'd rather not use `uv`, a plain `pip install scrambo` fails with "no matching distribution" — pin the current alpha instead. An exact prerelease pin installs without needing `--pre`: ```bash pip install scrambo==0.1.0a8 ``` That is enough to get started; run your program the normal way (`python make_reel.py`) instead of through `uv run`. --- # API reference > The complete Python SDK call surface: facades, methods, arguments, and returns. The complete Scrambo Python SDK call surface in one place. This is a lookup table, not a tutorial. For the learning path from installation to first edit, see [Start here](/python/docs/start-here). ```python from scrambo import editor, planner, source, timeline, validation from scrambo import Artifact, ValidationReport from scrambo.tools import transcribe, detect_events, detect_beats, masking from scrambo.tools.genAI import img2video, voiceover ``` Calls are synchronous. Most agent calls return an `Artifact`; `planner.ask` returns a plain string. Timeline agents edit the current revision in call order. ## `editor` — session and browser editor [#editor--session-and-browser-editor] | Call | Key arguments | Returns / effect | | ------------------------------------ | --------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | `editor.open(...)` | `project`, `input` **(required)**, `canvas=(w, h)`, `provider`, `model`, `thinking` | Creates or resumes the project session. | | `editor.start()` | — | Connects to the browser editor; call before any timeline op. Keep the tab open. | | `session.view_edit_snapshot(...)` | `edit_id=None` | Returns a detached `PublicEditorView`; call `.wait()` to wait for hydration. Defaults to the current edit. | | `session.create_render_handoff(...)` | `edit_id=None`, `request_id=None` | Waits for capture and returns the selected edit's renderer-neutral `scrambo.render-ir.v1` JSON as a `dict`. Defaults to the current edit. | | `editor.close_session()` | — | Cancels remaining work, destroys the active session, and releases server capacity. Optional — Scrambo auto-closes on program exit. | | `editor.close()` | — | Compatibility alias for `editor.close_session()`. | | `editor.export(path)` | — | **Not supported on the cloud SDK** (raises `ScramboError`). Export from the editor's Export control. | See [The session lifecycle](/python/docs/session-lifecycle) · [Supplying media and creative constraints](/python/docs/media-and-constraints). ## `source` — media before or alongside the timeline [#source--media-before-or-alongside-the-timeline] These agents do **not** change the live timeline. | Call | Key arguments | Returns | | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `source.work(prompt, ...)` | `tools=()`, `name="source-work"` | `Artifact` — the run's sole SourceBrief v2 in `result["brief"]`, plus atomically rendered derivatives with stable IDs and paths in `result["results"]`. | | `source.generate_agent.create(prompt, ...)` | `tools=[generation_tool, ...]` **(required)**; optional `masking`; `budget_usd=5.0`, `max_calls=4`, `name="generated-assets"` (1–80 chars) | `Artifact` — SourceBrief v2 over generated assets in `result["brief"]`. | See [Source agents](/python/docs/source-agents). Generation deployment ceilings are configurable and default to `$15` and 15 outputs per specialist turn. Requests above the active ceiling fail with `generation_limit_exceeded`. ## `planner` — read-only analysis and grounded planning [#planner--read-only-analysis-and-grounded-planning] | Call | Accepts | Returns | | -------------------------------------- | ------------------------------------------------- | ------------------------------------------------------------------------ | | `planner.ask(prompt, ...)` | `prompt`, `name="planner-answer"` | Plain `str`; cached automatically against current source/timeline state. | | `planner.compile(prompt, ...)` | current SourceBrief; `tools=()`, `name="plan"` | `Artifact` — grounded plan in `result["plan"]`. | | `planner.compile(answer, prompt, ...)` | answer `str`, `prompt`; `tools=()`, `name="plan"` | `Artifact` — answer is optional planning context. | `planner.ask` has read tools only and accepts no capabilities. `planner.compile` accepts `transcribe`, `detect_events`, and `masking`. See [Planning](/python/docs/planning). ## `validation` — artifact quality checks [#validation--artifact-quality-checks] | Call | Accepts | Returns | | --------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | | `validation.check(target, policy="all", ...)` | source, planner, or timeline `Artifact`; validator selection or `ValidationPolicy`; `name=` | `ValidationReport`. `all` runs every validator applicable to the target. | | `validation.applicable_to(target)` | one `Artifact` | Validator names that can inspect that artifact. | | `validation.applicability()` | — | Mapping of every validator name to its supported artifact categories. | A focused selection with no validator that can inspect the target raises; broader mixed groups are narrowed to applicable checks. Source artifacts currently have no public validators; narration semantic continuity is plan-only; the remaining public validators inspect timeline artifacts. ## `timeline` — author, refine, validate [#timeline--author-refine-validate] | Call | Accepts | Returns / effect | | -------------------------------------------- | ----------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | `timeline.author_agent.edit()` | current plan; `tools=()`, `name=` | `Artifact`; builds the planned edit from scratch as a new root revision. | | `timeline.author_agent.edit(request, ...)` | `str` \| plan `Artifact` \| `ValidationReport`; `tools=()`, `name=` | `Artifact`; promotes a new current revision. Structural editing. | | `timeline.graphics_agent.edit(request, ...)` | `str` \| `ValidationReport`; `tools=()`, `name=` | `Artifact`; shapes, panels, backings, accents. | | `timeline.sound_agent.edit(request, ...)` | `str` \| `ValidationReport`; `tools=()`, `name=` | `Artifact`; music, bundled SFX, balance, ducking. | | `timeline.titles_agent.edit(request, ...)` | `str` \| `ValidationReport`; `tools=()`, `name=` | `Artifact`; titles, cards, lower thirds, typography. | | `timeline.captions_agent.edit(request, ...)` | `str` \| `ValidationReport`; `tools=()`, `name=` | `Artifact`; transcript-aligned captions. | | `timeline.validate(policy, ...)` | validator `selection` spec or `ValidationPolicy` (not a prompt); `name=` | `ValidationReport`. | | `timeline.ensure(policy, repair_with=, ...)` | `selection`/`ValidationPolicy`; `repair_with=` one specialist; `repairs=`, `name=`, `require_passed=` | `ValidationReport`; validates, routes failures to `repair_with`, re-checks. | Refinement specialists (`graphics`/`sound`/`titles`/`captions`) require an existing timeline. Source Work, Planner Compile, and Author accept `tools=[transcribe]` or `tools=[detect_events]`; `tools=[detect_beats]` is accepted by Source Work only. `tools=[masking]` is accepted by Source Work, Generate, Planner Compile, Author, and every refinement specialist; Generate still requires at least one generation tool. See [Two ways to create the first timeline](/python/docs/first-timeline) · [Planning](/python/docs/planning) · [Timeline agents](/python/docs/timeline-agents) · [Validation and repair](/python/docs/validation-and-repair) · [Choose the right workflow](/python/docs/workflows). ## Tools (opt-in, `scrambo.tools`) [#tools-opt-in-scrambotools] | Tool | Grant / call | Notes | | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `transcribe` | `tools=[transcribe]` on Source Work/Planner Compile/author; `transcribe.set_config(provider, model, language, diarize)` | Per-call, fail-closed grant; does not persist. Calling `transcribe(...)` directly raises. Default ElevenLabs Scribe v2. | | `detect_events` | `tools=[detect_events]` on Source Work/Planner Compile/author; `detect_events.set_config(sample_density, max_frames)` | Per-call, fail-closed grant. Defaults: dense sampling, `max_frames=900` (1–2400). Calling directly raises. | | `detect_beats` | `tools=[detect_beats]` on Source Work only | Per-call, fail-closed grant with no config. Runs cached beat/onset analysis. Calling directly raises. | | `masking` | `tools=[masking]` on Source Work/generate/Planner Compile/author/graphics/sound/titles/captions | Paid, per-call, fail-closed grant with no caller configuration. Plain agents may precompute a matte; timeline agents may place one. Calling directly raises. | | `img2video(shots, ...)` | `duration`, `vendor`, `resolution`, `aspect_ratio`, `name`, `budget_usd=5.0` | `shots`: one dict, a list, or a `prompts.json` path. Each shot needs a first frame; optional `last_frame` interpolates. Returns `Artifact`. | | `voiceover(text_or_path, ...)` | `voice` **(required)**, `vendor`, `name`, `budget_usd=5.0` | Literal script text or a `.txt`/`.md` path. Returns `Artifact`. | Deterministic generation tools write media into `source/`, register it in the manifest, and reuse content-addressed outputs for free on a rerun. The opt-in capabilities (`transcribe`, `detect_events`, `detect_beats`, `masking`) are agent-only — you grant them, the specialist decides how to use them. See [Source agents](/python/docs/source-agents). ## Result types [#result-types] `Artifact` (returned by every agent call): | Field / method | Meaning | | -------------- | ---------------------------------------------------------------------------- | | `name` | Stage name your program supplied. | | `kind` | Kind of agent task that produced it. | | `execution_id` | Unique accepted execution of that task. | | `result` | Structured data, e.g. `result["brief"]`, `result["plan"]`, media records. | | `llm_invoked` | Whether a model call was required. | | `cached` | Whether Scrambo reused prior work. | | `input_ref()` | Compact compatibility reference for APIs that explicitly accept an artifact. | `ValidationReport` (returned by `validation.check` or `timeline.validate`) adds: | Field / method | Meaning | | ------------------ | ----------------------------------------------------- | | `passed` | `True` when all selected validators passed. | | `findings` | Structured findings for logging or custom routing. | | `md` | Readable Markdown report. | | `require_passed()` | Raises `ScramboValidationError` if the report failed. | Source Work, Planner Compile, and plan-backed Author hand off the current source brief and plan automatically. Keep their returned artifacts only when your own program needs to inspect or validate the result. See [Artifacts](/python/docs/artifacts). ## Validator selection spec [#validator-selection-spec] `validation.check(target, policy)` and `timeline.validate(policy)` take a comma-separated list of exact validator names or dotted group prefixes — `all`, `none`, a prefix like `edit_quality.caption`, and a leading `-` to exclude (e.g. `"all,-edit_quality.sfx"`). An unknown token raises and lists valid names. Pass a `ValidationPolicy` to bundle a selection with per-validator quality settings. ### How a check reports [#how-a-check-reports] Every validator returns structured findings. Each finding carries a **severity** (`fail` or `warn`), a plain-language message, and usually a **target** — the exact clip, caption group, or plan section it points at — plus a suggested fix. `report.passed` is `False` only if some selected check produced a `fail`; a `warn` is advisory and does not fail the report. `report.findings` is the structured list for custom routing; `report.md` is the same content rolled up as readable Markdown with fixes ordered by priority. Many checks **no-op to a pass when the layer they judge doesn't exist** (no captions authored, no plan, no sound), so selecting a group your edit didn't touch is harmless. ### `edit_contract.*` — hard invariants [#edit_contract--hard-invariants] These enforce that the edit *is what was declared*. Each is verified automatically when the owning specialist is promoted, so a caller usually selects them only as a final gate. | Validator | What it checks | Runs when | On a violation (`fail`) | | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | | `edit_contract.canvas_matches` | Timeline resolution equals the `editor.open(canvas=…)` you declared. | Always. | Reports the drifted dimensions. | | `edit_contract.duration_matches` | Final length is within tolerance of the declared target duration. | Only when a target duration was set (Planner Compile or author). Skipped silently otherwise. | Reports measured vs. target delta. | | `edit_contract.video_has_no_gaps` | The visible video track has no uncovered time from first to last shot. | Once a timeline exists. | Points at each uncovered interval. | | `edit_contract.video_matches_plan` | Video shots realize the plan's clip IDs, order, and section windows exactly. | Only with a `plan.json`. | Names the missing, extra, or misplaced shot. | | `edit_contract.captions_match_plan` | Captions realize the planned caption elements and timing. | Only when the plan declares captions. | Names the unrealized or drifted caption. | | `edit_contract.titles_match_plan` | Title/card elements match the plan's declared IDs and placements. | Only when the plan declares titles. | Names the missing or altered title. | | `edit_contract.graphics_match_plan` | Graphic elements match the plan's declared IDs and placements. | Only when the plan declares graphics. | Names the missing or altered graphic. | | `edit_contract.sound_matches_plan` | Music/SFX elements realize the planned sound layer: each SFX's transient lands on its trigger time, and each plan instruction fires its expected number of triggers. | Only when the plan declares sound. | Names the misaligned transient or the instruction's expected-vs-actual trigger count. | | `edit_contract.mask_matches_plan` | Every live mask has grounded generated-mask provenance, exact source/timeline mapping, and a valid layer stack. | On every timeline mutation; absence passes only when no mask is live. | Names orphaned media, mapping drift, or invalid compositor ordering. | | `edit_contract.fonts_loaded` | Every font family/weight/style used by text was actually loaded. | When text clips exist. | Names the unloaded face to `client.load_font(...)`. | ### `edit_quality.*` — editorial judgments [#edit_quality--editorial-judgments] Opt-in taste checks. They never run automatically — acceptance is caller-owned — and each accepts per-validator settings through `ValidationPolicy` (keyed by the concrete validator name). Their severity scales with how far off the edit is, so they emit `warn` for borderline cases and `fail` only for clear defects. | Validator | What it checks | Runs when | Typical fix | | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | `edit_quality.narration.semantic_continuity` | A provider scores whether outgoing and incoming planned narration phrases sound natural when joined. Tunable `minimum_score`, `warning_score`, `context_words`, and `include_timeline_edges`. | On a planner artifact whose ordered direct section clips resolve to word-timed transcripts. All seams are judged in one batch before Author runs. | Extend or trim the planned source windows to complete phrases, or choose a semantically continuous neighboring excerpt. | | `edit_quality.pacing.short_shots` | Shots aren't shorter than a readable hold. | On the video track. | Lengthen, merge, or replace the flashed shot. | | `edit_quality.pacing.parent_flashes` | The return to the speaker between cutaways isn't a second jump cut. | When cutaways sit over a base shot. | Hold the return longer or extend the cutaway. | | `edit_quality.coverage.broll` | Planned cutaway/B-roll coverage is present and long enough to read. | Only with a plan. | Add or lengthen the missing cover shot. | | `edit_quality.structure.plan` | Section order and boundaries follow the planned structure. | Only with a plan. | Re-order or re-time sections to the plan. | | `edit_quality.caption.cadence.minimum_duration` | Each caption stays on screen long enough to read (tunable `min_duration`). | When captions exist. | Extend or merge the short caption item. | | `edit_quality.caption.cadence.cut_clearance` | Caption groups don't straddle a shot change. | When captions exist. | Nudge the group clear of the cut. | | `edit_quality.caption.segmentation` | Words in a planned group reveal and clear together as one unit. | When captions exist. | Regroup so the phrase enters and exits together. | | `edit_quality.caption.layout.row_layout` | Caption rows sit in tidy bands with sane line spacing and word gaps. | When captions exist. | Adjust line height / row banding. | | `edit_quality.caption.layout.balance` | Groups read as horizontally centered by measured width. | When captions exist. | Re-center the group on its true width. | | `edit_quality.typography.minimum_size` | No text falls below a legible minimum size. | When text clips exist. | Raise `fontSize`. | | `edit_quality.typography.overlap` | Text rows/clips don't unintentionally overlap. | When text clips exist. | Fewer words per row, or re-measure the layout. | | `edit_quality.sfx.density` | SFX trigger cadence is neither too sparse nor too busy. | When a sound plan exists. | Adjust trigger cadence. | | `edit_quality.sfx.levels` | Music/SFX gain sits in a sane mix under narration. | When a sound plan exists. | Adjust gain or ducking. | See [Validation and repair](/python/docs/validation-and-repair). --- # Start with the smallest useful program > The shortest path: open a session and author a timeline from one prompt. For a straightforward edit, the timeline author can inspect the media and work directly from one prompt: ```python from scrambo import editor, timeline editor.open( project="launch-reel", input="./footage", canvas=(1080, 1920), ) editor.start() timeline.author_agent.edit( "Create a polished 20-second vertical launch reel. Open on the product " "reveal, alternate detail shots with people using it, keep the original " "dialogue where it is useful, and finish on the clean logo shot. Use " "confident hard cuts and restrained background music.", name="roughcut", ) report = timeline.validate("edit_contract,edit_quality.typography", name="final-check") report.require_passed() ``` You do not close the session yourself: Scrambo closes it automatically when the program ends, whether it finishes normally or stops on an error. Calls are synchronous: when an agent call returns, its accepted result is ready for the next call. Timeline agents edit the current revision in sequence, so the order of your calls is the order of the handoff. --- # Two ways to create the first timeline > Direct authoring versus the source work, planner, and author workflow. ### 1. Direct authoring [#1-direct-authoring] `timeline.author_agent.edit(str)` is the shortest path. On its first call it can scout the supplied media automatically and build a complete timeline: ```python timeline.author_agent.edit( "Make a 30-second 16:9 conference recap. Use keynote audio as the narrative " "spine, cover jump cuts with audience and venue B-roll, and end with the " "speaker's invitation to next year's event.", name="conference-roughcut", ) ``` Use this when the request is cohesive and you do not need to inspect or reuse a source brief or edit plan separately. If a timeline already exists, the same string form edits the current revision: ```python timeline.author_agent.edit( "Shorten the opening by two seconds, keep the speaker's full final sentence, " "and replace the weakest audience cutaway.", name="pacing-revision", ) ``` ### 2. Source work, planner, and author [#2-source-work-planner-and-author] The structured workflow splits editorial decisions into three stages: ```python from scrambo.tools import transcribe source.work( "Analyze the interviews and B-roll for a 45-second customer story. " "Transcribe interview_maya.mov. Find a concise problem statement, one " "specific outcome, a warm closing line, and B-roll that visibly supports " "each claim.", tools=[transcribe], name="customer-source-brief", ) planner.compile( "Plan a 45-second landscape story: 0-4s visual hook, then problem, solution, " "measurable outcome, and closing invitation. Preserve Maya's sentence " "boundaries, use B-roll as cutaways over dialogue, and leave the final two " "seconds visually clean for a logo.", name="customer-plan", ) timeline.author_agent.edit(name="customer-roughcut") ``` The responsibilities are deliberately different: * Source work identifies grounded source windows, renders any explicit mechanical derivatives, and requests transcription or deeper visual analysis when the brief needs it and the program grants the corresponding capability. * The planner turns accepted evidence into an ordered, timed narrative plan. * The author translates the plan into the live editor timeline. The specialists hand off through `source_brief.json` and `plan.json` in the current run directory. Their returned artifacts remain available when your own program needs to inspect or validate an intermediate result. --- # Writing Scrambo Programs > How a Python program directs Scrambo's team of video-editing agents. Scrambo lets a Python program direct a team of video-editing agents. Your program supplies the creative intent, passes work between specialists, and decides what must be validated. Scrambo inspects the real media and builds the timeline in a browser editor. Most programs use five facades: ```python from scrambo import editor, planner, source, timeline, validation ``` * `editor` owns the editing session and browser editor. * `source` understands, prepares, or generates media. * `planner` answers read-only editorial questions and compiles grounded edit plans. * `timeline` authors, refines, and validates the edit. * `validation` checks source, plan, or timeline artifacts with only the validators applicable to that artifact. Some steps also use **tools** — optional capabilities you import from `scrambo.tools` and grant to a specialist for a single call. For example, `transcribe` lets an agent read the spoken words in your media, `detect_events` lets it watch a clip and mark what happens, `detect_beats` lets Source Work find the rhythm of a track, `masking` prepares or places a paid subject matte, and the generation tools `img2video` and `voiceover` create new footage or narration. Sound effects are built into `timeline.sound_agent` rather than exposed as a separate public tool. Tools are opt-in and covered in detail later; the idea to hold onto now is that a specialist can use a tool only when you explicitly give it one. A Scrambo program is the Python script you write. Scrambo may generate its own low-level editing code while carrying out your requests, but that is an implementation detail; you do not write or manage it. This guide focuses on the API and on composing agents. Install the SDK and sign in once (below) before running your first program. --- # Choose the right workflow > Match the direct or structured workflow to what your edit actually needs. You do not need every agent in every program. | Need | Recommended workflow | | --------------------------------------------------------------------------- | ------------------------------------------------------------------------ | | A quick edit from a clear request | `author_agent.edit("...")` | | Careful source selection and narrative control | `source.work` → `planner.compile` → author | | Explore editorial directions without changing files or the timeline | `planner.ask` | | Semantically choose and render multiple trims, frames, or other derivatives | `source.work` | | Transcript-aware selection, planning, or direct editing | grant `tools=[transcribe]` to the specialist that needs it | | Grounding on-screen actions, zoom targets, or pacing from footage | grant `tools=[detect_events]` to Source Work, Planner Compile, or author | | Cutting or pacing to the music | grant `tools=[detect_beats]` to Source Work | | Put text/graphics behind a subject or build another matte-based composite | grant `tools=[masking]` to the agent that precomputes or places it | | Add editorial sound effects | ask `timeline.sound_agent` directly; SFX is intrinsic to that specialist | | Explicit mechanical transcodes, trims, or extracted audio | `source.work` (render + brief) → `planner.compile` → author | | Titles, captions, graphics, or sound as distinct creative passes | author → one or more refine agents | | A quality gate with automatic repair | validate → pass report to an edit agent → validate again | | Create voiceover or image-to-video footage deterministically | `scrambo.tools.genAI` → Planner Compile/author | | Ask a specialist to decide which generated assets are needed | generate specialist → Planner Compile → author | Use the short direct-author path when a single prompt expresses the edit well. Use the structured source/planner path when source choices, transcript evidence, section timing, or narrative order deserve their own reviewable handoffs. --- # The session lifecycle > Open, inspect, hand off, close, and export a Scrambo session. A typical program follows this arc: 1. `editor.open(...)` creates or resumes a project session. 2. `editor.start()` connects the program to the browser editor. 3. Source agents inspect or prepare the media, if needed. 4. Timeline agents create and refine the edit. 5. `timeline.validate(...)` checks the current revision. 6. Optionally open a detached editor snapshot or create renderer-neutral JSON. 7. The session closes automatically when the program ends. Scrambo registers the session to close automatically when your program exits — whether it finishes normally or stops on an uncaught error — so you do not need a `try`/`finally` or an explicit `editor.close_session()`. Call `editor.close_session()` only when you want to destroy the session and release its server capacity before the program ends. One Python process has one active Scrambo session. Facade calls use that active session implicitly; you do not pass a session object between agents and should not try to edit two projects concurrently in one process. ### `editor.open(...)` [#editoropen] ```python session = editor.open( project="customer-story", input="./media", canvas=(1920, 1080), provider="codex", model=None, thinking="high", ) ``` | Argument | Meaning | | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `project` | A stable project name. Reusing it lets Scrambo reuse matching work where the runtime supports resumable projects. | | `input` | **Required.** A media file, or a directory of top-level video, audio, and images plus optional transcript-candidate JSON. A cloud session needs at least one supported media file (video `.mp4/.mov/.m4v/.mkv/.avi/.webm`, audio `.mp3/.wav/.m4a/.aac/.flac/.ogg`, images `.jpg/.jpeg/.png/.gif/.webp/.heic`); JSON is ancillary only. Limits: at most 20 top-level files and 500 MB in total, with unique file names. Only top-level files are uploaded; nested directories are ignored. | | `canvas` | `(width, height)` in pixels. Common choices are `(1080, 1920)` for vertical, `(1920, 1080)` for landscape, and `(1080, 1080)` for square. | | `provider` | Optional agent provider. Omit it to use the environment's default. `echo` may be available as a deterministic test provider. | | `model` | Optional provider model identifier. Omit it unless the environment exposes a specific choice. | | `thinking` | Optional reasoning level supported by the selected provider. More reasoning can help with complex, evidence-heavy edits but usually takes longer. | Pin `canvas` when format matters. If you omit it, planning and authoring agents infer a format from your prompt and fall back to the source media when the request is ambiguous. The `project` name identifies work and cached artifacts; the `name=` on each agent call identifies a particular stage inside that project. Use stable, descriptive names such as `source-brief`, `plan`, `roughcut`, and `final-check`. ### `editor.start()` and `editor.close_session()` [#editorstart-and-editorclose_session] `editor.start()` opens or attaches to the browser editor and waits until it is ready. Call it before any operation that reads or changes the live timeline. Keep the editor tab open while the program is running. You normally do not call `editor.close_session()`. Scrambo destroys the active session automatically when your program exits, whether it finishes normally or stops on an uncaught error. Call `editor.close_session()` yourself to cancel any remaining operation and release server capacity before the program ends. After the call returns, the same owner or another user can start a new session. The finished edit remains available for preview and export in the browser editor. `editor.close()` remains available as a compatibility alias. ### View an edit snapshot [#view-an-edit-snapshot] After a timeline edit, `session.view_edit_snapshot()` opens a detached browser view of the current durable revision. Pass `edit_id=authored.edit_id` to select a specific revision. Open `view.view_url`, then call `view.wait()` to wait for the editor to finish hydrating. ```python authored = timeline.author_agent.edit("Create a concise caption reel") view = session.view_edit_snapshot(edit_id=authored.edit_id) print(view.view_url) view.wait() ``` The view is independent of the private agent editor. Manual changes in it do not become durable Scrambo revisions. ### Create a render handoff [#create-a-render-handoff] Call `session.create_render_handoff()` after authoring a timeline to capture the current durable edit as renderer-neutral `scrambo.render-ir.v1` JSON. The call waits for capture and downloads the complete manifest as a Python dictionary. ```python authored = timeline.author_agent.edit("Create a concise caption reel") handoff = session.create_render_handoff(edit_id=authored.edit_id) ``` Omit `edit_id` to use the current edit. Pass a stable `request_id` only when you need idempotent retries of the same capture request. ### Export [#export] Export the finished video with the browser editor's Export control. Programmatic `editor.export(path)` is not supported by the cloud SDK — calling it raises a `ScramboError` — so do not rely on it in a cloud program. --- # Source agents > Semantic source work, analysis capabilities, and source generation. Source agents operate on media before or alongside timeline planning. They do not change the live timeline. ### `source.work(...)` [#sourcework] ```python brief = source.work(prompt, tools=(), name="source-work") ``` Source Work is the single source-analysis entry point. It probes the real media, builds a visual inventory, and authors the run's sole grounded, asset-agnostic SourceBrief v2 — the `source_brief.json` that `planner.compile` consumes. In the same pass it runs the safe preparation renderer: the specialist may choose any number of grounded `transcode`, `extract_audio`, `trim`, `concat`, `mux`, or `extract_frame` jobs, then publishes the accepted batch atomically under `source/`. Its `Artifact.result` includes output paths and stable job IDs; paired IDs such as `kitchen-wide-first` and `kitchen-wide-last` can identify a first/last frame pair. Opt-in evidence capabilities are granted per call: transcript import and computation with `tools=[transcribe]`, grounded video events with `tools=[detect_events]`, beat/onset analysis with `tools=[detect_beats]`, and reusable matte precomputation with `tools=[masking]`. See [Agent-only capabilities](#agent-only-capabilities). Generation tools remain exclusive to `source.generate_agent.create`. The brief contains `schemaVersion: 2` and `selections[]`. Each selection names a manifest source, its matching `kind` (`video`, `audio`, `image`, `graphic`, or `text`), a free-form editorial `role`, and an optional `span` for video/audio. Whole temporal assets use `span: null`; image, graphic, and text selections always use `span: null`. Roles such as `opener`, `montage`, `ender`, `b_roll`, `narration`, `audio_bed`, `title_card`, and `overlay` are useful conventions, not required values. A useful source-work prompt says: * what each important file is for; * which speech source should be transcribed; * the roles to find, such as hook, establishing shot, proof, reaction, detail, transition, or closing image; * selection criteria such as camera stability, subject visibility, continuity, and unwanted content; * whether source order matters; * any explicit mechanical derivatives to render (working copies, trims, extracted audio, concatenations), naming inputs, outputs, formats, and ordering. Keep mechanical preparation requests explicit. Do not ask Source Work to design titles or build the final timeline. Its job is to find, explain, and prepare usable evidence in the source material, then hand a grounded brief to the planner. ### Agent-only capabilities [#agent-only-capabilities] Some evidence work is too expensive or too provider-specific to run on every pass, so it is opt-in per specialist call. You import a capability descriptor from `scrambo.tools` and grant it explicitly for one call: ```python from scrambo.tools import transcribe, detect_events, detect_beats, masking ``` | Capability | Import | Granted to | Produces | | --------------- | --------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | | `transcribe` | `scrambo.tools` | Source Work, Planner Compile, author | Working transcript from candidates or fresh transcription. | | `detect_events` | `scrambo.tools` | Source Work, Planner Compile, author | Grounded video event evidence. | | `detect_beats` | `scrambo.tools` | Source Work | Beat/onset analysis for selection or preparation decisions. | | `masking` | `scrambo.tools` | Source Work, generate, Planner Compile, author, graphics, sound, titles, captions | A cached video matte with generated-mask provenance; timeline agents may also place it. | Every grant is **fail-closed and per call**. It does not persist to later agents. Masking is the only capability accepted by the layer refinement specialists, and generate accepts masking only alongside at least one generation tool. Pass the descriptor again whenever a downstream specialist independently needs it. Calling a capability like `transcribe(...)`, `detect_events(...)`, or `masking(...)` directly always raises — the specialist decides which evidence is relevant and the host performs the work. #### Video masking [#video-masking] Grant `masking` when an agent needs a subject matte. A Source Work, generate, or Planner Compile turn may precompute and cache the matte without changing the timeline. A later timeline specialist can reuse the exact request without a new remote submission or customer charge: ```python from scrambo.tools import masking brief = source.work( "Prepare a person mask for presenter.mp4 from 1.0s to 3.5s.", tools=[masking], ) timeline.titles_agent.edit( "Put the opening title behind the presenter from 1.0s to 3.5s.", tools=[masking], name="masked-title", ) ``` Masking is paid and has no caller-configurable fields. Scrambo enforces fixed operator ceilings, disables automatic retries for the paid turn, records the managed service's returned USD estimate unchanged, and applies no additional markup. A timeline mask is promoted only after its full placement contract and layer ordering pass deterministic validation. #### Agent-directed transcripts [#agent-directed-transcripts] Import the agent-only capability descriptor and grant it to the Source Work, Planner Compile, or author that needs transcript evidence: ```python from scrambo.tools import transcribe brief = source.work( "Find the strongest explanation in interview_maya.mov and the B-roll that " "supports it.", tools=[transcribe], name="interview-brief", ) ``` Defaults are ElevenLabs Scribe v2, automatic language detection, speaker diarization, and mandatory word timestamps. Configure the descriptor before the agent call when the defaults are not appropriate: ```python # Pin a language or disable diarization while keeping ElevenLabs Scribe v2. transcribe.set_config(language="en", diarize=False) # Select the explicit WhisperX fallback and its default model. Other settings # retain their current values unless reset explicitly. transcribe.set_config(provider="whisperx", language=None, diarize=True) ``` Configuration applies to subsequent grants in the current Python process. Supported keys are `provider`, `model`, `language`, and `diarize`; word timestamps cannot be disabled. Set every non-default choice before making the agent call so the effective configuration is explicit in that task's cache identity. Top-level `.json` files supplied beside media are uploaded as untrusted transcript candidates. They are not automatically associated with a source and are never treated as transcripts merely because of their filename. An enabled agent first inspects the candidates and may ask the host to validate and import one; only if no candidate fits should it compute a transcript for selected audio or video. The importer accepts Scrambo word-timed `segments[].words[]` JSON and native ElevenLabs `words[]` JSON, normalizes the selected evidence, preserves the uploaded raw payload, and publishes the working transcript used downstream. Grant transcript access at the earliest specialist that needs it. For example, grant it to Source Work when quotes determine source selection, to Planner Compile when word timing affects narrative structure, or to a direct author when there is no separate source-work/planner pass. The caption specialist consumes the working transcript established upstream; it cannot create one itself. #### Video event detection [#video-event-detection] Grant `detect_events` when a specialist should *watch* a clip and record what happens in it. It works on any footage — screen recordings, browser, mobile, or app captures, games, design tools, or camera footage: ```python from scrambo.tools import detect_events brief = source.work( "Watch onboarding_screen_capture.mov and mark each step the user completes: " "sign-up, workspace creation, and first invite. Note which region of the " "frame the action happens in.", tools=[detect_events], name="onboarding-brief", ) ``` The capability deterministically samples the named clips into cached, timestamped contact sheets; the selected specialist provider then watches those sheets and authors the events. No separate vision provider is called, and the specialist samples only the clips the request names — not the whole `source/` folder — so name the footage you care about. Each event lands in the SourceBrief with an `importance` and `confidence` score (both `0.0`–`1.0`), an optional normalized `focusRegion` marking where in the frame it happens, and a `paceHint` recommending how to play that moment back (`realtime`, `speed_up` with an optional `1`–`16` speed, or `cut`). When your prompt names specific event types, the brief records a request-scoped `eventTaxonomy` and tags each event with a stable `typeId`; open-ended discovery requests omit the taxonomy and leave `typeId: null`. Downstream planner and author passes read these events from the accepted `source_brief.json`; grant `detect_events` again if a later pass needs to watch footage the source-work pass did not. Tune sampling density before the call when the defaults (dense sampling, capped at 900 frames) are not appropriate: ```python # Coarser sampling with a lower frame ceiling for a long recording. detect_events.set_config(sample_density="sparse", max_frames=400) ``` Supported keys are `sample_density` (`"sparse"` or `"dense"`) and `max_frames` (an integer from `1` to `2400`). As with `transcribe`, set every non-default choice before the agent call so the effective configuration is explicit in that task's cache identity. #### Beat and onset detection [#beat-and-onset-detection] Grant `detect_beats` when source selection or pacing should follow the music. It is **Source Work-only** and takes no configuration — Source Work chooses the beat selection parameters per call: ```python from scrambo.tools import detect_beats brief = source.work( "Analyze the beats in soundtrack.wav and pick the strongest downbeats to " "cut the montage on. Note the tempo and any section changes.", tools=[detect_beats], name="montage-brief", ) ``` The capability runs deterministic, cached beat/onset analysis over a manifest audio source (or a video source that carries audio) and preserves the full result at that source's `manifest.sources[].beats` path so later work can recover the raw numbers. Source Work promotes its interpretation into the SourceBrief as a typed, source-relative `beatGrid` — a `tempoBpm`, the `selectedBeats` it chose, and optional structural `sections` — which the planner and author consume to align cuts and rhythm. ### Deterministic generation tools [#deterministic-generation-tools] Import deterministic generation separately from the source-agent facade: ```python from scrambo.tools import masking from scrambo.tools.genAI import img2video, voiceover ``` Both tools write media into `source/`, register it in `manifest.sources[]`, and return a normal `Artifact`. Matching requests reuse content-addressed outputs. Each direct call defaults to a `$5` spend cap, and cached work costs nothing. These calls do not require `editor.start()` unless the program proceeds to timeline editing. In the thin cloud SDK, vendor credentials stay on the Scrambo server. Local `.json`, `.txt`, and `.md` control files are read by the SDK and canonicalized before submission. Every first/last frame must have been part of the initial `editor.open(input=...)` upload; cloud generation does not late-upload frames in this MVP. If a generation control `.json` is also top-level inside the initial input directory, it is uploaded as a transcript candidate as well; keep control files outside that directory when they are not intended as run evidence. Create voiceover from literal text or a `.txt`/`.md` file: ```python voice = voiceover( "A quieter way to start the day.", voice="rachel", name="brand-voiceover", budget_usd=5.0, ) ``` Create one or more motion clips from text and optional first/last images: ```python footage = img2video( [ { "name": "coffee-pour", "seed_image": "./stills/cup.jpg", "prompt": "Slow macro push-in as coffee pours into the ceramic cup; " "preserve the cup design and morning window light.", "duration": 4.0, }, { "name": "pack-shot", "seed_image": "./stills/package.jpg", "last_frame": "./stills/logo-lockup.jpg", "prompt": "Controlled tabletop dolly ending on the supplied logo frame; " "keep all packaging text unchanged.", "duration": 4.0, }, ], resolution="720p", aspect_ratio="16:9", name="generated-product-shots", ) ``` `img2video` accepts one shot dictionary, a list, or a compatible `prompts.json` path. Every shot requires a first-frame image; an optional last frame requests interpolation. Its call-level arguments include `duration`, `vendor`, `resolution`, `aspect_ratio`, `name`, and `budget_usd`; a per-shot duration overrides the default. Text-to-video is intentionally outside this MVP. `voiceover` accepts literal script text or a `.txt`/`.md` path and requires a `voice`. Its optional arguments are `vendor`, `name`, and `budget_usd`. Unknown Veo pricing is rejected before vendor submission. ElevenLabs voiceover remains available as usage-only unless the operator configured a plan-rate snapshot; submitted characters remain measurable even when USD is unknown. If a cached artifact points to a missing output, Scrambo runs the deterministic kernel again; a surviving content-addressed file is adopted and re-registered without spending. ### `source.generate_agent.create(...)` [#sourcegenerate_agentcreate] Use the specialist when the prompt should decide what to generate: ```python from scrambo import planner, source, timeline from scrambo.tools.genAI import img2video, voiceover source.generate_agent.create( "Create a narrated vertical tour from the listing photos.", tools=[img2video, voiceover, masking], budget_usd=5.0, max_calls=4, name="generated-assets", ) planner.compile("Build a 30-second tour") timeline.author_agent.edit() ``` The specialist receives only the registered Scrambo tool callables explicitly listed in `tools`; arbitrary Python functions are rejected. At least one generation tool is required, while `masking` may be added for matte precomputation. The specialist inspects seed images, creates only requested assets, inspects generated video contact sheets, and writes one complete SourceBrief v2. It does not create a generation-specific root contract. `budget_usd` is cumulative across uncached vendor work in that `create()` turn. `max_calls` is the cumulative requested-output cap, not an LLM iteration count: a batch of three video requests consumes three, and a cached output still consumes one slot while spending zero dollars. The default is four outputs. `name` must contain 1–80 characters. Deployment ceilings are configurable and default to `$15` and 15 outputs; requests above the active ceiling fail with `generation_limit_exceeded`. Budget, output-cap, unknown-price, and generation-tool errors fail the task. There is no automatic specialist revision after the paid turn; successful outputs remain content-addressed and can be adopted free on a rerun. The portable cloud SDK exposes the same registered tool identities and sends only their stable allowlisted IDs to the server. --- # Planning > Ask read-only editorial questions and compile grounded, timed edit plans. ```python answer = planner.ask(question, name="planner-answer") plan = planner.compile(prompt, tools=(), name="plan") # Or use a read-only answer as planning context: plan = planner.compile(answer, prompt, tools=(), name="plan") ``` `planner.ask` returns a plain string and cannot write files, compute new analysis, create source assets, or change the timeline. Its answer is cached against the prompt and current source/timeline state. Compile reads the current `source_brief.json` from the run directory, grounds it against `manifest.json`, and fingerprints both files so either source selection or generated-source changes invalidate a cached plan. It returns a structured plan with a canvas, total duration, ordered sections, and grounded source windows. You do not pass the Source Work artifact into this call. Pass `tools=[transcribe]` when compilation itself needs transcript evidence, or `tools=[detect_events]` when it must watch a clip and ground events that an earlier pass did not establish. Grant `tools=[masking]` when compilation should precompute a reusable matte; planning never places it or writes a timeline mask contract. Every grant is fail-closed and applies only to that planning call. See [Agent-only capabilities](/python/docs/source-agents#agent-only-capabilities). Use its prompt to define the edit's architecture: * target duration and orientation; * section order and approximate timing; * which audio is the narrative spine; * when B-roll should cover dialogue; * montage rhythm or beat relationship; * where titles, captions, or visual breathing room are needed; * required opening and closing behavior. Avoid vague requests such as `make it cinematic`. Say what cinematic means for this piece: perhaps slow establishing shots, longer holds, low transition density, natural sound at scene changes, and no animated text. --- # Timeline agents > The author and the graphics, sound, titles, and captions refinement specialists. Every timeline agent returns an `Artifact` and promotes a new current revision. Later timeline calls build on that current revision. Calling Author with no request builds the current `plan.json` as a new root edit. ### `timeline.author_agent.edit(...)` [#timelineauthor_agentedit] The author accepts four request forms: | Request | Effect | | --------------------- | ------------------------------------------------------------------------------------------ | | No request — `edit()` | Builds the complete current `plan.json` from scratch. Call `planner.compile(...)` first. | | `str` | Creates a timeline directly, or revises the current timeline from a free-form instruction. | | Planner `Artifact` | Builds the complete rough cut described by `result["plan"]`. | | `ValidationReport` | Repairs the report's timeline revision while preserving work that already passes. | The author owns structural editing: selecting and trimming clips, arranging tracks, preserving or muting source audio, placing cutaways, creating montages, and making timeline-wide repairs. All four request forms accept `tools=[transcribe]`, `tools=[detect_events]`, and `tools=[masking]`. Use `transcribe` for a direct-author workflow that must discover spoken content or a repair that truly needs new transcript evidence, and `detect_events` when the author must watch a clip and ground events itself; omit them when the accepted brief/plan already contains what the author needs. Both grants are fail-closed and per call — see [Agent-only capabilities](/python/docs/source-agents#agent-only-capabilities). `masking` grants paid matte generation/reuse for that turn. `detect_beats` is Source Work-only and is not accepted here. ### Refinement specialists [#refinement-specialists] Refinement specialists require an existing timeline. Each exposes `.edit(request, *, tools=(), name=None)` and accepts either a string or a `ValidationReport`. | Agent | Best used for | | ------------------------- | ----------------------------------------------------------------------- | | `timeline.graphics_agent` | Shapes, panels, backings, visual accents, and other graphic elements. | | `timeline.sound_agent` | Music treatment, sound effects, audio balance, and sound-design timing. | | `timeline.titles_agent` | Opening titles, section cards, lower thirds, end cards, and typography. | | `timeline.captions_agent` | Transcript-aligned spoken-word captions and caption styling. | Example layered pass: ```python timeline.titles_agent.edit( "Add a restrained opening title for 'Field Notes' and a two-line end card. " "Use warm white type, generous margins, and no other text.", name="titles", ) timeline.captions_agent.edit( "Caption only the interview speech. Use sentence-aware two-line groups, " "highlight the current word, keep clear of faces, and do not caption music.", name="captions", ) timeline.sound_agent.edit( "Add subtle transitions at the three section changes. Keep dialogue dominant, " "duck music under speech, and avoid effects during the closing sentence.", name="sound-design", ) ``` Give each specialist only the work it owns. A caption request should not ask for a recut; a sound request should not redesign titles. If a refinement reveals a structural problem, send that change to the author first. Every refinement specialist accepts `tools=[masking]` for an explicitly requested cross-layer mask effect. The specialist may coordinate the layer stack needed for that effect while preserving unrelated layers. Mask placement is full-state and transactionally validated on every later timeline mutation, even when that later call is not granted masking. Sound effects themselves stay intrinsic to `sound_agent`; there is no public SFX tool or endpoint. Order refinement passes intentionally. For example, lock the picture before caption timing and sound alignment, and make large structural repairs before fine typography adjustments. --- # Validation and repair > Run deterministic validators and route findings to the right specialist. ```python report = timeline.validate("all,-edit_quality.sfx", name="quality-check") ``` Validation checks the current timeline deterministically and returns a `ValidationReport`. Pass either a validator selection spec or an immutable, serializable `ValidationPolicy` — never a natural-language prompt. A selection is a comma-separated list of exact validator names or dotted group prefixes: ```python timeline.validate("all") timeline.validate("edit_quality.caption") timeline.validate("all,-edit_quality.sfx") timeline.validate("edit_contract.duration_matches") ``` * `all` means every registered validator. * `none` selects no validators. * A prefix such as `edit_quality.caption` selects that whole quality group. * A leading `-` excludes a name or group. * With exclusions but no positive token, selection starts from all validators. * An unknown token raises an error and lists the valid names. ## Validator catalog [#validator-catalog] Validators split into two families. **`edit_contract.*`** checks are hard invariants of a correct edit — the caller's declared canvas, target duration, and plan are realized exactly. **`edit_quality.*`** checks are editorial judgments you opt into and tune. | Group | What it checks | | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `edit_contract.*` | Canvas, optional target duration, plan realization, no video gaps, captions/titles/graphics/sound realization, and loaded fonts. | | `edit_quality.pacing.*` | Short shots and parent flashes, from both the live edit and the plan. | | `edit_quality.caption.*` | Cadence, segmentation, row layout, and balance. | | `edit_quality.typography.*` | Minimum text size and overlap. | | `edit_quality.sfx.*` | Density and mix levels. | | Other quality | `edit_quality.coverage.broll`, `edit_quality.structure.plan`. | `edit_contract.duration_matches` is skipped when the project has no declared target duration. For a meaningful duration gate, give the planner or direct author an explicit target duration. `program.safe` and `program.executable` are mandatory specialist-transaction verification — they are never selectable through `timeline.validate()`. A specialist is promoted only after those checks and its relevant `edit_contract.*` invariants pass. Specialists never run `edit_quality.*` automatically: editorial acceptance is caller-owned, which is what the rest of this page is about. ## Policies with settings [#policies-with-settings] For quality checks you want to tune, build a `ValidationPolicy`. It bundles a selection with per-validator settings, validates both at construction time, and is immutable and serializable so you can reuse it across runs. ```python from scrambo import ValidationPolicy policy = ValidationPolicy( "edit_quality.caption,edit_quality.typography", settings={ "edit_quality.caption.cadence.minimum_duration": {"min_duration": 0.25}, }, ) report = timeline.validate(policy, name="quality-check") ``` Settings are keyed by a concrete `edit_quality.*` validator and use that validator config's field names. Contract and program checks do not accept caller settings — passing settings for them raises. ## Inspecting a report [#inspecting-a-report] ```python if report.passed: print("ready") else: print(report.md) for finding in report.findings: print(finding) ``` Call `report.require_passed()` to fail the program when any selected validator fails. ## The repair loop [#the-repair-loop] To attempt repair, hand the failing report to the specialist that owns the problem, then re-check. `timeline.ensure()` makes this caller-owned loop transparent while keeping routing explicit: ```python report = timeline.ensure( "edit_quality.caption,edit_quality.typography", repair_with=timeline.captions_agent, repairs=2, name="caption-quality", require_passed=True, ) ``` `ensure()` runs the policy, hands any failure to the one `repair_with` specialist, and revalidates — up to `repairs` times. Stages are deterministically named `caption-quality-repair-N` and `caption-quality-recheck-N`. It performs no routing inference: choose the one specialist that owns the selected quality policy. With `require_passed=False` (the default), an exhausted loop returns the final failed report for custom handling. Use a focused specialist for focused findings — `captions_agent` for caption layout or timing, `sound_agent` for mix levels, `titles_agent` for typography. Use `author_agent` for cross-cutting or structural findings. If you want the loop spelled out instead of `ensure()`: ```python selection = "edit_contract,edit_quality.caption,edit_quality.typography" report = timeline.validate(selection, name="quality-check") for attempt in range(2): if report.passed: break timeline.author_agent.edit(report, name=f"quality-repair-{attempt + 1}") report = timeline.validate(selection, name=f"quality-check-{attempt + 2}") report.require_passed() ``` --- # Artifacts > Immutable Artifact and ValidationReport results and how caching uses them. Agent calls return immutable `Artifact` objects. They are useful results for inspection, validation, logging, and compatibility APIs. The normal Source Work → Planner Compile → Author flow hands off through the current run directory, so it does not require passing artifacts between those calls. ```python from scrambo import Artifact, ValidationReport ``` | Field or method | Meaning | | --------------- | ------------------------------------------------------------------------------------------------ | | `name` | The stage name supplied by your program. | | `kind` | The kind of agent task that produced it. | | `execution_id` | The unique accepted execution of that task. | | `result` | Structured result data, such as `result["brief"]`, `result["plan"]`, or generated media records. | | `llm_invoked` | Whether this result required a model call. | | `cached` | Whether Scrambo reused matching prior work. | | `input_ref()` | A compact compatibility reference for APIs that explicitly accept an artifact. | `ValidationReport` adds: | Field or method | Meaning | | ------------------ | ----------------------------------------------------------- | | `passed` | `True` when all selected validators passed. | | `findings` | Structured findings suitable for logging or custom routing. | | `md` | A readable Markdown report. | | `require_passed()` | Raises `ScramboValidationError` if the report failed. | Stable prompts and stable stage names allow Scrambo to reuse work when inputs and upstream artifacts have not changed. A changed prompt, media input, base timeline revision, model setting, or upstream artifact creates new work. Treat caching as an optimization, not as program state: always express the complete intent needed by the call. --- # Prompting each agent well > Assign decisions to the right specialist and make constraints testable. A strong Scrambo program assigns decisions to the right specialist and makes constraints testable. ### Include concrete editorial requirements [#include-concrete-editorial-requirements] Useful prompt ingredients include: * deliverable and audience; * exact or bounded duration; * canvas or orientation; * named source roles and required assets; * narrative order; * pacing and cut style; * audio policy; * text that must appear exactly; * accessibility requirements; * explicit exclusions; * a clear definition of the ending. Instead of: ```text Make a cool social video. ``` Prefer: ```text Create a 12-second 9:16 teaser for first-time runners. Start with the shoe touching pavement, reveal the group by 3 seconds, cut on the next four drum beats, and hold the smiling finish for the final 2 seconds. Use no dialogue, no captions, and no more than one title: RUN TOGETHER. ``` ### Repeat critical constraints at handoffs [#repeat-critical-constraints-at-handoffs] The planner receives the source brief, and the author receives the plan, but specialists do not share hidden conversational memory. Put selection evidence in the source-work prompt, structure in the planner prompt, and styling in the relevant refinement prompt. Repeat non-negotiable requirements—duration, required wording, source-audio policy—where the responsible agent needs them. ### Ask for observable outcomes [#ask-for-observable-outcomes] Prefer `keep captions within two lines and clear of faces` over `make captions nice`. Prefer `duck music beneath every spoken section` over `balance the audio`. Concrete outcomes give both the editing agent and validators something to act on. --- # Common mistakes > Frequent pitfalls to avoid when writing Scrambo programs. * Calling a facade before `editor.open(...)`. * Editing the timeline before `editor.start()` has connected the browser. * Calling a refinement specialist before a first timeline exists. * Passing a plain dictionary where Planner Compile or Author expects an `Artifact`. * Asking Source Work to edit the timeline, or a caption specialist to restructure the whole video. * Hiding the narration source or target duration in vague language. * Asking for transcript-based decisions without granting `tools=[transcribe]`, event-grounded decisions without `tools=[detect_events]`, or beat-driven cutting without `tools=[detect_beats]`, to the specialist that must make them. * Granting `tools=[detect_beats]` to Planner Compile or Author — it is accepted only by Source Work. * Expecting `planner.ask` to write files, compute analysis, prepare media, or change the timeline; it is deliberately read-only. * Reaching for a separate scout or prepare agent; `source.work` is the single entry point for semantic selection and mechanical rendering, and it authors the run's sole source brief. * Calling a capability like `transcribe(...)` or `detect_events(...)` directly, or assuming a per-call grant persists to the next specialist. * Calling `masking(...)` directly, passing it caller configuration, or assuming a precomputed matte is live before a timeline specialist places it. * Looking for a public SFX tool; ask `timeline.sound_agent` for sound design. * Assuming an uploaded JSON file is automatically associated with similarly named media; it remains an untrusted candidate until an enabled agent imports it. * Passing prose to `timeline.validate(...)`; it accepts a selection spec or a `ValidationPolicy`, not a natural-language prompt. * Assuming every validation group applies to every edit. Select checks relevant to the layers you actually created. * Closing the browser tab during a run. * Depending on programmatic export in code meant to run against the cloud API. The most reliable programs are explicit about outcomes, keep agent responsibilities narrow, pass artifacts directly, and validate the properties that matter for the deliverable.