---
name: starter
description: "Get a predictable, reproducible result out of Livepeer Agent on the first run"
agent_rule: "Pin the model, pin the tier, verify the artifact, hand back one file — never report a substitute as a success."
task: [render, animate, compose, edit]
domain: [image, video, audio]
persona: [director, editor]
scope: epic
---
# Starter — how to get the same good result twice

Livepeer Agent runs 200+ capabilities across a GPU network. That breadth is the
product's strength and also the reason two runs of "the same" brief can differ:
unless you say otherwise, the system is allowed to choose for you, and it will
choose differently as models, load and health change.

This skill is the **determinism contract**. Follow it and a run is reproducible,
its cost is knowable before you spend it, and the thing you get back is a file
rather than a scavenger hunt. Read it once before your first render.

---

## 1. The four things that make a run reproducible

A run is reproducible when all four are pinned. Pin them in this order — each one
removes a whole class of variance.

| # | Pin | How | What drifts without it |
|---|-----|-----|------------------------|
| 1 | **The model** | `model_override: "kling-v3-turbo-i2v"` | Health-based routing and keyword resolution can select a sibling |
| 2 | **The tier** | `quality: "fast" \| "balanced" \| "hq"` | Tier defaults change as faster models land |
| 3 | **The seed** | `seed: 12345` | Same prompt, different frame, every time |
| 4 | **The prompt inputs** | `use_style_profile` left OFF | A saved profile can prepend text you did not write |

```jsonc
// A pinned render. Say this and you will get this.
create_media({
  action: "animate",
  source_url: "https://…/frame.png",
  prompt: "slow push in, handheld, dusk",
  model_override: "kling-v3-turbo-i2v",
  quality: "balanced",
  seed: 12345,
  duration: 5
})
```

**When you must have exactly that model and nothing else**, do not use
`create_media` — call `run_capability` with the capability name. `create_media`
is allowed to fail over to a sibling; `run_capability` sends the name verbatim
and fails instead. You lose the conveniences (project voice/LoRA, brand kit,
quality gates, scene wiring) and you keep the guarantee. Spend is metered either
way.

---

## 2. Read what actually ran — do not assume you got what you asked for

Every `create_media` response carries its own honesty fields. **Read them out
loud to the user.** They exist because a substitution the user can see is a
fixable typo, and one they cannot see is a bug report they will never file.

- **`model_note`** — present **only** when what ran differed from what you asked
  for, and it carries the reason. Relay the reason verbatim.
- **`param_note`** — present when a parameter you sent was adapted or dropped.
- **`prompt_modified`** — present when anything was added to your prompt. If this
  appears and you did not ask for it, stop and tell the user what got prepended.
- **`fallback_fired`** — **the important one.** Present when the AI render failed
  and a real stock clip was substituted. The job still says `done`. It is not
  your render. The poll text now **leads** with `STOCK SUBSTITUTE — …`.

> **Never present a result carrying `fallback_fired` as a successful render.**
> Say: *"the AI video failed, so this is a stock substitute — want me to retry
> with AI?"* Take the **Redo with AI** next_action when present.

Auto-substitution only happens when you did **not** name a model. Naming a model
turns it off. If a reel must never contain stock, either name the model or pass
`on_i2v_timeout: "wait"`.

---

## 2b. When you help write a video prompt (LAT-53)

If the user asks for prompt help — "write me a prompt", "improve this for i2v",
"make this more cinematic" — do **not** return vague descriptive prose. Fill
this scaffold every time:

| Slot | Required content | Example |
|------|------------------|---------|
| Shot / framing | shot type + lens feel | `medium close-up, 35mm` |
| Camera motion | how the camera moves | `slow dolly-in` |
| Subject action | what the subject does | `she turns to camera and exhales` |
| Duration | seconds the clip should hold | `5s` |
| Continuity | tokens shared with adjacent shots | `charcoal coat, dusk, wet asphalt` |

One line is fine — as long as all five slots are present. Then tune the wording
to the model that will actually receive it (`attach_skill('cinematography')`
for the full grammar).

---

## 3. Know the cost before you spend it, and read it after

- `get_pricing` — rates, before you generate.
- `max_cost_usd: 2.50` on `create_media` — a pre-flight cap. The call is rejected
  before submission if the estimate exceeds it. **Use this on every unattended
  fan-out.**
- `spend_cap` — a rolling 24h ceiling across everything.
- `get_cost_report({ scope: "mine", since: "24h" })` — what you actually spent,
  grouped by model. No admin needed; `scope:"mine"` reads your own ledger.
- Pass a `session_id` on every `create_media` in one piece of work, then read it
  back with `scope: "session"` — that total reconciles to exactly this run.

Video is where money goes. A 5s clip ranges from ~$0.05 to ~$2.10 depending on
the model. **Draft on the fast tier, then upgrade the one take you like** — this
is the intended loop, not a compromise.

---

## 4. Async is normal. Read the handle, not the clock

