backlot phase 0: artifact contract + gate hardening

- init_project() writes project.json marker + canonical workspace layout
- write_checkpoint enforces approval gates: completed on a gated stage
  requires human_approved=True (GATE VIOLATION otherwise)
- superseded checkpoints archived to projects/<id>/history/ (stage
  versioning, gate audit trail, replay)
- BaseTool auto-instruments execute() -> projects/<id>/events.jsonl
  (start/finish/error, scene_id, cost) for the Backlot live board
- assets stage now gates (human_approval_default: true) in all manifests
- checkpoint-protocol + AGENT_GUIDE: manifest gate value is binding,
  awaiting_human + end-turn protocol, per-gate approval, canonical
  checkpoint location fixed to projects/<id>/
- gate reminder footer on all gating stage director skills
- /backlot command files for Claude Code, Codex, Cursor, Copilot
This commit is contained in:
calesthio 2026-07-01 23:08:51 -07:00
parent 169124d0fd
commit 722491d732
85 changed files with 1243 additions and 38 deletions

View File

@ -0,0 +1,15 @@
---
description: Open the Backlot living storyboard — the browser board that shows pipeline stages, script, scene plan, and generated assets live as a production runs.
argument-hint: [project-id (optional — defaults to the current/most recent project)]
---
Open the Backlot board for the requested project:
```bash
python -m backlot open $ARGUMENTS
```
- No argument → open the library view (all projects): `python -m backlot open`
- The command is idempotent: it starts the Backlot server if it isn't running, then opens the browser at the project's board.
- If the command fails, report it and continue with whatever the user asked — the board is an observer, never a blocker.
- The board derives everything from disk (`projects/<id>/` checkpoints, artifacts, assets, events). You never update the UI manually; keep checkpoints and artifacts honest per `skills/meta/checkpoint-protocol.md` and the board stays honest too.

12
.codex/prompts/backlot.md Normal file
View File

@ -0,0 +1,12 @@
# /backlot — open the living storyboard
Open the Backlot board (browser UI showing pipeline stages, script, scene plan, and generated assets live) for the requested project:
```bash
python -m backlot open <project-id>
```
- No project id → open the library view: `python -m backlot open`
- Idempotent: starts the Backlot server if needed, then opens the browser at the project's board.
- If it fails, report and continue — the board is an observer, never a blocker.
- The board derives all state from `projects/<id>/` on disk; never update the UI manually. Keep checkpoints and artifacts honest per `skills/meta/checkpoint-protocol.md`.

View File

@ -0,0 +1,12 @@
# /backlot — open the living storyboard
Open the Backlot board (browser UI showing pipeline stages, script, scene plan, and generated assets live) for the requested project:
```bash
python -m backlot open <project-id>
```
- No project id → open the library view: `python -m backlot open`
- Idempotent: starts the Backlot server if needed, then opens the browser at the project's board.
- If it fails, report and continue — the board is an observer, never a blocker.
- The board derives all state from `projects/<id>/` on disk; never update the UI manually. Keep checkpoints and artifacts honest per `skills/meta/checkpoint-protocol.md`.

12
.github/prompts/backlot.prompt.md vendored Normal file
View File

@ -0,0 +1,12 @@
# /backlot — open the living storyboard
Open the Backlot board (browser UI showing pipeline stages, script, scene plan, and generated assets live) for the requested project:
```bash
python -m backlot open <project-id>
```
- No project id → open the library view: `python -m backlot open`
- Idempotent: starts the Backlot server if needed, then opens the browser at the project's board.
- If it fails, report and continue — the board is an observer, never a blocker.
- The board derives all state from `projects/<id>/` on disk; never update the UI manually. Keep checkpoints and artifacts honest per `skills/meta/checkpoint-protocol.md`.

View File

