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:
parent
169124d0fd
commit
722491d732
|
|
@ -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.
|
||||
|
|
@ -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`.
|
||||
|
|
@ -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`.
|
||||
|
|
@ -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`.
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue