From 722491d7324c5c9d0c2fb8e4e8eb82fda5819f89 Mon Sep 17 00:00:00 2001 From: calesthio Date: Wed, 1 Jul 2026 23:08:51 -0700 Subject: [PATCH] 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//history/ (stage versioning, gate audit trail, replay) - BaseTool auto-instruments execute() -> projects//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// - gate reminder footer on all gating stage director skills - /backlot command files for Claude Code, Codex, Cursor, Copilot --- .claude/commands/backlot.md | 15 ++ .codex/prompts/backlot.md | 12 + .cursor/commands/backlot.md | 12 + .github/prompts/backlot.prompt.md | 12 + AGENT_GUIDE.md | 24 +- lib/checkpoint.py | 135 +++++++++++ lib/events.py | 111 +++++++++ pipeline_defs/animated-explainer.yaml | 2 +- pipeline_defs/animation.yaml | 2 +- pipeline_defs/avatar-spokesperson.yaml | 2 +- pipeline_defs/character-animation.yaml | 2 +- pipeline_defs/cinematic.yaml | 2 +- pipeline_defs/clip-factory.yaml | 2 +- pipeline_defs/documentary-montage.yaml | 2 +- pipeline_defs/hybrid.yaml | 2 +- pipeline_defs/localization-dub.yaml | 2 +- pipeline_defs/podcast-repurpose.yaml | 2 +- pipeline_defs/screen-demo.yaml | 2 +- pipeline_defs/talking-head.yaml | 2 +- skills/meta/checkpoint-protocol.md | 70 ++++-- skills/pipelines/animation/asset-director.md | 9 + skills/pipelines/animation/idea-director.md | 9 + .../pipelines/animation/proposal-director.md | 9 + .../pipelines/animation/publish-director.md | 9 + skills/pipelines/animation/scene-director.md | 9 + skills/pipelines/animation/script-director.md | 9 + .../avatar-spokesperson/asset-director.md | 9 + .../avatar-spokesperson/idea-director.md | 9 + .../avatar-spokesperson/publish-director.md | 9 + .../avatar-spokesperson/scene-director.md | 9 + .../avatar-spokesperson/script-director.md | 9 + .../character-animation/asset-director.md | 9 + .../character-design-director.md | 9 + .../character-animation/proposal-director.md | 9 + .../character-animation/publish-director.md | 9 + .../character-animation/scene-director.md | 9 + .../character-animation/script-director.md | 9 + skills/pipelines/cinematic/asset-director.md | 9 + skills/pipelines/cinematic/idea-director.md | 9 + .../pipelines/cinematic/proposal-director.md | 9 + .../pipelines/cinematic/publish-director.md | 9 + skills/pipelines/cinematic/scene-director.md | 9 + skills/pipelines/cinematic/script-director.md | 9 + .../pipelines/clip-factory/asset-director.md | 9 + .../pipelines/clip-factory/idea-director.md | 9 + .../clip-factory/publish-director.md | 9 + .../pipelines/clip-factory/scene-director.md | 9 + .../pipelines/clip-factory/script-director.md | 9 + .../documentary-montage/asset-director.md | 9 + .../documentary-montage/idea-director.md | 9 + .../documentary-montage/scene-director.md | 9 + skills/pipelines/explainer/asset-director.md | 9 + skills/pipelines/explainer/idea-director.md | 9 + .../pipelines/explainer/proposal-director.md | 9 + .../pipelines/explainer/publish-director.md | 9 + skills/pipelines/explainer/scene-director.md | 9 + skills/pipelines/explainer/script-director.md | 9 + skills/pipelines/hybrid/asset-director.md | 9 + skills/pipelines/hybrid/idea-director.md | 9 + skills/pipelines/hybrid/publish-director.md | 9 + skills/pipelines/hybrid/scene-director.md | 9 + skills/pipelines/hybrid/script-director.md | 9 + .../localization-dub/asset-director.md | 9 + .../localization-dub/idea-director.md | 9 + .../localization-dub/publish-director.md | 9 + .../localization-dub/scene-director.md | 9 + .../localization-dub/script-director.md | 9 + .../podcast-repurpose/asset-director.md | 9 + .../podcast-repurpose/idea-director.md | 9 + .../podcast-repurpose/publish-director.md | 9 + .../podcast-repurpose/scene-director.md | 9 + .../podcast-repurpose/script-director.md | 9 + .../pipelines/screen-demo/asset-director.md | 9 + skills/pipelines/screen-demo/idea-director.md | 9 + .../pipelines/screen-demo/publish-director.md | 9 + .../pipelines/screen-demo/scene-director.md | 9 + .../pipelines/screen-demo/script-director.md | 9 + .../pipelines/talking-head/asset-director.md | 9 + .../pipelines/talking-head/idea-director.md | 9 + .../talking-head/publish-director.md | 9 + .../pipelines/talking-head/scene-director.md | 9 + .../pipelines/talking-head/script-director.md | 9 + tests/contracts/test_backlot_contract.py | 218 ++++++++++++++++++ tests/qa/test_08_end_to_end.py | 10 +- tools/base_tool.py | 80 +++++++ 85 files changed, 1243 insertions(+), 38 deletions(-) create mode 100644 .claude/commands/backlot.md create mode 100644 .codex/prompts/backlot.md create mode 100644 .cursor/commands/backlot.md create mode 100644 .github/prompts/backlot.prompt.md create mode 100644 lib/events.py create mode 100644 tests/contracts/test_backlot_contract.py diff --git a/.claude/commands/backlot.md b/.claude/commands/backlot.md new file mode 100644 index 00000000..be09edf7 --- /dev/null +++ b/.claude/commands/backlot.md @@ -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//` 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. diff --git a/.codex/prompts/backlot.md b/.codex/prompts/backlot.md new file mode 100644 index 00000000..9af8f2be --- /dev/null +++ b/.codex/prompts/backlot.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 +``` + +- 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//` on disk; never update the UI manually. Keep checkpoints and artifacts honest per `skills/meta/checkpoint-protocol.md`. diff --git a/.cursor/commands/backlot.md b/.cursor/commands/backlot.md new file mode 100644 index 00000000..9af8f2be --- /dev/null +++ b/.cursor/commands/backlot.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 +``` + +- 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//` on disk; never update the UI manually. Keep checkpoints and artifacts honest per `skills/meta/checkpoint-protocol.md`. diff --git a/.github/prompts/backlot.prompt.md b/.github/prompts/backlot.prompt.md new file mode 100644 index 00000000..9af8f2be --- /dev/null +++ b/.github/prompts/backlot.prompt.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 +``` + +- 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//` on disk; never update the UI manually. Keep checkpoints and artifacts honest per `skills/meta/checkpoint-protocol.md`. diff --git a/AGENT_GUIDE.md b/AGENT_GUIDE.md index 085a52d3..e1506c06 100644 --- a/AGENT_GUIDE.md +++ b/AGENT_GUIDE.md @@ -213,7 +213,14 @@ projects// **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('', 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: diff --git a/lib/checkpoint.py b/lib/checkpoint.py index 8a070234..d8a37c88 100644 --- a/lib/checkpoint.py +++ b/lib/checkpoint.py @@ -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) diff --git a/lib/events.py b/lib/events.py new file mode 100644 index 00000000..a8449b53 --- /dev/null +++ b/lib/events.py @@ -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 diff --git a/pipeline_defs/animated-explainer.yaml b/pipeline_defs/animated-explainer.yaml index fb6c9f84..bb351bba 100644 --- a/pipeline_defs/animated-explainer.yaml +++ b/pipeline_defs/animated-explainer.yaml @@ -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 diff --git a/pipeline_defs/animation.yaml b/pipeline_defs/animation.yaml index e2361cc3..1002eabf 100644 --- a/pipeline_defs/animation.yaml +++ b/pipeline_defs/animation.yaml @@ -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 diff --git a/pipeline_defs/avatar-spokesperson.yaml b/pipeline_defs/avatar-spokesperson.yaml index 4b6afae7..21beedf3 100644 --- a/pipeline_defs/avatar-spokesperson.yaml +++ b/pipeline_defs/avatar-spokesperson.yaml @@ -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 diff --git a/pipeline_defs/character-animation.yaml b/pipeline_defs/character-animation.yaml index 22213467..e40452ec 100644 --- a/pipeline_defs/character-animation.yaml +++ b/pipeline_defs/character-animation.yaml @@ -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 diff --git a/pipeline_defs/cinematic.yaml b/pipeline_defs/cinematic.yaml index 1c089f92..0f806d6b 100644 --- a/pipeline_defs/cinematic.yaml +++ b/pipeline_defs/cinematic.yaml @@ -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 diff --git a/pipeline_defs/clip-factory.yaml b/pipeline_defs/clip-factory.yaml index 690957f2..8f4e1345 100644 --- a/pipeline_defs/clip-factory.yaml +++ b/pipeline_defs/clip-factory.yaml @@ -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 diff --git a/pipeline_defs/documentary-montage.yaml b/pipeline_defs/documentary-montage.yaml index d1bb3645..2b7ef65c 100644 --- a/pipeline_defs/documentary-montage.yaml +++ b/pipeline_defs/documentary-montage.yaml @@ -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 diff --git a/pipeline_defs/hybrid.yaml b/pipeline_defs/hybrid.yaml index 0de387eb..b46dd642 100644 --- a/pipeline_defs/hybrid.yaml +++ b/pipeline_defs/hybrid.yaml @@ -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 diff --git a/pipeline_defs/localization-dub.yaml b/pipeline_defs/localization-dub.yaml index 726a348d..f4ee1fb5 100644 --- a/pipeline_defs/localization-dub.yaml +++ b/pipeline_defs/localization-dub.yaml @@ -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 diff --git a/pipeline_defs/podcast-repurpose.yaml b/pipeline_defs/podcast-repurpose.yaml index f41e97f9..ed33181a 100644 --- a/pipeline_defs/podcast-repurpose.yaml +++ b/pipeline_defs/podcast-repurpose.yaml @@ -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 diff --git a/pipeline_defs/screen-demo.yaml b/pipeline_defs/screen-demo.yaml index 950f4845..9b7920d7 100644 --- a/pipeline_defs/screen-demo.yaml +++ b/pipeline_defs/screen-demo.yaml @@ -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 diff --git a/pipeline_defs/talking-head.yaml b/pipeline_defs/talking-head.yaml index 33a21787..8bcfd35e 100644 --- a/pipeline_defs/talking-head.yaml +++ b/pipeline_defs/talking-head.yaml @@ -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 diff --git a/skills/meta/checkpoint-protocol.md b/skills/meta/checkpoint-protocol.md index 700be44f..7adfc043 100644 --- a/skills/meta/checkpoint-protocol.md +++ b/skills/meta/checkpoint-protocol.md @@ -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 diff --git a/skills/pipelines/animation/asset-director.md b/skills/pipelines/animation/asset-director.md index 26830c00..6f61a92b 100644 --- a/skills/pipelines/animation/asset-director.md +++ b/skills/pipelines/animation/asset-director.md @@ -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. diff --git a/skills/pipelines/animation/idea-director.md b/skills/pipelines/animation/idea-director.md index 424eb472..f8f6cbae 100644 --- a/skills/pipelines/animation/idea-director.md +++ b/skills/pipelines/animation/idea-director.md @@ -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. diff --git a/skills/pipelines/animation/proposal-director.md b/skills/pipelines/animation/proposal-director.md index 5dc7bdeb..7ea0fb86 100644 --- a/skills/pipelines/animation/proposal-director.md +++ b/skills/pipelines/animation/proposal-director.md @@ -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. diff --git a/skills/pipelines/animation/publish-director.md b/skills/pipelines/animation/publish-director.md index 1048201c..c670a93e 100644 --- a/skills/pipelines/animation/publish-director.md +++ b/skills/pipelines/animation/publish-director.md @@ -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. diff --git a/skills/pipelines/animation/scene-director.md b/skills/pipelines/animation/scene-director.md index f5a247fe..7245fd63 100644 --- a/skills/pipelines/animation/scene-director.md +++ b/skills/pipelines/animation/scene-director.md @@ -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. diff --git a/skills/pipelines/animation/script-director.md b/skills/pipelines/animation/script-director.md index 11f56ef1..11c8c9f1 100644 --- a/skills/pipelines/animation/script-director.md +++ b/skills/pipelines/animation/script-director.md @@ -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. diff --git a/skills/pipelines/avatar-spokesperson/asset-director.md b/skills/pipelines/avatar-spokesperson/asset-director.md index 9b2ca32d..400a5176 100644 --- a/skills/pipelines/avatar-spokesperson/asset-director.md +++ b/skills/pipelines/avatar-spokesperson/asset-director.md @@ -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. diff --git a/skills/pipelines/avatar-spokesperson/idea-director.md b/skills/pipelines/avatar-spokesperson/idea-director.md index e1a84e84..040c2240 100644 --- a/skills/pipelines/avatar-spokesperson/idea-director.md +++ b/skills/pipelines/avatar-spokesperson/idea-director.md @@ -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. diff --git a/skills/pipelines/avatar-spokesperson/publish-director.md b/skills/pipelines/avatar-spokesperson/publish-director.md index a3005f79..0a6d7103 100644 --- a/skills/pipelines/avatar-spokesperson/publish-director.md +++ b/skills/pipelines/avatar-spokesperson/publish-director.md @@ -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. diff --git a/skills/pipelines/avatar-spokesperson/scene-director.md b/skills/pipelines/avatar-spokesperson/scene-director.md index e827ff22..f224ba73 100644 --- a/skills/pipelines/avatar-spokesperson/scene-director.md +++ b/skills/pipelines/avatar-spokesperson/scene-director.md @@ -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. diff --git a/skills/pipelines/avatar-spokesperson/script-director.md b/skills/pipelines/avatar-spokesperson/script-director.md index aab93102..cb1aa017 100644 --- a/skills/pipelines/avatar-spokesperson/script-director.md +++ b/skills/pipelines/avatar-spokesperson/script-director.md @@ -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. diff --git a/skills/pipelines/character-animation/asset-director.md b/skills/pipelines/character-animation/asset-director.md index c6161a0b..6a76d70e 100644 --- a/skills/pipelines/character-animation/asset-director.md +++ b/skills/pipelines/character-animation/asset-director.md @@ -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. diff --git a/skills/pipelines/character-animation/character-design-director.md b/skills/pipelines/character-animation/character-design-director.md index af54377f..27a38dac 100644 --- a/skills/pipelines/character-animation/character-design-director.md +++ b/skills/pipelines/character-animation/character-design-director.md @@ -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. diff --git a/skills/pipelines/character-animation/proposal-director.md b/skills/pipelines/character-animation/proposal-director.md index b250c221..55c490f9 100644 --- a/skills/pipelines/character-animation/proposal-director.md +++ b/skills/pipelines/character-animation/proposal-director.md @@ -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. diff --git a/skills/pipelines/character-animation/publish-director.md b/skills/pipelines/character-animation/publish-director.md index 6ddf2346..52a0deaa 100644 --- a/skills/pipelines/character-animation/publish-director.md +++ b/skills/pipelines/character-animation/publish-director.md @@ -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. diff --git a/skills/pipelines/character-animation/scene-director.md b/skills/pipelines/character-animation/scene-director.md index 1bd57339..c1b2063e 100644 --- a/skills/pipelines/character-animation/scene-director.md +++ b/skills/pipelines/character-animation/scene-director.md @@ -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. diff --git a/skills/pipelines/character-animation/script-director.md b/skills/pipelines/character-animation/script-director.md index c1c310ed..0c784f1e 100644 --- a/skills/pipelines/character-animation/script-director.md +++ b/skills/pipelines/character-animation/script-director.md @@ -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. diff --git a/skills/pipelines/cinematic/asset-director.md b/skills/pipelines/cinematic/asset-director.md index 73f45a26..17da2b00 100644 --- a/skills/pipelines/cinematic/asset-director.md +++ b/skills/pipelines/cinematic/asset-director.md @@ -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. diff --git a/skills/pipelines/cinematic/idea-director.md b/skills/pipelines/cinematic/idea-director.md index 2f0f1c6a..0f332c81 100644 --- a/skills/pipelines/cinematic/idea-director.md +++ b/skills/pipelines/cinematic/idea-director.md @@ -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. diff --git a/skills/pipelines/cinematic/proposal-director.md b/skills/pipelines/cinematic/proposal-director.md index 9508e164..8537dd8f 100644 --- a/skills/pipelines/cinematic/proposal-director.md +++ b/skills/pipelines/cinematic/proposal-director.md @@ -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. diff --git a/skills/pipelines/cinematic/publish-director.md b/skills/pipelines/cinematic/publish-director.md index bf70f0af..b7d16725 100644 --- a/skills/pipelines/cinematic/publish-director.md +++ b/skills/pipelines/cinematic/publish-director.md @@ -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. diff --git a/skills/pipelines/cinematic/scene-director.md b/skills/pipelines/cinematic/scene-director.md index d03817bb..034a026d 100644 --- a/skills/pipelines/cinematic/scene-director.md +++ b/skills/pipelines/cinematic/scene-director.md @@ -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. diff --git a/skills/pipelines/cinematic/script-director.md b/skills/pipelines/cinematic/script-director.md index 4f065e40..b5e3e512 100644 --- a/skills/pipelines/cinematic/script-director.md +++ b/skills/pipelines/cinematic/script-director.md @@ -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. diff --git a/skills/pipelines/clip-factory/asset-director.md b/skills/pipelines/clip-factory/asset-director.md index 027cc605..8b537a6a 100644 --- a/skills/pipelines/clip-factory/asset-director.md +++ b/skills/pipelines/clip-factory/asset-director.md @@ -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. diff --git a/skills/pipelines/clip-factory/idea-director.md b/skills/pipelines/clip-factory/idea-director.md index ea0fe712..7563231a 100644 --- a/skills/pipelines/clip-factory/idea-director.md +++ b/skills/pipelines/clip-factory/idea-director.md @@ -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. diff --git a/skills/pipelines/clip-factory/publish-director.md b/skills/pipelines/clip-factory/publish-director.md index 0273b83c..e6a53bf4 100644 --- a/skills/pipelines/clip-factory/publish-director.md +++ b/skills/pipelines/clip-factory/publish-director.md @@ -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. diff --git a/skills/pipelines/clip-factory/scene-director.md b/skills/pipelines/clip-factory/scene-director.md index a321385d..8549497e 100644 --- a/skills/pipelines/clip-factory/scene-director.md +++ b/skills/pipelines/clip-factory/scene-director.md @@ -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. diff --git a/skills/pipelines/clip-factory/script-director.md b/skills/pipelines/clip-factory/script-director.md index fc6a16a5..20b14d18 100644 --- a/skills/pipelines/clip-factory/script-director.md +++ b/skills/pipelines/clip-factory/script-director.md @@ -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. diff --git a/skills/pipelines/documentary-montage/asset-director.md b/skills/pipelines/documentary-montage/asset-director.md index 17e58db7..edad9580 100644 --- a/skills/pipelines/documentary-montage/asset-director.md +++ b/skills/pipelines/documentary-montage/asset-director.md @@ -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. diff --git a/skills/pipelines/documentary-montage/idea-director.md b/skills/pipelines/documentary-montage/idea-director.md index 6b07657a..204187d2 100644 --- a/skills/pipelines/documentary-montage/idea-director.md +++ b/skills/pipelines/documentary-montage/idea-director.md @@ -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. diff --git a/skills/pipelines/documentary-montage/scene-director.md b/skills/pipelines/documentary-montage/scene-director.md index 3e3905a7..9280be61 100644 --- a/skills/pipelines/documentary-montage/scene-director.md +++ b/skills/pipelines/documentary-montage/scene-director.md @@ -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. diff --git a/skills/pipelines/explainer/asset-director.md b/skills/pipelines/explainer/asset-director.md index b98c358d..307091cc 100644 --- a/skills/pipelines/explainer/asset-director.md +++ b/skills/pipelines/explainer/asset-director.md @@ -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. diff --git a/skills/pipelines/explainer/idea-director.md b/skills/pipelines/explainer/idea-director.md index 6b8deb6b..f500804b 100644 --- a/skills/pipelines/explainer/idea-director.md +++ b/skills/pipelines/explainer/idea-director.md @@ -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. diff --git a/skills/pipelines/explainer/proposal-director.md b/skills/pipelines/explainer/proposal-director.md index 526cc61c..8e5e59b6 100644 --- a/skills/pipelines/explainer/proposal-director.md +++ b/skills/pipelines/explainer/proposal-director.md @@ -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. diff --git a/skills/pipelines/explainer/publish-director.md b/skills/pipelines/explainer/publish-director.md index aaa2dcd6..b55d09a3 100644 --- a/skills/pipelines/explainer/publish-director.md +++ b/skills/pipelines/explainer/publish-director.md @@ -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. diff --git a/skills/pipelines/explainer/scene-director.md b/skills/pipelines/explainer/scene-director.md index 0d37827a..51238bb0 100644 --- a/skills/pipelines/explainer/scene-director.md +++ b/skills/pipelines/explainer/scene-director.md @@ -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. diff --git a/skills/pipelines/explainer/script-director.md b/skills/pipelines/explainer/script-director.md index 42005743..f00404c8 100644 --- a/skills/pipelines/explainer/script-director.md +++ b/skills/pipelines/explainer/script-director.md @@ -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. diff --git a/skills/pipelines/hybrid/asset-director.md b/skills/pipelines/hybrid/asset-director.md index 89b7e0e5..049baade 100644 --- a/skills/pipelines/hybrid/asset-director.md +++ b/skills/pipelines/hybrid/asset-director.md @@ -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. diff --git a/skills/pipelines/hybrid/idea-director.md b/skills/pipelines/hybrid/idea-director.md index 5c3a8b54..0aa620d3 100644 --- a/skills/pipelines/hybrid/idea-director.md +++ b/skills/pipelines/hybrid/idea-director.md @@ -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. diff --git a/skills/pipelines/hybrid/publish-director.md b/skills/pipelines/hybrid/publish-director.md index e8e10a6d..17e65ec8 100644 --- a/skills/pipelines/hybrid/publish-director.md +++ b/skills/pipelines/hybrid/publish-director.md @@ -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. diff --git a/skills/pipelines/hybrid/scene-director.md b/skills/pipelines/hybrid/scene-director.md index b31f5683..61182fb8 100644 --- a/skills/pipelines/hybrid/scene-director.md +++ b/skills/pipelines/hybrid/scene-director.md @@ -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. diff --git a/skills/pipelines/hybrid/script-director.md b/skills/pipelines/hybrid/script-director.md index 2d6d9de7..6c1ab72f 100644 --- a/skills/pipelines/hybrid/script-director.md +++ b/skills/pipelines/hybrid/script-director.md @@ -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. diff --git a/skills/pipelines/localization-dub/asset-director.md b/skills/pipelines/localization-dub/asset-director.md index 637f3d44..ebd35cd4 100644 --- a/skills/pipelines/localization-dub/asset-director.md +++ b/skills/pipelines/localization-dub/asset-director.md @@ -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. diff --git a/skills/pipelines/localization-dub/idea-director.md b/skills/pipelines/localization-dub/idea-director.md index c1149fef..8c48b9a3 100644 --- a/skills/pipelines/localization-dub/idea-director.md +++ b/skills/pipelines/localization-dub/idea-director.md @@ -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. diff --git a/skills/pipelines/localization-dub/publish-director.md b/skills/pipelines/localization-dub/publish-director.md index bd39714a..69fbf765 100644 --- a/skills/pipelines/localization-dub/publish-director.md +++ b/skills/pipelines/localization-dub/publish-director.md @@ -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. diff --git a/skills/pipelines/localization-dub/scene-director.md b/skills/pipelines/localization-dub/scene-director.md index 7dfdae44..11662de5 100644 --- a/skills/pipelines/localization-dub/scene-director.md +++ b/skills/pipelines/localization-dub/scene-director.md @@ -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. diff --git a/skills/pipelines/localization-dub/script-director.md b/skills/pipelines/localization-dub/script-director.md index 2a0f95f8..58223ace 100644 --- a/skills/pipelines/localization-dub/script-director.md +++ b/skills/pipelines/localization-dub/script-director.md @@ -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. diff --git a/skills/pipelines/podcast-repurpose/asset-director.md b/skills/pipelines/podcast-repurpose/asset-director.md index 2902ad2c..f6df0538 100644 --- a/skills/pipelines/podcast-repurpose/asset-director.md +++ b/skills/pipelines/podcast-repurpose/asset-director.md @@ -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. diff --git a/skills/pipelines/podcast-repurpose/idea-director.md b/skills/pipelines/podcast-repurpose/idea-director.md index c40c12f3..d8632304 100644 --- a/skills/pipelines/podcast-repurpose/idea-director.md +++ b/skills/pipelines/podcast-repurpose/idea-director.md @@ -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. diff --git a/skills/pipelines/podcast-repurpose/publish-director.md b/skills/pipelines/podcast-repurpose/publish-director.md index 0323ebc9..9c99b53a 100644 --- a/skills/pipelines/podcast-repurpose/publish-director.md +++ b/skills/pipelines/podcast-repurpose/publish-director.md @@ -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. diff --git a/skills/pipelines/podcast-repurpose/scene-director.md b/skills/pipelines/podcast-repurpose/scene-director.md index b8671753..4190e327 100644 --- a/skills/pipelines/podcast-repurpose/scene-director.md +++ b/skills/pipelines/podcast-repurpose/scene-director.md @@ -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. diff --git a/skills/pipelines/podcast-repurpose/script-director.md b/skills/pipelines/podcast-repurpose/script-director.md index 11673624..76c70a8e 100644 --- a/skills/pipelines/podcast-repurpose/script-director.md +++ b/skills/pipelines/podcast-repurpose/script-director.md @@ -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. diff --git a/skills/pipelines/screen-demo/asset-director.md b/skills/pipelines/screen-demo/asset-director.md index 4f3df502..f4d7c8b2 100644 --- a/skills/pipelines/screen-demo/asset-director.md +++ b/skills/pipelines/screen-demo/asset-director.md @@ -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. diff --git a/skills/pipelines/screen-demo/idea-director.md b/skills/pipelines/screen-demo/idea-director.md index 5e62c126..2ae272a6 100644 --- a/skills/pipelines/screen-demo/idea-director.md +++ b/skills/pipelines/screen-demo/idea-director.md @@ -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. diff --git a/skills/pipelines/screen-demo/publish-director.md b/skills/pipelines/screen-demo/publish-director.md index 040f540a..a59002da 100644 --- a/skills/pipelines/screen-demo/publish-director.md +++ b/skills/pipelines/screen-demo/publish-director.md @@ -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. diff --git a/skills/pipelines/screen-demo/scene-director.md b/skills/pipelines/screen-demo/scene-director.md index 11f5b33f..b0777c5c 100644 --- a/skills/pipelines/screen-demo/scene-director.md +++ b/skills/pipelines/screen-demo/scene-director.md @@ -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. diff --git a/skills/pipelines/screen-demo/script-director.md b/skills/pipelines/screen-demo/script-director.md index fb1d6331..8c784fd2 100644 --- a/skills/pipelines/screen-demo/script-director.md +++ b/skills/pipelines/screen-demo/script-director.md @@ -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. diff --git a/skills/pipelines/talking-head/asset-director.md b/skills/pipelines/talking-head/asset-director.md index c0082607..6f31c0bb 100644 --- a/skills/pipelines/talking-head/asset-director.md +++ b/skills/pipelines/talking-head/asset-director.md @@ -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. diff --git a/skills/pipelines/talking-head/idea-director.md b/skills/pipelines/talking-head/idea-director.md index 7003ad7e..068bb032 100644 --- a/skills/pipelines/talking-head/idea-director.md +++ b/skills/pipelines/talking-head/idea-director.md @@ -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. diff --git a/skills/pipelines/talking-head/publish-director.md b/skills/pipelines/talking-head/publish-director.md index 456dba84..3141041a 100644 --- a/skills/pipelines/talking-head/publish-director.md +++ b/skills/pipelines/talking-head/publish-director.md @@ -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. diff --git a/skills/pipelines/talking-head/scene-director.md b/skills/pipelines/talking-head/scene-director.md index 39d1eb3e..a0ee4b6c 100644 --- a/skills/pipelines/talking-head/scene-director.md +++ b/skills/pipelines/talking-head/scene-director.md @@ -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. diff --git a/skills/pipelines/talking-head/script-director.md b/skills/pipelines/talking-head/script-director.md index c98b4f58..11f35334 100644 --- a/skills/pipelines/talking-head/script-director.md +++ b/skills/pipelines/talking-head/script-director.md @@ -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. diff --git a/tests/contracts/test_backlot_contract.py b/tests/contracts/test_backlot_contract.py new file mode 100644 index 00000000..50245baa --- /dev/null +++ b/tests/contracts/test_backlot_contract.py @@ -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 diff --git a/tests/qa/test_08_end_to_end.py b/tests/qa/test_08_end_to_end.py index cbbee2da..bef19072 100644 --- a/tests/qa/test_08_end_to_end.py +++ b/tests/qa/test_08_end_to_end.py @@ -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", ) diff --git a/tools/base_tool.py b/tools/base_tool.py index 50e6d249..3440c1d7 100644 --- a/tools/base_tool.py +++ b/tools/base_tool.py @@ -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"