@ -213,7 +213,14 @@ projects/<project-name>/
**Naming convention**: Use kebab-case derived from the video title (e.g., `hidden-math-of-nature`, `how-music-rewires-brain`).
Create the project directory at pipeline initialization, before any stage runs. All tools and agents should write outputs to these paths — never to the repo root or ad-hoc locations.
At pipeline initialization, before any stage runs:
1. **Initialize the workspace**: `python -c "from lib.checkpoint import init_project; init_project('<project-id>', title='<Title>', pipeline_type='<pipeline>')"` — creates the layout above and writes `project.json` (the marker the Backlot board reads).
2. **Open the board**: run `python -m backlot open <project-id>`. This starts the Backlot server if needed and opens the user's browser at the project's live board. If the command fails, continue the production — the board is an observer, never a blocker. This is the agent's ONLY board duty; the board derives everything else from disk.
All tools and agents must write outputs to these paths — **always pass an explicit `output_path` under `projects/<project-id>/`**. Assets written to the repo root, cwd, or temp dirs are invisible to the user's board and violate the workspace contract.
**This applies to atelier and HyperFrames-skill runs too**: hand-authored compositions still write the canonical artifacts they have (script or beats-plan, scene_plan-equivalent, asset manifest) plus checkpoints into `projects/<project-id>/`. The board is runtime-agnostic; only runs that skip the artifacts get a degraded board.
## Music Library
@ -568,11 +575,11 @@ The reviewer is a meta skill (`skills/meta/reviewer.md`) — advisory, never dir
The checkpoint protocol meta skill (`skills/meta/checkpoint-protocol.md`) teaches the agent when to pause:
- Read `human_approval_default` from the pipeline manifest per stage
- Creative stages (`idea`, `script`, `scene_plan`) typically require approval
- Technical stages (`assets`, `edit`, `compose`) typically auto-proceed
- When approval is required: present artifact summary, review findings, and cost snapshot
- Wait for human to approve, request revision, or abort
- Read `human_approval_default` from the pipeline manifest per stage. **The manifest value is binding** — never re-judge it. `lib/checkpoint.py` enforces this: a gated stage cannot be written `completed` without `human_approved=True`.
- Gated stages across all pipelines: `idea`/`proposal`, `script`, `scene_plan`, **`assets`** (review the generated assets scene-by-scene — the Backlot board's filmstrip — before compose locks them in), and `publish`. `edit` and `compose` auto-proceed.
- When approval is required: write the checkpoint as `awaiting_human`, present artifact summary, review findings, and cost snapshot — then **END YOUR TURN**. Doing further pipeline work in the same response is a gate violation.
- **Approval is per-gate.** An early "go ahead" never covers later gates; explicit full-run pre-authorization must be recorded as a `decision_log` entry (`category: "approval_policy"`) to count.
- Wait for human to approve, request revision, or abort.
## Communication Protocol
@ -592,9 +599,12 @@ Primary files:
Checkpoint rules:
- Checkpoints live at `pipelines/<project_id>/checkpoint_<stage>.json`.
- Checkpoints live at `projects/<project_id>/checkpoint_<stage>.json` (the project workspace — this is what the Backlot board watches).
- `status` may be `completed`, `failed`, `awaiting_human`, or `in_progress`.
- Write an `in_progress` checkpoint on entering each stage; during `assets`/`compose`, refresh `metadata.partial_progress` after each completed scene/asset unit — this powers live progress on the board.
- `completed` and `awaiting_human` checkpoints must include the canonical artifact.
- A gated stage (`human_approval_default: true`) can only be written `completed` with `human_approved=True` — the writer raises a GATE VIOLATION otherwise.
- Superseded checkpoints are archived automatically to `projects/<project_id>/history/` — stage re-runs never destroy run history.
- Invalid checkpoints or invalid canonical artifacts are contract violations and should fail fast.
Pipeline manifest rules:

View File

@ -81,6 +81,15 @@ CHECKPOINT_SCHEMA_PATH = (
/ "checkpoint.schema.json"
)
# Canonical project root. Checkpoints, artifacts, and the project marker all
# live under PROJECTS_DIR/<project_id>/ — this is the location the Backlot
# board watches. Callers may still pass a different pipeline_dir (tests do),
# but production runs should use the default.
PROJECTS_DIR = Path(__file__).resolve().parent.parent / "projects"
PROJECT_MARKER_FILENAME = "project.json"
HISTORY_DIRNAME = "history"
class CheckpointValidationError(ValueError):
"""Raised when a checkpoint or its canonical artifacts are invalid."""
@ -157,6 +166,108 @@ def _checkpoint_path(pipeline_dir: Path, project_id: str, stage: str) -> Path:
return pipeline_dir / project_id / f"checkpoint_{stage}.json"
def init_project(
project_id: str,
*,
title: str,
pipeline_type: str,
pipeline_dir: Optional[Path] = None,
style_playbook: Optional[str] = None,
) -> Path:
"""Initialize a project workspace with the canonical layout + marker file.
Creates projects/<project_id>/ with the standard subdirectories and writes
project.json the marker the Backlot board uses to render a project's
identity and stage rail before the first checkpoint exists.
Idempotent: re-running preserves the original created_at and merges fields.
Returns the project directory.
"""
base = pipeline_dir or PROJECTS_DIR
project_dir = base / project_id
for sub in (
"artifacts",
"assets/images",
"assets/video",
"assets/audio",
"assets/music",
"renders",
):
(project_dir / sub).mkdir(parents=True, exist_ok=True)
marker_path = project_dir / PROJECT_MARKER_FILENAME
marker: dict[str, Any] = {}
if marker_path.exists():
try:
with open(marker_path) as f:
marker = json.load(f)
except (json.JSONDecodeError, OSError):
marker = {}
marker.setdefault("version", "1.0")
marker.setdefault("created_at", datetime.now(timezone.utc).isoformat())
marker["project_id"] = project_id
marker["title"] = title
marker["pipeline_type"] = pipeline_type
if style_playbook is not None:
marker["style_playbook"] = style_playbook
with open(marker_path, "w") as f:
json.dump(marker, f, indent=2)
return project_dir
def _stage_requires_approval(pipeline_type: Optional[str], stage: str) -> Optional[bool]:
"""Read human_approval_default for a stage from its pipeline manifest.
Returns None when the manifest can't answer (unknown pipeline_type,
stage not declared) the caller then falls back to the value the
agent passed in.
"""
if not pipeline_type or pipeline_type == "unknown":
return None
try:
from lib.pipeline_loader import load_pipeline
manifest = load_pipeline(pipeline_type)
for stage_def in manifest.get("stages", []):
if stage_def.get("name") == stage:
return bool(stage_def.get("human_approval_default", False))
except Exception:
return None
return None
def _archive_superseded_checkpoint(path: Path, stage: str) -> None:
"""Move an existing checkpoint into history/ before it is overwritten.
Preserves the full run record: stage re-runs (script v1 v2) and gate
transitions (awaiting_human completed) remain reconstructable. Repeated
in_progress refreshes are NOT archived they are partial-progress
heartbeats, not versions.
"""
if not path.exists():
return
try:
with open(path) as f:
existing = json.load(f)
except (json.JSONDecodeError, OSError):
existing = {}
if existing.get("status") == "in_progress":
return
stamp = str(existing.get("timestamp", ""))
safe_stamp = "".join(c for c in stamp if c.isalnum()) or f"{path.stat().st_mtime_ns}"
history_dir = path.parent / HISTORY_DIRNAME
history_dir.mkdir(parents=True, exist_ok=True)
target = history_dir / f"checkpoint_{stage}_{safe_stamp}.json"
counter = 1
while target.exists():
target = history_dir / f"checkpoint_{stage}_{safe_stamp}_{counter}.json"
counter += 1
path.replace(target)
def _decision_log_path(pipeline_dir: Path, project_id: str) -> Path:
return pipeline_dir / project_id / "decision_log.json"
@ -219,6 +330,26 @@ def write_checkpoint(
f"Valid stages: {sorted(valid_stages)}"
)
# --- Gate enforcement (GI-4) ---
# The pipeline manifest is the binding source of truth for whether a stage
# gates on human approval. A gated stage can only be written as
# "completed" with explicit evidence of approval (human_approved=True).
# Skipping a gate is a hard error, not a soft violation.
manifest_gate = _stage_requires_approval(pipeline_type, stage)
gated = manifest_gate if manifest_gate is not None else human_approval_required
if gated:
human_approval_required = True
if status == "completed" and not human_approved:
raise CheckpointValidationError(
f"GATE VIOLATION: stage {stage!r} requires human approval "
f"(human_approval_default: true in the {pipeline_type!r} manifest) "
f"but status='completed' was written without human_approved=True. "
f"Correct protocol: write status='awaiting_human', present the "
f"artifact summary to the user, END YOUR TURN, and only after "
f"the user approves re-write with status='completed', "
f"human_approved=True."
)
checkpoint = {
"version": "1.0",
"project_id": project_id,
@ -266,6 +397,10 @@ def write_checkpoint(
path = _checkpoint_path(pipeline_dir, project_id, stage)
path.parent.mkdir(parents=True, exist_ok=True)
# Preserve run history: a superseded completed/awaiting_human checkpoint
# moves to history/ instead of being destroyed (stage versioning, gate
# audit trail, replay).
_archive_superseded_checkpoint(path, stage)
with open(path, "w") as f:
json.dump(checkpoint, f, indent=2)

111
lib/events.py Normal file
View File

@ -0,0 +1,111 @@
"""Backlot event stream — append-only tool-event log per project.
Written by the BaseTool instrumentation layer (tools/base_tool.py) whenever a
tool executes against a project directory; consumed by the Backlot board's
watcher to power live activity and per-scene generating states.
Design rules:
- Observability must never break production: every public function swallows
its own errors. A failed event write is silently dropped.
- Zero agent burden: project attribution is inferred from the tool's inputs
(explicit ``project_dir`` or any path argument under ``projects/``).
"""
from __future__ import annotations
import json
import threading
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Optional
REPO_ROOT = Path(__file__).resolve().parent.parent
PROJECTS_DIR = REPO_ROOT / "projects"
EVENTS_FILENAME = "events.jsonl"
_write_lock = threading.Lock()
# Input keys checked (in order) when inferring the project a tool call
# belongs to. Explicit project keys win over path inference.
_EXPLICIT_PROJECT_KEYS = ("project_dir", "project_path")
_PATH_HINT_KEYS = (
"output_path",
"output_dir",
"output_file",
"input_path",
"video_path",
"audio_path",
"image_path",
"file_path",
)
def infer_project_dir(inputs: Any) -> Optional[Path]:
"""Best-effort: which project directory does this tool call belong to?
Returns None when the call can't be attributed — the event is then
simply not emitted (principle: never guess loudly, never fail).
"""
if not isinstance(inputs, dict):
return None
try:
for key in _EXPLICIT_PROJECT_KEYS:
value = inputs.get(key)
if isinstance(value, (str, Path)) and str(value):
p = Path(value)
if p.is_dir():
return p
projects_root = PROJECTS_DIR.resolve()
for key in _PATH_HINT_KEYS:
value = inputs.get(key)
if not isinstance(value, (str, Path)) or not str(value):
continue
try:
resolved = Path(value).resolve()
rel = resolved.relative_to(projects_root)
except (ValueError, OSError):
continue
if rel.parts:
return PROJECTS_DIR / rel.parts[0]
except Exception:
return None
return None
def emit_event(project_dir: Path | str, payload: dict[str, Any]) -> None:
"""Append one event to the project's events.jsonl. Never raises."""
try:
entry = {"ts": datetime.now(timezone.utc).isoformat()}
entry.update({k: v for k, v in payload.items() if v is not None})
path = Path(project_dir) / EVENTS_FILENAME
path.parent.mkdir(parents=True, exist_ok=True)
line = json.dumps(entry, default=str)
with _write_lock:
with open(path, "a", encoding="utf-8") as f:
f.write(line + "\n")
except Exception:
pass
def read_events(project_dir: Path | str, limit: Optional[int] = None) -> list[dict[str, Any]]:
"""Read events for a project (oldest first). Tolerates malformed lines."""
path = Path(project_dir) / EVENTS_FILENAME
if not path.exists():
return []
events: list[dict[str, Any]] = []
try:
with open(path, encoding="utf-8", errors="replace") as f:
for line in f:
line = line.strip()
if not line:
continue
try:
events.append(json.loads(line))
except json.JSONDecodeError:
continue
except OSError:
return []
if limit is not None:
return events[-limit:]
return events

View File

@ -184,7 +184,7 @@ stages:
- music_gen
- math_animate
checkpoint_required: true
human_approval_default: false
human_approval_default: true
review_focus:
- All asset files exist on disk
- Narration covers all script sections

View File

@ -193,7 +193,7 @@ stages:
- code_snippet
- music_gen
checkpoint_required: true
human_approval_default: false
human_approval_default: true
review_focus:
- Asset production path is explicit per scene
- Reusable motifs and templates are prepared and referenced

View File

@ -124,7 +124,7 @@ stages:
- audio_enhance
- video_selector
checkpoint_required: true
human_approval_default: false
human_approval_default: true
review_focus:
- Avatar generation path is explicit and honest (including no-avatar pivot if applicable)
- Narration, subtitle, and background assets are aligned

View File

@ -212,7 +212,7 @@ stages:
- music_gen
- character_rig_renderer
checkpoint_required: true
human_approval_default: false
human_approval_default: true
review_focus:
- Character parts, backgrounds, props, audio, and effects are linked to scenes
- Layer 3 skills are read for every generation or animation-runtime tool

View File

@ -183,7 +183,7 @@ stages:
- freesound_music
- music_gen
checkpoint_required: true
human_approval_default: false
human_approval_default: true
review_focus:
- Source selects and support assets are clearly separated
- Motion-required beats use actual video clips rather than still-image substitutes

View File

@ -123,7 +123,7 @@ stages:
- subtitle_gen
- audio_enhance
checkpoint_required: true
human_approval_default: false
human_approval_default: true
review_focus:
- Per-clip subtitles generated with correct time offsets
- Shared title / hook / branding assets prepared for each clip

View File

@ -102,7 +102,7 @@ stages:
- clip_search
- music_gen
checkpoint_required: true
human_approval_default: false
human_approval_default: true
review_focus:
- Every slot has exactly one picked clip
- No clip_id is picked for two slots

View File

@ -138,7 +138,7 @@ stages:
- music_gen
- audio_enhance
checkpoint_required: true
human_approval_default: false
human_approval_default: true
review_focus:
- Support assets clearly map to real narrative gaps
- Shared template assets are reused

View File

@ -125,7 +125,7 @@ stages:
- lip_sync
- audio_enhance
checkpoint_required: true
human_approval_default: false
human_approval_default: true
review_focus:
- Subtitle and dubbed-audio assets exist for each language
- Timing and pronunciation risks are recorded

View File

@ -131,7 +131,7 @@ stages:
- music_gen
- audio_enhance
checkpoint_required: true
human_approval_default: false
human_approval_default: true
review_focus:
- Subtitles generated for all clips and full episode
- Quote card / speaker-card assets match playbook style

View File

@ -162,7 +162,7 @@ stages:
- diagram_gen
- audio_enhance
checkpoint_required: true
human_approval_default: false
human_approval_default: true
review_focus:
- Subtitle file exists and matches speech timing
- Reusable callout overlays (arrows, highlights, masks) are prepared

View File

@ -124,7 +124,7 @@ stages:
- audio_mixer
- image_selector
checkpoint_required: true
human_approval_default: false
human_approval_default: true
review_focus:
- Subtitle file exists and matches transcript timing
- Audio extracted and normalized

View File

@ -49,10 +49,33 @@ write_checkpoint(
The checkpoint utility will:
- Validate the artifact against its schema
- Enforce the approval gate (a gated stage cannot be written `completed` without `human_approved=True`)
- Archive any superseded checkpoint to `projects/<id>/history/` (stage versions and gate transitions are never destroyed)
- Write the checkpoint JSON to disk
- Include timestamp and stage metadata
### Step 4: Intra-Stage Checkpointing (Resume Support)
Canonical location: `projects/<project_id>/checkpoint_<stage>.json` — always
pass the repo's `projects/` directory as `pipeline_dir` (or use
`lib.checkpoint.PROJECTS_DIR`). Always pass `pipeline_type` — gate enforcement
reads the manifest through it.
At pipeline initialization (before any stage), call `init_project()`:
```python
from lib.checkpoint import init_project
init_project("my-project", title="My Project", pipeline_type="cinematic")
```
This creates the canonical directory layout and writes `project.json` — the
marker the Backlot board needs to show the project before its first
checkpoint. Then launch the board: `python -m backlot open my-project`
(non-fatal if unavailable — the board is an observer, never a blocker).
### Step 4: Intra-Stage Checkpointing (Resume Support + Liveness)
**On entering any stage, write an `in_progress` checkpoint first.** This is
what tells the user (via the Backlot board) that the stage is live rather
than stalled — certainty matters more than speed.
Long-running stages (like `assets` or `compose` loops) can fail midway due to API errors, rate limits, or session interruptions. To allow resuming from the exact point of failure (e.g., Scene 4):
@ -78,14 +101,23 @@ Long-running stages (like `assets` or `compose` loops) can fail midway due to AP
### Step 5: Human Approval (If Required)
**The manifest value is binding.** `human_approval_default` in the pipeline
manifest is the single source of truth for whether a stage gates. This skill
never overrides it, and neither do you — there is no "this case is different."
(`lib/checkpoint.py` enforces this: writing `status="completed"` for a gated
stage without `human_approved=True` raises a `GATE VIOLATION` error.)
When `human_approval_default: true`:
1. **Present a summary** to the human:
1. **Write the checkpoint with `status="awaiting_human"`** (not `completed`).
2. **Present a summary** to the human:
```
## Stage Complete: [stage_name]
## Stage Complete: [stage_name] — awaiting your approval
### Artifact Summary
[Key details from the artifact — title, duration, key decisions]
[If the Backlot board is running, point to it: the artifact renders there]
### Review Findings
[Summary from reviewer: N critical (all fixed), N suggestions]
@ -97,19 +129,29 @@ When `human_approval_default: true`:
Please review and approve to continue, or provide feedback for revision.
```
2. **Wait for human response:**
- **Approved** → update checkpoint status to `"completed"`, proceed to next stage
- **Revision requested** → go back to the stage director skill with the human's feedback, produce revised artifacts, re-review, re-checkpoint
3. **END YOUR TURN.** Performing any further pipeline work in the same
response is a gate violation. "Present and continue" is not waiting —
the turn must end with the question, and the next pipeline action must
be caused by the user's reply.
4. **On the user's response:**
- **Approved** → re-write the checkpoint with `status="completed"`,
`human_approved=True`, then proceed to the next stage
- **Revision requested** → go back to the stage director skill with the
human's feedback, produce revised artifacts, re-review, re-checkpoint
(the superseded checkpoint is preserved automatically in `history/`)
- **Abort** → stop the pipeline
3. **Approval stages** (which stages typically need human approval):
- `idea` — Always. The creative direction defines everything downstream.
- `script` — Always. The words are the foundation.
- `scene_plan` — Usually. Visual choices are subjective.
- `assets` — Rarely. Automated quality checks are sufficient.
- `edit` — Rarely. Technical assembly, not creative.
- `compose` — Rarely. But human may want to preview.
- `publish` — Always. Human must approve before anything goes public.
5. **Approval is per-gate.** A prior approval, however broad ("looks great,
go ahead and make the whole thing"), never covers a later gate. If the
user explicitly pre-authorizes the full run, record that as a
`decision_log` entry (`category: "approval_policy"`) at the moment they
say it — absent that entry, stop at every gate.
6. **The assets gate reviews the storyboard.** `assets` now gates in every
pipeline: present the generated assets scene-by-scene (the Backlot board's
filmstrip is the natural review surface), including spend so far and the
projected compose cost. A bad asset caught here saves a full re-render.
### Step 6: Determine Next Stage

View File

@ -164,3 +164,12 @@ This is especially important for:
- **Remotion component patterns** — new composition techniques emerge as the framework evolves
Do not rely on stale knowledge. When in doubt, search first.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -71,3 +71,12 @@ Recommended metadata keys:
- Treating all animation as one generic category.
- Planning bespoke visuals for every scene.
- Hiding missing tool paths until the asset stage.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -466,3 +466,12 @@ This is especially important for:
- **Remotion component patterns** — new composition techniques emerge as the framework evolves
Do not rely on stale knowledge. When in doubt, search first.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -43,3 +43,12 @@ Store in `publish_log.metadata`:
- Writing generic metadata that ignores the animation style.
- Creating a thumbnail concept unrelated to the final frames.
- Mixing platform variants without clear labels.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -113,3 +113,12 @@ Recommended metadata keys:
- Adding a new transition idea in every scene.
- Planning scenes that have no realistic production path.
- Overanimating text-heavy scenes.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -134,3 +134,12 @@ add the source. Do not invent statistics, dates, or attributions.
- **Ignoring the animation mode.** A Manim script reads differently than an AI video script.
- **Writing research-less scripts when a research_brief exists.** If the research found surprising data, use it. Generic scripts waste the research investment.
- **Oversimplifying math to the point of being wrong.** Check the research brief's accuracy notes.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -129,3 +129,12 @@ This is especially important for:
- **Remotion component patterns** — new composition techniques emerge as the framework evolves
Do not rely on stale knowledge. When in doubt, search first.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -77,3 +77,12 @@ Recommended metadata keys:
- Treating a generic generated-video request as a deterministic avatar workflow.
- Writing the CTA before confirming the avatar and narration path.
- Planning multiple aspect ratios before the hero layout is proven.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -42,3 +42,12 @@ If the avatar path has limitations such as visible lip-sync risk, retain that no
- Mixing hero and derivative exports without clear naming.
- Reusing generic metadata that ignores the spokesperson offer.
- Dropping risk notes that matter for downstream publishing teams.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -80,3 +80,12 @@ When the EP triggers a no-avatar pivot (no `talking_head` or `lip_sync` availabl
- Filling empty space with decorative panels.
- Assuming a landscape presenter layout will survive a vertical crop untouched.
- (Fallback mode) Producing a wall of text on screen to compensate for no presenter — let the narration carry the content.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -74,3 +74,12 @@ add the source. Do not invent statistics, dates, or attributions.
- Overstuffing one scene because the script reads well on paper.
- Duplicating the same sentence in speech and large text overlays.
- Writing humor or improvisational beats the avatar path cannot sell.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -55,3 +55,12 @@ projects/<project-name>/assets/backgrounds/
All parts referenced by `rig_plan` must exist before compose. Missing parts are a
blocker unless the action timeline removes the action requiring them.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -32,3 +32,12 @@ using image generation, read the tool's Layer 3 skills from the registry.
A character design is ready only when an animator or tool can infer what parts,
expressions, and actions must exist.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -59,3 +59,12 @@ Report the difference:
- TTS/music cost,
- local render cost,
- manual complexity risk.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -24,3 +24,12 @@ Produce `publish_log` with:
- description,
- platform-specific export notes,
- limitations or follow-up recommendations.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -35,3 +35,12 @@ Prefer fewer, stronger shots:
Avoid scenes that require many unique views or complex physical contact unless
the user approved that complexity.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -35,3 +35,12 @@ In the `script` artifact metadata, include:
- `character_beats`,
- `required_emotions`,
- `required_actions`.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -162,3 +162,12 @@ This is especially important for:
- **Remotion component patterns** — new composition techniques emerge as the framework evolves
Do not rely on stale knowledge. When in doubt, search first.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -124,3 +124,12 @@ Record the decision in `brief.metadata.music_strategy` with the chosen source an
- Assuming generated inserts are available without checking tools.
- Quietly turning a motion-led brief into a still-led teaser.
- Planning a trailer shape with no reveal or payoff.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -290,3 +290,12 @@ This is especially important for:
- **Remotion component patterns** — new composition techniques emerge as the framework evolves
Do not rely on stale knowledge. When in doubt, search first.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -54,3 +54,12 @@ Store in `publish_log.metadata`:
- Mixing teaser and hero outputs without clear naming.
- Writing generic metadata that ignores the mood.
- Treating all cutdowns as interchangeable.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -76,3 +76,12 @@ Recommended metadata keys:
- Using title cards as filler.
- Treating generated inserts like the primary story without saying so.
- Planning flashy transitions for every beat.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -78,3 +78,12 @@ add the source. Do not invent statistics, dates, or attributions.
- Writing full explanatory paragraphs instead of beats.
- Using too many title cards.
- Revealing the best moment too early.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -106,3 +106,12 @@ This is especially important for:
- **Remotion component patterns** — new composition techniques emerge as the framework evolves
Do not rely on stale knowledge. When in doubt, search first.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -102,3 +102,12 @@ Recommended metadata keys:
- Assuming every source can produce vertical clips cleanly.
- Treating all clips as interchangeable instead of intentionally varied.
- Starting extraction without defining what "good" means for this batch.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -58,3 +58,12 @@ Store in `publish_log.metadata`:
- Publishing the whole batch on the same day.
- Using one caption everywhere.
- Losing the rank/order logic after rendering is complete.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -74,3 +74,12 @@ Each scene should map to one clip variant or one clip family deliverable. Keep `
- Ignoring slide or screen-share content while focusing only on faces.
- Letting each clip invent its own layout.
- Forgetting that the first frame determines whether a viewer keeps watching.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -99,3 +99,12 @@ add the source. Do not invent statistics, dates, or attributions.
- Selecting too many calm, same-energy clips.
- Preserving chronological order instead of ranking by quality.
- Treating transcript quality issues as minor when they affect selection accuracy.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -515,3 +515,12 @@ clip_search.execute({
Used when the edit director wants to confirm the provider/URL before
locking the cut.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -211,3 +211,12 @@ open for the scene director to decide per slot.
the user explicitly says no.
- Skipping the end-tag because "the images speak for themselves". They
don't — the end-tag is the thesis. Propose one every time.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -347,3 +347,12 @@ Each slot gets:
- `target_hold_seconds` summing to ~90.
This is the artifact the asset director will run retrieval against.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -278,3 +278,12 @@ This is especially important for:
- **Remotion component patterns** — new composition techniques emerge as the framework evolves
Do not rely on stale knowledge. When in doubt, search first.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -181,3 +181,12 @@ If no existing playbook fits, describe the desired style in `brief.style` and th
- Angle 1: "HTTPS Explained" — generic, no hook
- Angle 2: "How HTTPS Works" — same thing, reworded
- Angle 3: "Understanding HTTPS" — still the same, no structural difference
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -540,3 +540,12 @@ This is especially important for:
- **Remotion component patterns** — new composition techniques emerge as the framework evolves
Do not rely on stale knowledge. When in doubt, search first.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -150,3 +150,12 @@ Validate the publish_log against the schema and persist via checkpoint.
- **Description keyword stuffing**: Write for humans first, search engines second. Natural language with keywords woven in.
- **Forgetting the CTA**: Every description should end with a call to action.
- **Wrong platform format**: YouTube descriptions differ from TikTok captions. Tailor to the target platform.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -238,3 +238,12 @@ Call `handle_explainer_scene_plan(state, {"scene_plan": scene_plan_json})` to va
- **Preset thinking**: A scene plan that says "make it flat-motion-graphics" is not enough. The planner must specify what makes THIS video's motion graphics feel distinct.
- **Static scenes for dynamic concepts**: If the narrator describes a process or transformation, the visual should move. Use animation or progressive reveal, not a static image.
- **Using `generated` type for CTA/closing screens with exact text**: AI image models hallucinate text — wrong business names, misspelled words, wrong phone numbers. Any scene with verbatim text (CTA, business info, contact details, legal) MUST be `type: "text_card"` so Remotion renders the text exactly. Never plan a `generated` image for a scene where text accuracy matters.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -255,3 +255,12 @@ add the source. Do not invent statistics, dates, or attributions.
]
}
```
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -98,3 +98,12 @@ This is especially important for:
- **Remotion component patterns** — new composition techniques emerge as the framework evolves
Do not rely on stale knowledge. When in doubt, search first.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -84,3 +84,12 @@ Recommended metadata keys:
- Calling everything hybrid without defining a primary medium.
- Planning support layers before understanding the source.
- Treating optional generated inserts as guaranteed.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -48,3 +48,12 @@ Recommended metadata keys:
- Hiding which output is the hero cut.
- Packaging a source-led project like a generic generated asset.
- Losing platform-specific copy and labeling across variants.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -60,3 +60,12 @@ Recommended metadata keys:
- Turning source-led scenes into overlay soup.
- Forgetting variant-safe zones until compose.
- Using generated inserts for every transition.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -68,3 +68,12 @@ add the source. Do not invent statistics, dates, or attributions.
- Rewriting strong source dialogue into weaker narration.
- Adding diagrams or cards where the footage already explains the point.
- Hiding unsupported requirements until asset generation.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -90,3 +90,12 @@ This is especially important for:
- **Remotion component patterns** — new composition techniques emerge as the framework evolves
Do not rely on stale knowledge. When in doubt, search first.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -74,3 +74,12 @@ Recommended metadata keys:
- Calling every translation request a dubbing request.
- Ignoring glossary control until after audio is generated.
- Promising lip sync on visually difficult source footage without warning.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -42,3 +42,12 @@ If a language output has pronunciation caveats, timing warnings, or missing lip
- Shipping localized videos without the matching subtitle or transcript files.
- Mixing audio-dub and subtitle-only variants under the same generic filename.
- Removing the QA notes that explain known issues.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -63,3 +63,12 @@ Recommended metadata keys:
- Assuming dubbed audio will fit the source timing exactly.
- Choosing lip sync for every shot instead of only the shots that justify it.
- Forgetting about baked-in text until compose time.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -63,3 +63,12 @@ add the source. Do not invent statistics, dates, or attributions.
- Generating audio from an unreviewed transcript.
- Letting product names drift across languages.
- Treating translation text as final timing without acknowledging length drift.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -103,3 +103,12 @@ This is especially important for:
- **Remotion component patterns** — new composition techniques emerge as the framework evolves
Do not rely on stale knowledge. When in doubt, search first.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -90,3 +90,12 @@ Use `brief.metadata` for the richer podcast-specific contract:
- Treating audio-only and video-podcast sources as the same production problem.
- Planning too many deliverables from a weak episode.
- Promising a rich full-episode visual treatment without the assets to support it.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -59,3 +59,12 @@ Recommended metadata keys:
- Publishing clips without clear episode references.
- Forgetting to tag or mention the guest when that audience matters.
- Reusing one caption style across every platform.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -68,3 +68,12 @@ Every layout should clearly preserve:
- Planning speaker-centric layouts for audio-only episodes.
- Turning every clip into the same waveform-plus-logo composition.
- Using generated graphics to cover weak editorial choices.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -79,3 +79,12 @@ add the source. Do not invent statistics, dates, or attributions.
- Treating diarization errors as minor when they change who said the quote.
- Selecting clips that need too much earlier context.
- Overfitting the batch to one section of the episode.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -164,3 +164,12 @@ This is especially important for:
- **Remotion component patterns** — new composition techniques emerge as the framework evolves
Do not rely on stale knowledge. When in doubt, search first.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -133,3 +133,12 @@ Before checkpointing, verify:
- Choosing `9:16` for a dense desktop capture just because the user asked for Shorts.
- Writing a concept-heavy brief when the user really needs task completion.
- Failing to note silence; if there is no voiceover, downstream stages must know immediately.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -78,3 +78,12 @@ For developer or product-demo content, also package:
- Publishing with generic titles that omit the actual software or task.
- Using the same caption for YouTube, LinkedIn, and short-form social.
- Building chapter markers from the script without checking the render.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -129,3 +129,12 @@ If a step cannot survive vertical, say so. The correct answer is sometimes to sh
- Planning vertical crops for wide UI without admitting they fail.
- Adding highlight layers everywhere instead of choosing the single clearest cue.
- Ignoring sensitive data revealed in seemingly minor frames.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -125,3 +125,12 @@ add the source. Do not invent statistics, dates, or attributions.
- Letting spoken timing drift away from the visual action.
- Keeping builds and loading screens in real time.
- Writing a silent-recording script that secretly depends on unavailable TTS.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -206,3 +206,12 @@ This is especially important for:
- **Remotion component patterns** — new composition techniques emerge as the framework evolves
Do not rely on stale knowledge. When in doubt, search first.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -63,3 +63,12 @@ Create a brief artifact documenting:
### Step 5: Submit
Validate the brief against the schema and persist via checkpoint.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -50,3 +50,12 @@ Document the publish event with platform, status (draft), and export path.
### Step 6: Submit
Validate the publish_log against the schema and persist via checkpoint.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -241,3 +241,12 @@ Assemble the full scene plan with:
### Step 10: Submit
Validate the scene_plan against the schema and persist via checkpoint.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -65,3 +65,12 @@ If you encounter uncertainty during script writing:
Every factual claim in the script should be traceable to the `research_brief`.
If you make a claim that isn't in the research, do additional research and
add the source. Do not invent statistics, dates, or attributions.
---
## Gate Reminder (Binding)
This stage gates on human approval (`human_approval_default: true`). After review passes:
checkpoint with `status="awaiting_human"`, present the summary (the Backlot board renders
the artifact), and **END YOUR TURN**. Do not start the next stage in the same response.
Approval is per-gate — an earlier "go ahead" does not cover this gate.

View File

@ -0,0 +1,218 @@
"""Contract tests for Backlot Phase 0: gate enforcement, checkpoint history,
project markers, and tool-event instrumentation."""
import json
import pytest
from lib.checkpoint import (
CheckpointValidationError,
HISTORY_DIRNAME,
PROJECT_MARKER_FILENAME,
init_project,
read_checkpoint,
write_checkpoint,
)
from lib.events import emit_event, infer_project_dir, read_events
def _minimal_script() -> dict:
return {
"version": "1.0",
"title": "Test Script",
"total_duration_seconds": 10,
"sections": [
{"id": "s1", "text": "Hello.", "start_seconds": 0, "end_seconds": 10}
],
}
class TestGateEnforcement:
"""GI-4: gated stages cannot be completed without approval evidence."""
def test_completed_without_approval_raises(self, tmp_path):
with pytest.raises(CheckpointValidationError, match="GATE VIOLATION"):
write_checkpoint(
tmp_path, "proj", "script", "completed",
artifacts={"script": _minimal_script()},
pipeline_type="animated-explainer",
)
def test_awaiting_human_is_the_correct_gate_state(self, tmp_path):
path = write_checkpoint(
tmp_path, "proj", "script", "awaiting_human",
artifacts={"script": _minimal_script()},
pipeline_type="animated-explainer",
)
cp = json.loads(path.read_text())
assert cp["status"] == "awaiting_human"
# Manifest gating is reflected in the checkpoint even when the
# caller didn't pass human_approval_required.
assert cp["human_approval_required"] is True
def test_completed_with_approval_passes(self, tmp_path):
path = write_checkpoint(
tmp_path, "proj", "script", "completed",
artifacts={"script": _minimal_script()},
pipeline_type="animated-explainer",
human_approved=True,
)
assert path.exists()
def test_assets_stage_now_gates(self, tmp_path):
"""The assets gate flip: every pipeline's assets stage requires approval."""
manifest_assets = {"version": "1.0", "assets": [], "total_cost_usd": 0.0}
with pytest.raises(CheckpointValidationError, match="GATE VIOLATION"):
write_checkpoint(
tmp_path, "proj", "assets", "completed",
artifacts={"asset_manifest": manifest_assets},
pipeline_type="cinematic",
)
def test_ungated_stage_unaffected(self, tmp_path):
from tests.contracts.test_phase0_contracts import sample_artifact
path = write_checkpoint(
tmp_path, "proj", "research", "completed",
artifacts={"research_brief": sample_artifact("research_brief")},
pipeline_type="animated-explainer",
)
assert path.exists()
class TestCheckpointHistory:
"""Superseded checkpoints are archived, not destroyed."""
def test_overwrite_archives_previous(self, tmp_path):
write_checkpoint(
tmp_path, "proj", "script", "awaiting_human",
artifacts={"script": _minimal_script()},
pipeline_type="animated-explainer",
)
write_checkpoint(
tmp_path, "proj", "script", "completed",
artifacts={"script": _minimal_script()},
pipeline_type="animated-explainer",
human_approved=True,
)
history = list((tmp_path / "proj" / HISTORY_DIRNAME).glob("checkpoint_script_*.json"))
assert len(history) == 1
archived = json.loads(history[0].read_text())
assert archived["status"] == "awaiting_human"
current = read_checkpoint(tmp_path, "proj", "script")
assert current["status"] == "completed"
def test_in_progress_refreshes_are_not_archived(self, tmp_path):
for _ in range(3):
write_checkpoint(
tmp_path, "proj", "assets", "in_progress",
artifacts={},
pipeline_type="cinematic",
metadata={"partial_progress": {"completed_scene_ids": ["sc1"]}},
)
history_dir = tmp_path / "proj" / HISTORY_DIRNAME
assert not history_dir.exists() or not list(history_dir.iterdir())
class TestInitProject:
def test_creates_layout_and_marker(self, tmp_path):
pdir = init_project(
"my-film", title="My Film", pipeline_type="cinematic",
pipeline_dir=tmp_path, style_playbook="clean-professional",
)
assert (pdir / "artifacts").is_dir()
assert (pdir / "assets" / "images").is_dir()
assert (pdir / "renders").is_dir()
marker = json.loads((pdir / PROJECT_MARKER_FILENAME).read_text())
assert marker["project_id"] == "my-film"
assert marker["pipeline_type"] == "cinematic"
assert marker["style_playbook"] == "clean-professional"
assert "created_at" in marker
def test_idempotent_preserves_created_at(self, tmp_path):
pdir = init_project("p", title="P", pipeline_type="cinematic", pipeline_dir=tmp_path)
created = json.loads((pdir / PROJECT_MARKER_FILENAME).read_text())["created_at"]
init_project("p", title="P2", pipeline_type="cinematic", pipeline_dir=tmp_path)
marker = json.loads((pdir / PROJECT_MARKER_FILENAME).read_text())
assert marker["created_at"] == created
assert marker["title"] == "P2"
class TestEvents:
def test_emit_and_read_roundtrip(self, tmp_path):
emit_event(tmp_path, {"tool": "t1", "event": "start", "scene_id": "sc1"})
emit_event(tmp_path, {"tool": "t1", "event": "finish", "duration_s": 1.2})
events = read_events(tmp_path)
assert len(events) == 2
assert events[0]["event"] == "start"
assert events[1]["duration_s"] == 1.2
assert all("ts" in e for e in events)
def test_read_tolerates_garbage_lines(self, tmp_path):
(tmp_path / "events.jsonl").write_text('{"ok": 1}\nnot json\n{"ok": 2}\n')
events = read_events(tmp_path)
assert [e["ok"] for e in events] == [1, 2]
def test_infer_project_dir_from_output_path(self):
from lib.events import PROJECTS_DIR
target = PROJECTS_DIR / "some-proj" / "assets" / "images" / "x.png"
assert infer_project_dir({"output_path": str(target)}) == PROJECTS_DIR / "some-proj"
assert infer_project_dir({"output_path": "C:/elsewhere/x.png"}) is None
assert infer_project_dir("not-a-dict") is None
class TestBaseToolInstrumentation:
def test_execute_emits_events(self, tmp_path, monkeypatch):
import lib.events as events_mod
monkeypatch.setattr(events_mod, "PROJECTS_DIR", tmp_path)
from tools.base_tool import BaseTool, ToolResult
class FakeTool(BaseTool):
name = "fake_tool"
def execute(self, inputs):
return ToolResult(success=True, cost_usd=0.05)
project = tmp_path / "proj-x"
project.mkdir()
out = project / "assets" / "clip.mp4"
FakeTool().execute({"output_path": str(out), "scene_id": "sc3"})
events = read_events(project)
assert [e["event"] for e in events] == ["start", "finish"]
assert events[0]["scene_id"] == "sc3"
assert events[1]["success"] is True
assert events[1]["cost_usd"] == 0.05
def test_execute_emits_error_event_and_reraises(self, tmp_path, monkeypatch):
import lib.events as events_mod
monkeypatch.setattr(events_mod, "PROJECTS_DIR", tmp_path)
from tools.base_tool import BaseTool
class BoomTool(BaseTool):
name = "boom_tool"
def execute(self, inputs):
raise RuntimeError("kaput")
project = tmp_path / "proj-y"
project.mkdir()
with pytest.raises(RuntimeError, match="kaput"):
BoomTool().execute({"output_path": str(project / "a.png")})
events = read_events(project)
assert [e["event"] for e in events] == ["start", "error"]
assert "kaput" in events[1]["error"]
def test_unattributable_call_emits_nothing_and_works(self, tmp_path):
from tools.base_tool import BaseTool, ToolResult
class PlainTool(BaseTool):
name = "plain_tool"
def execute(self, inputs):
return ToolResult(success=True)
result = PlainTool().execute({"text": "hello"})
assert result.success is True

View File

@ -234,7 +234,7 @@ except Exception as e:
check("Proposal packet validates against schema", False, str(e))
cp_path = write_checkpoint(
PIPELINE_DIR, PROJECT_ID, "proposal", "completed",
PIPELINE_DIR, PROJECT_ID, "proposal", "completed", human_approved=True,
artifacts={"proposal_packet": proposal_packet},
pipeline_type="animated-explainer",
style_playbook="clean-professional",
@ -283,7 +283,7 @@ except Exception as e:
check("Script validates against schema", False, str(e))
write_checkpoint(
PIPELINE_DIR, PROJECT_ID, "script", "completed",
PIPELINE_DIR, PROJECT_ID, "script", "completed", human_approved=True,
artifacts={"script": script},
pipeline_type="animated-explainer",
)
@ -322,7 +322,7 @@ except Exception as e:
check("Scene plan validates against schema", False, str(e))
write_checkpoint(
PIPELINE_DIR, PROJECT_ID, "scene_plan", "completed",
PIPELINE_DIR, PROJECT_ID, "scene_plan", "completed", human_approved=True,
artifacts={"scene_plan": scene_plan},
pipeline_type="animated-explainer",
)
@ -401,7 +401,7 @@ tracker.reconcile(eid, 0.0, success=True)
print(f" Cost snapshot: {tracker.cost_snapshot()}")
write_checkpoint(
PIPELINE_DIR, PROJECT_ID, "assets", "completed",
PIPELINE_DIR, PROJECT_ID, "assets", "completed", human_approved=True,
artifacts={"asset_manifest": asset_manifest},
pipeline_type="animated-explainer",
cost_snapshot=tracker.cost_snapshot(),
@ -615,7 +615,7 @@ except Exception as e:
check("Publish log validates against schema", False, str(e))
write_checkpoint(
PIPELINE_DIR, PROJECT_ID, "publish", "completed",
PIPELINE_DIR, PROJECT_ID, "publish", "completed", human_approved=True,
artifacts={"publish_log": publish_log},
pipeline_type="animated-explainer",
)

View File

@ -6,6 +6,7 @@ interface for discovery, execution, cost estimation, and health reporting.
from __future__ import annotations
import functools
import hashlib
import inspect
import json
@ -13,6 +14,7 @@ import os
import platform
import subprocess
import shutil
import time
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum
@ -136,9 +138,87 @@ class ToolResult:
model: Optional[str] = None
def _instrument_execute(fn: Callable) -> Callable:
"""Wrap a tool's execute() with Backlot event emission.
Appends start/finish/error entries to the owning project's events.jsonl
when the call can be attributed to a project (explicit project_dir input
or any path input under projects/). Powers the board's live activity
ticker and per-scene generating states with zero agent involvement.
Instrumentation is strictly non-fatal: any failure inside the event layer
is swallowed and the tool call proceeds untouched.
"""
if getattr(fn, "_backlot_instrumented", False):
return fn
@functools.wraps(fn)
def wrapper(self, inputs: Any, *args: Any, **kwargs: Any):
project_dir = None
tool_name = getattr(self, "name", "") or self.__class__.__name__
scene_id = inputs.get("scene_id") if isinstance(inputs, dict) else None
output_path = inputs.get("output_path") if isinstance(inputs, dict) else None
try:
from lib.events import emit_event, infer_project_dir
project_dir = infer_project_dir(inputs)
if project_dir is not None:
emit_event(project_dir, {
"tool": tool_name,
"event": "start",
"scene_id": scene_id,
"output_path": str(output_path) if output_path else None,
})
except Exception:
project_dir = None
started = time.monotonic()
try:
result = fn(self, inputs, *args, **kwargs)
except Exception as exc:
if project_dir is not None:
try:
from lib.events import emit_event
emit_event(project_dir, {
"tool": tool_name,
"event": "error",
"scene_id": scene_id,
"error": str(exc)[:300],
"duration_s": round(time.monotonic() - started, 2),
})
except Exception:
pass
raise
if project_dir is not None:
try:
from lib.events import emit_event
emit_event(project_dir, {
"tool": tool_name,
"event": "finish",
"scene_id": scene_id,
"output_path": str(output_path) if output_path else None,
"success": getattr(result, "success", None),
"cost_usd": getattr(result, "cost_usd", None) or None,
"duration_s": round(time.monotonic() - started, 2),
})
except Exception:
pass
return result
wrapper._backlot_instrumented = True # type: ignore[attr-defined]
return wrapper
class BaseTool(ABC):
"""Abstract base class for all OpenMontage tools."""
def __init_subclass__(cls, **kwargs: Any) -> None:
"""Auto-instrument every concrete execute() with Backlot events."""
super().__init_subclass__(**kwargs)
impl = cls.__dict__.get("execute")
if impl is not None and not getattr(impl, "__isabstractmethod__", False):
cls.execute = _instrument_execute(impl)
# --- Identity (override in subclasses) ---
name: str = ""
version: str = "0.1.0"