Anything video, 3D or audio returns a `job_id` immediately and runs server-side.

1. Call `subscribe_progress({ job_id })` **in a loop** until status is `done` or
   `failed`. Do not sleep-poll and do not re-submit.
2. If your client drops the connection, the job keeps running. Reconnect and call
   `get_create_media({ job_id })`.
3. A stalled stream is a client timeout, not a failed render. **Re-submitting is
   the one dangerous reaction** — auto-idempotency makes it safe, but recovering
   the handle is correct.

Realistic waits: image 2–30s · fast i2v ~50s · balanced i2v ~2min · hq i2v 3–6min
· a 30s single-pass Seedance take ~3–7min. The response's `eta_seconds` /
`eta_p95_seconds` are measured, not guessed — quote the p95.

---

## 5. Finish the job — hand back one file

A pile of raw asset URLs is not a deliverable. Before you say you are done:

1. **Stitch** — `director_export({ project_id })` concatenates the scenes, lays a
   soundtrack under them, runs the brand kit's finishing chain, and returns **one
   MP4 URL**. For loose clips, `create_media` with
   `model_override: "ffmpeg-concat"`.
2. **Spec it** — `ffmpeg-export` for the platform's aspect and bitrate.
3. **Verify it** — open the URL before you hand it over. A publish can return a
   URL that does not resolve yet; if it 404s, re-publish with a fresh
   `idempotency_key`. **Never hand a user a link you have not checked.**
4. **Lead with the file.** The download link first; canvas and project links
   after it, labelled as "revisit / edit".

---

## 6. What this network is genuinely best at

Reach for these first — they are things most tools cannot do at all.

| Want | Use | Why it wins here |
|---|---|---|
| One continuous take up to **30 seconds**, audio synced | `seedance-25-t2v` | Single pass — no stitching, no scene-cut splicing |
| Several shots with **one model's continuity** | `submit_creative_job({ generation_mode: "native" })` | Kling multi-prompt: one call, model-native continuity |
| **Text that renders correctly** in an image | `grok-image-2` | Character-exact on posters, packaging, signage |
| A **rigged, animatable** 3D mesh | `meshy-v7-i3d` + `enable_rigging` | The only caps that produce a skeleton; walk+run come free |
| A song with **your specific lyrics** | `minimax-music-3` | The only music cap you can tell what to sing |
| **Pixel-exact** titles/UI over AI video | `hyperframes-render` | Real HTML/CSS compositing — AI text rendering never is |
| Re-voice or **localize a finished video** | `veed-lipsync-v2` | Swaps audio and re-matches lips on existing footage |
| Deterministic **finishing** | the `ffmpeg-*` tool caps | Same input, same output, every time — no model involved |

**The single highest-leverage habit:** do the creative part with AI and the exact
part with a tool capability. Type, logos, crops, timing, loudness and stitching
are all deterministic tool caps. Asking a diffusion model for precision it cannot
give is the most common way a run goes wrong.

---

## 7. Known traps — check these before you trust a result

These are real, current, and each one looks like success:

- **Transcript search can miss on its own transcription errors.** ASR mangles
  proper nouns and brand names, so `find_moments({ query: "YourBrand" })` may
  silently skip the moments where it was misheard. Read the transcript, or
  pre-filter moments yourself and pass them to `autoclip({ moments })`.
- **`list_loras` reports `ready` from a stored field**, without checking the
  weights still exist. An `apply_lora` failure that blames a model/base mismatch
  is usually expired weights — check `get_recent_failures`.
- **AI-rendered text in images is unreliable** outside `grok-image-2`. Do type in
  post.
- **Inline base64 uploads are transport-limited** (~26 KB through an agent
  channel, cut from the tail). Truncated JPEG/PNG is now rejected loudly instead
  of hosted broken — but the limit itself remains: for anything above a small
  thumbnail use `create_upload_url`'s signed PUT, or pass a public URL.

Fixed and removed from this register (2026-08-20): `quality_tier` now describes
the capability that ran; `critique_shot` returns `gradeable:false` + null score
instead of a false-perfect 1.0; published URLs are verified to resolve before
`done` is reported.

---

## 8. A first run that works

```
1. list_capabilities                    → see what is live right now
2. get_pricing                          → know the rate
3. create_media({ action:"generate", prompt:"…",
     model_override:"flux-dev", seed:1, max_cost_usd:0.20 })
                                        → one still, pinned and capped
4. create_media({ action:"animate", source_url:<that image>,
     model_override:"kling-v3-turbo-i2v", quality:"balanced",
     seed:1, duration:5, max_cost_usd:1.00 })
5. subscribe_progress({ job_id })       → loop until done
6. read model_note / fallback_fired     → confirm you got what you asked for
7. director_export or ffmpeg-concat     → one MP4
8. open the URL, then hand it over
```

Change **one** variable per iteration. With the seed and model pinned, a prompt
edit is the only thing that moved — which is the whole point.
