This file defines how an LLM agent should use castkit in non-interactive mode.
Produce a polished demo video (terminal or web mode) from evidence-backed steps, with no invented commands.
mode: "terminal": command-driven scenes with optionalartifactsoverlays.mode: "web": deterministic browser action timeline inweb.actions.- If
modeis omitted, castkit defaults toterminal.
- Never invent executable commands, flags, file paths, or setup steps.
- Every executable step must include non-empty
source_refsfrom the active handoff session. - Validate before execute. If validation fails, fix the script and re-validate.
- Prefer
manual_step=trueonly when no runnable command exists in evidence. - Keep output deterministic: non-interactive only (
--non-interactive). - Treat bootstrap contract/schema commands as planning context, not main demo scenes.
- For
mode=web, every action must include non-emptysource_refs.
Do this before generating or validating any script:
- Load machine contract:
castkit --json agent contract- Load machine schema:
castkit --json schema- Use returned
contract_versionand schema as runtime source of truth.
- Initialize handoff session:
castkit handoff init <target_binary_or_path> --json- Discover refs with pagination (repeat for each source):
castkit handoff list --session <session_id> --source help --page 1 --per-page 20 --json
castkit handoff list --session <session_id> --source readme --page 1 --per-page 20 --json
castkit handoff list --session <session_id> --source files --page 1 --per-page 20 --json
castkit handoff list --session <session_id> --source probes --page 1 --per-page 20 --json- Fetch exact refs you plan to cite:
castkit handoff get --session <session_id> --ref <ref_id> --json- Optional scaffold:
castkit plan scaffold --session <session_id> --output demo-script.json --max-scenes 3 --json- Write/refine
DemoScriptJSON. - Validate:
castkit validate --session <session_id> --script demo.json --json- Execute + render:
castkit execute --session <session_id> --script demo.json --non-interactive --preset polished --output demo.mp4 --jsonHuman-readable contract:
castkit agent contractThese are injected automatically for every step:
SESSIONCASTKIT_SESSION
Both start as the value passed to castkit execute --session <id>.
If a step prints JSON containing "session_id", castkit updates both variables for subsequent steps.
Return script as raw JSON only (no markdown), strictly matching the schema below.
{
"version": "1",
"mode": "terminal",
"setup": [
{
"id": "setup_01",
"run": "<command>",
"expect": {
"contains": null,
"regex": null,
"exit_code": 0
},
"timeout_ms": 120000,
"source_refs": ["ref_help_0001"],
"manual_step": false,
"manual_reason": null,
"artifacts": [
{
"type": "result_card",
"title": "Setup status",
"position": "top_right",
"show_ms": 1800,
"items": [
{ "label": "Config", "value": "ready" }
]
}
]
}
],
"scenes": [
{
"id": "scene_01",
"title": "<human-readable scene title>",
"steps": [
{
"id": "step_01",
"run": "<command>",
"expect": {
"contains": null,
"regex": null,
"exit_code": 0
},
"timeout_ms": 120000,
"source_refs": ["ref_readme_0003"],
"manual_step": false,
"manual_reason": null,
"artifacts": []
}
]
}
],
"checks": [],
"cleanup": [],
"redactions": [],
"audio": {
"typing": true,
"music_path": null
},
"branding": {
"title": "castkit demo",
"watermark_text": "castkit.com"
},
"web": {
"base_url": "https://example.com",
"viewport": { "width": 1440, "height": 900 },
"actions": [
{ "id": "open", "type": "goto", "url": "/", "source_refs": ["ref_files_0001"] },
{ "id": "click_cta", "type": "click", "selector": "[data-demo=cta]", "source_refs": ["ref_files_0002"] },
{ "id": "assert", "type": "assert_text", "text": "Welcome", "source_refs": ["ref_files_0003"] }
]
}
}- Terminal mode:
- Use
artifactsfor visual payoff (image/result_card) when a step produces a meaningful output. - Keep overlays short (
show_msaround 1200-2600) and positioned to avoid obscuring active typing lines.
- Use
- Web mode:
- Prefer stable selectors (
data-*attributes) over brittle CSS chains. - Keep actions explicit and small (
goto -> wait/click/type -> assert -> screenshot). - Use at least one
assert_textaction before final screenshot.
- Prefer stable selectors (
- Scene progression should tell a product story, not just
--helpoutput. - Include meaningful feature coverage: init/config, core workflow, result verification.
- Prefer short commands with observable output.
- Keep each scene focused (2-5 steps).
- Include at least one check that verifies final state.
Design each demo around one clear promise:
- "By the end, the user sees
<outcome>working in<target_time>."
Build scenes in this order:
Setup trust: show environment/config is correct (.env, config files, auth status).Happy path: run the primary workflow end-to-end.Power move: show one advanced/high-value feature.Proof: verify output/state with explicit checks.Wrap: summarize artifact/result (file created, record updated, service running).
Recommended scene count:
- 3 scenes for short demos.
- 4-5 scenes for medium demos.
- Real user outcome, not only command catalogs.
- Before/after state (input then transformed result).
- At least one "why this matters" moment (speed, clarity, reliability, automation).
- Concrete outputs: generated files, structured JSON, test results, status commands.
Avoid:
- Long
--helpdumps as the main content. - Full
castkit --json agent contractorcastkit --json schemadumps inside showcase scenes. - Repetitive commands with low information value.
- Setup-heavy intros with no visible payoff.
- Keep commands short and readable; split complex flows into multiple steps.
- Prefer deterministic commands with stable output.
- Use
expect.containsorexpect.regexfor each meaningful step. - Add short setup steps before commands that depend on config/env.
- Use
checksfor final proof, not only in-scene assumptions.
Command writing tips:
- Good: one intent per step, visible stdout, easy-to-verify result.
- Bad: chained opaque shell one-liners that hide what changed.
- Scene titles should be outcome-first (
"Generate typed client from schema"), not vague ("Run command"). - Step IDs should be stable and descriptive (
setup_env,build_bundle,verify_output). - Use
manual_step=trueonly for genuinely non-runnable actions and provide a concretemanual_reason. - Keep total runnable steps lean:
- short demo: 8-14 steps
- medium demo: 12-24 steps
- First meaningful value should appear within 20-35 seconds of video time.
- Every scene should have a visible output event.
- If a command is noisy, prefer filtered/targeted variants so key lines are visible.
- End with explicit verification (not just "command succeeded").
Before validate, confirm all are true:
- Each runnable step has valid
source_refs. - No scene is pure setup without user-visible outcome.
- At least one final-state check exists in
checks. - No step depends on hidden state that was not established in
setup. - Titles and command sequence tell a coherent story from problem to proof.
Use one preset for simplicity:
--preset quick: fastest iteration and lower encode cost.--preset balanced: better quality with moderate speed.--preset polished: highest default polish for showcase videos.
You can still override with explicit flags (--speed, --fps, --theme, --keystroke-profile).
Treat each CLI call as complete only when both are true:
- Process exit code is
0. - JSON response has
"ok": true.
For validate, completion means:
"ok": true- no validation errors.
For execute, completion means:
"ok": true"output"points to the generated file."transcript_path"exists."render"exists with duration/paths.
If execute.ok is false:
- If
failurescontains step-level errors, revise script and rerunvalidate. - If failure is infra/runtime (missing ffmpeg/node/playwright), fix environment and rerun.
After execute, return this summary to caller:
status:successorfailedsession_idoutputduration_secs(fromrender.duration_secsif present)failed_step(first failure path, if any)next_action(done,fix-script,fix-environment)
Use broad, safe defaults; do not assume high-end hardware.
Recommended polling interval:
- every
20swhileexecuteis running.
Recommended timeout policy:
- soft timeout:
8m(emit warning, keep running) - hard timeout:
20m(mark failed and stop)
Approximate end-to-end execute times:
- Short demo (20-45s output):
quick:~45-120sbalanced:~90-210spolished:~120-300s
- Medium demo (60-120s output):
quick:~2-5mbalanced:~3-7mpolished:~4-10m
- Long demo (3-5 min output):
quick:~6-12mbalanced:~8-16mpolished:~10-20m
If unknown, use this hard-timeout heuristic:
hard_timeout_minutes = max(10, ceil(video_minutes * 4)), capped at20.
Example watchdog loop:
castkit execute --session "$SESSION" --script demo.json --non-interactive --preset polished --output demo.mp4 --json > execute.json &
PID=$!
START=$(date +%s)
SOFT=480 # 8m
HARD=1200 # 20m
while kill -0 "$PID" 2>/dev/null; do
NOW=$(date +%s)
ELAPSED=$((NOW - START))
if [ "$ELAPSED" -ge "$HARD" ]; then
kill -TERM "$PID" 2>/dev/null || true
echo "hard-timeout"
break
fi
if [ "$ELAPSED" -ge "$SOFT" ]; then
echo "soft-timeout-warning"
fi
sleep 20
done
wait "$PID"