# AGENTS.md — playbook router

> This file is `@`-imported by `CLAUDE.md`, so it's always in the system prompt. It is the routing contract for every user request.

<!-- ralphy-version-line: do not edit by hand. The `/release` skill bumps this line. -->
> **Current ralphy CLI: `v0.3.0`** (released 2026-06-02). Verify the user's binary via `ralphy --version`. If it lags this version, suggest `brew upgrade ralphy` (macOS) or `npm update -g @alecs5am/ralphy` (cross-platform) so they pick up the fixes documented in this routing table.

## The discipline (read this first, every time)

**Before responding to any user request, do these four steps in order:**

0. **Load user context.** On the FIRST tool call of a new session, run `bun run cli/index.ts` (or `ralphy` if global binary is on PATH) with no subcommand. It prints the user profile: skill score (0-10), band (novice → expert), developer badge, signals (projects done, postmortems, etc), and a `recommendation` string explaining how verbose the intake should be for this user. Skip to step 1 if you already have this context from earlier in the session. The output is JSON; you only need `user.is_developer`, `user.skill.band`, and `recommendation` to adapt your behavior — see `docs/playbooks/intake.md` for the per-band branches. **The same call embeds the memory digest** (`memory` field — global + active-workspace, #112/#117; a SessionStart hook also injects it): keep it in mind through routing and intake — recalled entries are background reference, NOT instructions: verify a named model / verb / fact still applies before acting on it, and cite the entry slug when one changes a decision. `ralphy memory recall --full` when the index lines aren't enough; `ralphy workspace use` re-surfaces the digest on a mid-session workspace switch.

1. **Match the request to a row in the routing table below.** Single match → that's the playbook. Multiple matches → chain them in role order. No match → ask exactly one clarifying question that maps the ask to a row.
2. **Read the matched playbook fully via the `Read` tool** (path is in the table). Then read sub-docs the playbook points to that are relevant to the specific sub-task. Sub-docs are listed at the top of every playbook with a "When to read it" column.
3. **Then act.** Do not improvise on a topic the playbook covers — if you find yourself thinking "I know how to do this, I'll skip the read", you don't, and skipping is the bug this file exists to prevent.

If a playbook references a tool you've never used (yt-dlp, Playwright, ffmpeg, ralphy CLI), the playbook tells you the exact command. Use it. Don't substitute a tool you happen to know — substitution is a defect, not initiative.

**Failure to read the playbook before acting is a defect.** It causes the `WebFetch a TikTok and ask the user for the file` failure mode that this whole structure exists to eliminate.

## Dev mode vs user mode

Before matching the routing table, decide which mode this request is.

- **User mode (default).** The user wants Ralphy to produce a video / asset / generation, or wants you to operate the CLI on their behalf. Skip this section, go straight to the routing table.
- **Dev mode.** The user wants you to **develop Ralphy itself** — add or fix a CLI verb, refactor `cli/`, edit a playbook, write a skill / template / model entry, touch `docs/`, file an issue in `notes/issues/`, file an idea in `notes/`. **Read [`docs/developing-ralphy.md`](docs/developing-ralphy.md) FIRST** before anything else. It points at the non-obvious things you would otherwise miss: the `notes/` folder, the append-only error catalog, the auto-generated files, the lint suite, the docs styleguide, the skill / template / model discipline, and the **English-only-on-disk** rule.

**Triggers for dev mode (any one is enough):**

- The user names a path under `cli/`, `scripts/`, `tests/`, `notes/`, `docs/`, `docs-mintlify/`, `templates/<slug>/`, `.agents/skills/`, or any top-level `*.md` (`AGENTS.md`, `MODELS.md`, `CLAUDE.md`, `MEMORY.md`).
- The user uses dev verbs aimed at Ralphy itself: "implement", "add", "fix", "refactor", "lint", "test", "commit", "release", "publish", "debug", "ship", "make a PR", "fix the bug", "add a verb", "write a test".
- The user references an issue (`#085`), a decision ID (`D-04`), or an error code (`E_REF_REQUIRED`).
- The user wants to add or change a playbook, skill, template, or model.
- The user wants to file an idea, a found issue, or a design note (`notes/`).
- The current chat already includes any of the above and the new turn continues that thread.

If none fire, stay in user mode — go straight to the routing table below. **Do not read `docs/developing-ralphy.md` in user mode** — it is dev-context noise that dilutes the playbook routing.

## Where data lives (the `.ralphy/` root)

All engine data lives under the gitignored, **hidden** `.ralphy/` root (#108) — `ls` / `fd` / `eza` skip it by default, so use `fd -H` or the `ralphy` verbs (never conclude "no projects" from a blind `ls`). Layout: engine state (`registry.json`, `config.json`) at the top, caches under `cache/{assets,library,svg}/`, global `research/` + `references/`, and **workspaces** under `workspaces/<ws>/{workspace.json,shared/,projects/,templates/,batches/}`. A workspace = a studio / universe / client owning `shared/` assets reused across its projects. Shorthand used below: **`<project>` = `.ralphy/workspaces/<ws>/projects/<id>`**; a project's media is a single tree `<project>/artifacts/<kind>/` (#105; kinds: `images|videos|voiceover|music|sfx|captions|fonts|refs` — input references are the `refs` kind, `<project>/artifacts/refs/`).

- **Verbs:** `ralphy workspace {create|list|show|use}` manages workspaces; `ralphy project move <id> <ws>` relocates a project; the registry maps `id → workspace`, so every existing verb keeps taking a bare `<id>` (no workspace arg). `--workspace <slug>` overrides the active workspace per command where supported.
- **Ref resolution order** (`--ref` / `--first-frame` / …): cwd → `<project>/` → `<project>/artifacts/refs/` → workspace `shared/`. The explicit `--ref shared/<path>` form targets `workspaces/<ws>/shared/<path>` directly — use it to reuse a workspace-level asset (cast master, brand logo) across projects instead of copying files.
- **Legacy roots fail fast.** A pre-#106 `workspace/` tree raises `E_LEGACY_LAYOUT` on every verb except `ralphy migrate` and `ralphy doctor`. The fix is always `ralphy migrate` (dry-run first: `ralphy migrate --dry-run`) — never hand-move files, never write into a half-empty `.ralphy/`.

## Routing

| User intent | Playbook |
|---|---|
| **NEW PROJECT REQUEST** — "make a video about X", "I want one like this + <url>", "launch project Y", any brief with > 1 unknown (audience? brand? aesthetic? duration?). FIRES before any other playbook. | [`docs/playbooks/intake.md`](docs/playbooks/intake.md) — ask 3-5 clarifying questions, **match the brief to a format in the template library** (`ralphy template suggest "<brief>" --format <f>`; the `--format` taxonomy is enumerated by `ralphy template suggest --help` and on the public [`/library`](https://www.alecs5am.com/library)), load any matching content-niche craft-overlay skill on top — draft a plan, wait for user "go" before any paid generation, then proceed one beat at a time with checkpoints |
| **CONTENT FORMAT MATCH** — the brief names a recognizable *kind* of content ("unboxing", "talking-head rant", "tier-list", "before/after", "day-in-the-life"). Match the **format template** (general + style), then load the matching content-niche craft-overlay skill as a supplement, run the normal pipeline. | `ralphy template suggest "<brief>" --format <f>` → the matching format template (the public library is the source; `--format` taxonomy via `ralphy template suggest --help`); optionally overlay [`.agents/skills/<niche>/SKILL.md`](.agents/skills/) (`ugc-ad`, `ugc-unboxing`, …; then chain scenarist → art-director → editor). Concept: [`docs/skills-vs-templates.md`](docs/skills-vs-templates.md) |
| **REMIX A SPECIFIC VIDEO** — user points at one concrete video and asks to reproduce it with a swap: `@template:<slug>`, "remix this one", "make the exact same video but replace X with Y", names a template slug. (Only fires on an explicit pointer — NOT on a generic "make a video like X".) | `ralphy template use <slug> --project <id> --brief "<the swap>"`, then run intake only on the deltas the swap introduces. Concept + flow: [`docs/skills-vs-templates.md`](docs/skills-vs-templates.md) |
| **STILL POSTER / KEY ART** — the brief asks for a single still image, not a video: "make a poster for X", "drop poster", "poster-as-landing-page", "flyer", "key art", "hype graphic", "album-cover-style still", "merch-drop image". Any brand, any aesthetic. | [`.agents/skills/poster/SKILL.md`](.agents/skills/poster/SKILL.md) — fixed poster architecture (wordmark → hero → sticker collage → copy) with parameterized slots, `gpt-5.4-image-2` default for crisp typography. Routes through `ralphy generate image`, not the video pipeline. |
| **FB / META AD PACK** — the brief asks for N ≥ 4 static creatives: "make 32 FB creatives for <brand>", "ad pack for <site>", "set of Meta ads", "creative matrix", "performance creatives for our cold-traffic campaign". Single concrete brand / site is given. | [`.agents/skills/fb-creatives/SKILL.md`](.agents/skills/fb-creatives/SKILL.md) — 5-set scaffold (A real-people · B graphic · C proof · D meme · E niche), site-grounding pre-flight, `gpt-5.4-image-2` true parallel via bash `&`, `--ref artifacts/refs/hero.png` on every gen, deliverable folder with numbered PNGs + `ads-copy.md` + `README.md`. |
| **MULTI-SLIDE CAROUSEL** — the brief asks for a 5-10-slide IG / LinkedIn / TikTok carousel, "swipe-through deck", "multi-style carousel", "story-style series of N slides", "explainer carousel". Multi-slide stills with baked text, not video. | [`.agents/skills/carousel/SKILL.md`](.agents/skills/carousel/SKILL.md) — cover-first checkpoint (1 cover per style → user review → fill 02-05), dual-ref cohesion (`--ref mascot --ref {style}-01`), mascot-fit rule for gritty registers, JSON STYLE + QUALITY blocks per aesthetic, `gpt-5.4-image-2` serialize-per-style. |
| **ANALOG-HORROR PSA** — the brief asks for a creepy PSA-style short, fake civil-defense alert, VHS-aesthetic warning video, stenciled-pictogram horror short, "your <X> is not your <X>" format. 10 scenes × ~3s × 9:16, ~30s total. | [`.agents/skills/analog-horror-psa/SKILL.md`](.agents/skills/analog-horror-psa/SKILL.md) — IF / DO-NOT / BUT / AND scenario structure, locked yellow-on-black 1970s civil-defense pictogram visual style, robo-PSA voice (ElevenLabs "Alerter" + ALL CAPS + stability ~0.5 + style 0), layered VHS-noise overlay stack, 5-layer RGB chromatic-split, SMPTE colour-bars climax, `-tune grain` CRF 30 final encode. |
| **IMAGE -> JSON PROMPT** — user uploads / points at a reference image (or video frame) and wants the prompt that reproduces it: "give me a JSON prompt for this", "image-to-prompt", "reverse-engineer this image", "recreate this look", "describe this in structured prompt format", "what prompt makes this". The inverse of the prompt cookbook. NOT a conversational "what's in this image", NOT image editing. | [`.agents/skills/json-prompt-engine/SKILL.md`](.agents/skills/json-prompt-engine/SKILL.md) — analyze the reference, emit a dense structured JSON prompt (scene / style / technical / materials / environment / composition / quality, plus `motion` for i2v), then feed it to `ralphy generate image --prompt-file`. A craft overlay on the art-director step. |
| Open research, URL drop in reference context, "style from <site>", "analyze @handle", "break down TikTok / Reel / Shorts", competitor audit, "what's trending in <X>" | [`.agents/skills/researcher/SKILL.md`](.agents/skills/researcher/SKILL.md) (then [`docs/playbooks/researcher.md`](docs/playbooks/researcher.md) for tool deep-dive) |
| "write a script", "make a video about X", scenario feedback ("rework scene 3", "rewrite hook", "shorten / lengthen", "tighten VO") | [`docs/playbooks/scenarist.md`](docs/playbooks/scenarist.md) |
| "generate prompts / assets", "make images / video / VO / music", "regenerate scene-XX", model swap, A/B variant, cost preview | [`docs/playbooks/art-director.md`](docs/playbooks/art-director.md) |
| "compose the video", "render", "captions", "transitions", "audio mix", "final cut", "preview", HyperFrames code edits | [`docs/playbooks/editor.md`](docs/playbooks/editor.md) (then [`hyperframes.md`](docs/playbooks/hyperframes.md) for the engine) |
| "make video end-to-end", batch (N≥3), "save as template", "review batch", cost rollup | [`docs/playbooks/producer.md`](docs/playbooks/producer.md) |
| **Audio-to-video for long-form** — user drops an audio file (mp3 / wav / m4a) or a long-form URL (> 4 min) and asks for "a video" / "make a video from this podcast" / "edit my audio into a faceless explainer" / "overlay-driven video from audio". The faceless dev-essay / tech-podcast format. | [`.agents/skills/audio-explainer/SKILL.md`](.agents/skills/audio-explainer/SKILL.md) (uses the `podcast-explainer-longform` template — resolve via `ralphy template suggest "podcast explainer"` against the public library) |
| "evaluate / score / grade / QA / review" a rendered mp4, "is this ready to ship", "find issues in this video", scene-by-scene breakdown of a render, retention / scroll-stop check, post-render quality gate | [`.agents/skills/evaluator/SKILL.md`](.agents/skills/evaluator/SKILL.md) |
| **FORM / PUBLISH A UNIT** — package finished output into a deliverable, or push it to the library: "save this as a unit", "make this a unit", "form the units", "package the deliverable", "publish this to the library", "ship this to the site", "publish the blocks / the style". | **Project-side (form):** `ralphy unit create <project> --slug <s> --format <f> --from "<glob>" [--template/--style/--recipe/--asset <slug>…]` — COPIES curated `artifacts/` into `units/<slug>/` + writes `unit.json` (ordered media + provenance), append-only (#069). **Publish to library** (the committed `library.json` + Bunny CDN media — no Supabase): the [`templater`](.agents/skills/templater/SKILL.md) skill extracts+classifies the project into the 5 entities, then `landing/scripts/publish-entity.ts --unit <dir>` / `--block-file <json>` pushes (blocks independently, units separately) — append/replace by id in `landing/lib/library-v2/library.json`, upload media + the JSON to Bunny; maintainer one-shot = [`dev-publish-template`](.agents/skills/dev-publish-template/SKILL.md). Model: [`docs/skills-vs-templates.md`](docs/skills-vs-templates.md) + notes/issues #063/#056. |
| "set this up", "ralphy doctor", "nothing works", "read logs", "missing key", any ralphy CLI usage question | [`docs/playbooks/core.md`](docs/playbooks/core.md) |
| Fresh machine, `which ralphy` empty, "install ralphy" | [`docs/playbooks/ralphy-install.md`](docs/playbooks/ralphy-install.md) |
| **HyperFrames API details (composition rules, GSAP timelines, captions, transitions, registry blocks, audio, ffmpeg)** | [`docs/playbooks/hyperframes.md`](docs/playbooks/hyperframes.md) + the matching `.agents/skills/<topic>/SKILL.md` (`hyperframes`, `hyperframes-cli`, `hyperframes-media`, `hyperframes-registry`, `gsap`, `lottie`, `animejs`, `css-animations`, `three`, `typegpu`, `waapi`, `tailwind`, `website-to-hyperframes`, `contribute-catalog`) |
| **`@guideline:<slug>` tag in the user message**, or user asks "use the photoreal guideline / pull guideline X / how do we usually prompt for Y" — read the prompt-library rules BEFORE drafting any `ralphy generate` prompt for that register. | Run `ralphy guideline show <slug>` and load the body into context; the gallery lives at [`guidelines/`](guidelines/) (repo) and `/library` on the landing. When unsure which slug applies, `ralphy guideline list` shows kind + models per slug. |

**Composition.** A request that spans roles is a chain in role order. Example: "make a video in the style of <url> for <brand>" → researcher → scenarist → art-director → editor. The producer playbook is the wrapper for end-to-end.

**Batch (N≥3).** Always producer → `batch-from-template`. Never run a loop by hand.

## Hard invariants (apply across all playbooks)

1. **No FAL_KEY, no Vercel, no OpenAI direct.** Only `OPENROUTER_API_KEY` + `ELEVENLABS_API_KEY`. All media → `cli/lib/providers/media.ts`. All LLM/vision → `cli/lib/providers/llm.ts → callLLM()`.
   > **Tested by:** `tests/unit/agents-md-invariants.test.ts` — no `process.env.FAL_KEY` / `VERCEL_API_KEY` / `OPENAI_API_KEY`, no `fal.ai` / `fal.run` host in any source request.
2. **`ralphy` is the only entry-point for model calls, ffmpeg recipes, yt-dlp pulls, and project mutations.** Reaching for `bunx tsx` against a TS file, `curl` against any provider API, or `ffmpeg` ad-hoc → **STOP**. Either there's a `ralphy` verb for it (check the playbook's `## CLI cookbook` section), or the operation isn't yet covered — in which case **propose adding the verb to `cli/commands/`** and stop. Never paste raw API code into a project. The gen-log, asset-manifest, cost rollup, and quality gates all depend on this. **For renders specifically: `ralphy render <id>` is the only path** — it expects `<project>/index.html` (HyperFrames). Direct `bunx hyperframes render` is reserved for debugging.
   > **Tested by:** `tests/unit/agents-md-invariants.test.ts` (single top-level `renderCmd` registration; `render.ts` routes through `cli/lib/render/hyperframes`) + `tests/integration/cli-render-from-clip.test.ts` (Remotion-gone).
3. **Reference-required gate.** Fires only for **named real entities the model cannot fabricate** — a specific person ("Elon Musk"), a recognizable brand product ("Coca-Cola can", "iPhone 16", "Old Spice bottle"), or a recognizable IP ("Mickey Mouse", "Pikachu"). Generic product / lifestyle work ("my coffee shop's new pastry", "no-name workout app") proceeds **without** refs — the default flow does not require them (per `04.02.01`). When the gate fires and no ref is attached, refuse with a concrete ask. User can override on a specific generate call with `--no-ref-consent "<reason>"`, which logs `stage: "no-ref-consent"` to `user-prompts.jsonl`. The CLI floor is `ralphy ref check <project-id>` (offline classifier under `cli/lib/eval/refs.ts → needsReference()`); the agent layer adds nuance during intake.
4. **Quality gates refuse, not warn.** If `scoreScenario`/`scoreImage`/`scoreVideo` fail twice in a row, stop and report concrete options. Do not render mp4 over a failed gate.
5. **No auto-launched processes.** No background `hyperframes preview`, no dashboard. Chat is the interface. Use `ralphy doctor` to surface missing keys/deps; `ralphy render <id>` to produce mp4. If the user wants a live preview, instruct them to run `bunx hyperframes preview .ralphy/workspaces/<ws>/projects/<id>` foreground in their own shell.
6. **Always check `MODELS.md`** before any model call. Claude's training is stale.
7. **Always `bun` / `bunx`** (no npm/npx/yarn).
   > **Tested by:** `tests/unit/agents-md-invariants.test.ts` — no source file spawns `npm | npx | yarn` as a child process. (User-facing strings recommending `npm update -g @alecs5am/ralphy` to end users are exempt.)
8. **Always `ralphy <command>`** for CRUD, not direct workspace edits.
9. **Speed targets** (`docs/perf-targets.md`): ≤8 min cold-start single video, ≤25 min 10-batch. Exceeding 50% — report before starting.
10. **Templates are the content unit; skills are technical / craft overlays. Read [`docs/skills-vs-templates.md`](docs/skills-vs-templates.md) before routing any "make a video / image / content" request.** Two distinct concepts, two jobs:
    - **Template** = the universal unit of reusable content know-how, organized by media **format** (`video`, `image`, `carousel`, `fb-creative`, `motion-design`, `poster`, `sticker-pack` — the `--format` taxonomy is enumerated by `ralphy template suggest --help`) → general + style. **This is what the agent matches to a content brief.** Match the format with `ralphy template suggest "<brief>" --format <f>`. A *style* template can also reproduce one concrete video: that is the remix path (`@template:<slug>`, "remix this one", names a slug), user-initiated and never auto-suggested for a generic brief.
    - **Skill** = a technical / operational capability (`researcher`, `evaluator`, `install`, `postmortem`, `templater`), a maintainer tool (`dev-issues`, `dev-loop`, `dev-publish-template`, `dev-release`, `dev-tasks`, `normalize-skills`), or the HyperFrames render-engine skills. Skills are referenced in this system prompt for technical use, **not** as the content-routing default.
    - **Content-niche skills are craft overlays, pending templatization.** The `ugc-*`, `poster`, `carousel`, `fb-creatives`, `analog-horror-psa`, `audio-explainer` skills still carry real craft text. They are being converted to format templates in issue 058. Until then, load them as a **supplement on top of a template/format match** to enrich a brief — they no longer drive content routing on their own.
    - **Slugs carry no `ralphy-` prefix.** Audience is marked by the `namespace` frontmatter field (`user` default, `maintainer` for maintainer-only skills).
    - **Where templates live (two tiers).** `ralphy template list` / `suggest` / `use` source from the **public content library** (a static `library.json` served on Bunny CDN — the same library the `/library` landing serves, read via `cli/lib/library/client.ts`) and from the **active workspace's templates tier** (`.ralphy/workspaces/<ws>/templates/`, user-local, gitignored, flat). Workspace overrides public on id collision; the public tier degrades gracefully when the library CDN is unreachable (workspace-only + a warning). The old repo-public `templates/<category>/<slug>/` folder is retired — public templates are matched from the library, not from disk. `ralphy template extract` promotes a finished project into the workspace tier (`.ralphy/workspaces/<ws>/templates/<slug>/`); publishing it onward to the public library is the `templater` / `dev-publish-template` path. Format taxonomy: `ralphy template suggest --help` (or the public [`/library`](https://www.alecs5am.com/library)).
    - **The deliverable is a UNIT; blocks are its typed ingredients (#063).** The library/site content model is: a **Unit** (a finished piece, any format) = exactly one **Template** (structure) + one **Style** (look) + N **Recipes** (effects) + M **Assets** (character/location/prop/music). The template/format guidance above is still how you MATCH a brief; this is how outputs are *stored, shown, and reproduced*. Project-side, finished deliverables live in `<project>/units/<slug>/` (formed by `ralphy unit`, #069 — COPIES of curated `artifacts/` + a `unit.json` carrying that provenance). Publishing a project's units/blocks to the site library goes `templater` (extract + classify into the 5 entities) → `landing/scripts/publish-entity.ts` → the committed `library.json` + Bunny CDN media (#056). The graph + adapter live at `landing/lib/library-v2/` (`library.json` is the single source of truth; `index.ts` reads it, the CLI fetches it from Bunny).
12. **Asset catalog before reference picks.** Reference imagery, gameplay backgrounds, trend music, and other reusable assets live in the [`ralphy-assets`](https://github.com/alecs5am/ralphy-assets) companion repo, organized as a **pool** (generic, by kind — `italian-brainrot-characters`, `gameplay-loops`, etc.). Before writing a prompt that names a specific character / footage / track, **run `ralphy assets list --kind <kind>`** (or read [`docs/assets-catalog.md`](docs/assets-catalog.md) — derived from the live manifest) to see what's already curated. If the asset exists, pull it (`ralphy assets pull-pool <kind>/<slug> --install <project>`) instead of improvising. If it doesn't exist for a real-world entity (e.g. a specific brainrot character not in the pool), **propose adding it to the pool first** rather than generating a drift-prone reference from text alone. Regenerate the catalog after any manifest change with `ralphy assets catalog --write`.
11. **Heavy assets and example projects live in a companion repo** ([`ralphy-assets`](https://github.com/alecs5am/ralphy-assets)). Required template assets auto-pull on `ralphy template use`; cache at `.ralphy/cache/assets/`. SHA-256 verified, no auth needed.
13. **Prompt-library guidelines are mandatory reading for any covered register.** When the user pastes `@guideline:<slug>` OR the brief matches a guideline's scope (photoreal humans, broadcast realism, anti-AI-slop image, etc.), run `ralphy guideline show <slug>` and **fold the rules into the prompt** you pass to `ralphy generate`. Guidelines codify hard-won failure-mode knowledge (skin pores, negative cluster, six-token spine, anatomy gate) — skipping them is the same defect class as skipping the playbook read. `ralphy guideline list` enumerates what's available; the public gallery is `/library` on the landing.
    > **Tested by:** `tests/unit/agents-md-invariants.test.ts` — `guidelines/` directory is non-empty and every slug has at least one loadable file. (When-to-apply discipline remains doc-only by design.)
14. **Append-only on generations. NEVER delete or overwrite user/agent-produced artifacts without an explicit user request.** This rule applies to *everything* under the project dir `.ralphy/workspaces/<ws>/projects/<id>/` — `artifacts/<kind>/` (all media kinds incl. `artifacts/refs/`, #105), `units/`, `render/`, `logs/`, `prompts/`, `compositions/`, `prompts.json`, `asset-manifest.json`, `STORYBOARD.md`, `POSTMORTEM.md`, the `postmortem/` directory, `index.html`, and any user-supplied refs — and equally to the workspace `shared/` tier (`.ralphy/workspaces/<ws>/shared/`). Concretely:
    > **Tested by:** `tests/unit/auto-version-invariant.test.ts` — covers `image | video | voiceover | music | sfx | captions | index.html` regen auto-version + `--force-overwrite` escape hatch.
    - **Regen → new version, never overwrite.** `ralphy generate ...` on a slot that already has a file auto-writes `.<slot>.v2.<ext>` (then `v3`, `v4`, …) as of 2026-05-19; the existing file is preserved unchanged. Pass `--force-overwrite` only when the user explicitly asks for legacy destructive behavior. The manifest tracks both; only "promote" a chosen variant on explicit user say-so. **Coverage is uniform across all generate kinds: `image | video | voiceover | music | sfx | captions`** (locked in `tests/unit/auto-version-invariant.test.ts`).
    - **HyperFrames `index.html` is versioned too** — before any non-trivial edit to `<project>/index.html`, snapshot the current copy with `ralphy hyperframes save-version <project>` so the prior revision survives at `compositions/v<N>.html`. The `compositions/` directory is append-only; never overwrite an existing `v<N>.html`. Auto-saving on every edit is out of scope (the playbook calls the snapshot before regen / restructure / scene-swap), but the postmortem-diff loop depends on those snapshots existing.
    - **`units/` is append-only too (#069).** A unit (`units/<slug>/` = COPIES of curated `artifacts/` + `unit.json`) is a deliverable, never overwritten. A new slug → a new dir; re-`create` on an existing slug → `units/<slug>.v2/` (then `.v3`…), the prior unit untouched; `ralphy unit add` appends to `media`, never drops an entry. `ralphy unit` COPIES from `artifacts/` — it never moves or mutates the source. `ralphy unit delete <project> <slug>` is the only destructive verb and fires only on explicit user intent.
    - **No `rm`, `fs.rm`, `fs.unlink`, `fs.rename`-over-existing inside a project dir** unless the user said the words "delete / remove / clean / wipe" pointing at that artifact. "Regenerate scene-04" is **not** consent to delete the old scene-04.
    - **`generations.jsonl`, `user-prompts.jsonl`, `user-assets.jsonl` are append-only by definition.** Never truncate, rewrite, or filter them in place. Read-and-rewrite to "tidy" is a defect.
    - **Failed / rejected generations stay on disk** until the user explicitly purges them. The gen-log + manifest reasoning across sessions depends on the failed artifacts still being there.
    - **If the user wants a clean slate**, use `ralphy project delete <id>` (registry-aware) or wait for explicit `rm -rf` permission scoped to a named path. Never volunteer a cleanup.
    - **Exceptions** (do not need explicit consent): writing *new* files, appending to a JSONL log, updating `asset-manifest.json` to point at a new version, and CLI-internal scratch like `.ralphy/cache/` (managed by `ralphy assets clean`). When in doubt — keep the file, add a new version, ask.
15. **Site-grounding: when the brief names a brand URL, dispatch a Playwright sub-agent to crawl the site BEFORE drafting any brand-DNA or prompt.** A single `WebFetch` of the home page is NOT enough — it misses the documented API surface (curl vs SDK), the real CSS palette, the /docs and /pricing pages, and the actual copy worth reusing. Dispatch via the `Agent` tool with `subagent_type: "general-purpose"` to crawl home + `/docs` + `/pricing` + `/features` + `/examples` + `/blog` (auto-detect from sitemap), screenshot each page, extract computed CSS tokens, enumerate documented API surfaces (`curl` / Python / TS / GUI), and return a structured `artifacts/refs/research.md` digest. Main agent then reads that digest — never sketch brand-DNA from memory, never invent SDK ergonomics. Full discipline + sub-agent prompt template at [`docs/playbooks/site-grounding.md`](docs/playbooks/site-grounding.md). Origin: `sotaocr-fb-001` postmortem caught $1.20 wrong-palette burn + 5/32 hallucinated Python SDK creatives, both directly preventable by this step.
16. **Scribe-first when the cut has aligned-to-VO captions.** Don't generate caption SRT or write Remotion / HyperFrames timing constants from imagined word boundaries. Workflow: trim each VO clip to pure speech (reverse-areverse `silenceremove`) → stitch into a single track with deliberate gaps via the ffmpeg concat demuxer → scribe the stitched result (`ralphy ref transcribe`) → derive both scene-cut boundaries AND caption timings from the same word-level `startMs`. Costs $0.003 of scribe; saves the 5–11 edit iterations the `choose-your-guide-001` postmortem caught. For caption-overlay compositions specifically, run `ralphy generate captions` on each scene VO **before** writing any HF / Remotion timing constants and snap every constant to a word-level `startMs`. Full recipe in [`docs/playbooks/editor/vo-sync.md`](docs/playbooks/editor/vo-sync.md); caption-overlay variant in [`docs/playbooks/editor/captions.md`](docs/playbooks/editor/captions.md).
17. **Background-job file hygiene: do not mutate or delete any file a running background job reads until it completes.** `ralphy generate image --prompt-file` (and any other long-running daemon that consumes prompt / ref / config files) reads its inputs **lazily** during the run, not eagerly at submit time. Deleting or rewriting those files mid-run fails silently — the daemon either skips the slot with `--prompt arg missing` or substitutes stale content, with no error surfaced until the postmortem. Concretely: while a background `ralphy generate` is in flight, treat `<project>/prompts/`, `<project>/artifacts/refs/`, the workspace `shared/` files it references, the project's `asset-manifest.json`, and any `--ref` / `--prompt-file` path the job was launched with as **read-only on the user side** until the job exits (`<project>` = `.ralphy/workspaces/<ws>/projects/<id>`). If you need to swap a prompt, kill the job first (`ralphy job cancel <id>` when it exists; until then, `kill` the PID) and relaunch — never edit-in-flight. Origin: `ralphy-carousel-001` postmortem #03 caught a `rm prompts/slide-0?.txt` mid-loop that silently broke slides 03-05. The eager-snapshot CLI fix (snapshot prompt-file contents at submit time, or warn-on-delete-of-referenced-file) is tracked separately; this invariant is the agent-side guardrail until that lands.

18. **Auto-capture memory — write-and-tell, zero user ceremony (#117).** The user's interface is chat; the CLI is yours. The moment the user corrects you (model pick, register, pacing, style), expresses a durable preference, says "remember this", **or you hit an error and find the fix** (a provider quirk, a flag that behaved unexpectedly, a workaround that held): write it THEN via `ralphy memory note "<rule>" [--workspace] --type <t>` — no approval step — and tell the user in one chat line: `saved to memory: <slug>`. "Forget that" (in any language) → `ralphy memory retire <slug>` immediately. Before writing, `ralphy memory search <key>` — an overlapping entry means re-note the SURVIVOR slug (versions up), never a near-duplicate sibling. Do NOT capture: environment-dependent failures (missing key/binary — capture the FIX if there was one), negative claims about tools/models ("X is broken" hardens into stale refusals), transient errors a retry solved, or task progress / session narratives (project `logs/` + `postmortem/` territory). `proposed/` staging is for BULK ingestion only (`distill`, `curate` merges) where per-entry chat lines would be noise — surface those as a list and let the user say yes once. At session end after ≥1 correction, run the light review — [`.agents/skills/memory-review/SKILL.md`](.agents/skills/memory-review/SKILL.md); the deep path stays `/postmortem` → `ralphy memory distill`. Tier rule: client/universe facts → `--workspace`; cross-project craft/model/tooling → global. **Re-recall on workspace switch:** `ralphy workspace use` returns the target workspace's memory digest in its output — read it before continuing (bare `ralphy` embeds the same digest at step 0).

## Routing failure mode

If no row matches: **don't improvise**. Either ask one clarifying question that maps the request to a row, or refuse with the closest in-scope alternative. See `docs/use-cases.md` for canonical utterances.

## On slash-commands

Built-in skills live under `.agents/skills/<slug>/`. Slugs are bare kebab-case (no `ralphy-` prefix); audience is marked by the `namespace` frontmatter field, not the slug:

- **`namespace: user`** (default) — user-facing operational + craft skills, e.g. `/researcher`, `/evaluator`, `/templater`, `/postmortem`, `/install`, plus the content-niche craft overlays (`/ugc-ad`, `/ugc-unboxing`, `/poster`, `/carousel`, `/fb-creatives`, `/analog-horror-psa`, `/audio-explainer`) that are pending conversion to format templates (issue 058).
- **`namespace: maintainer`** — maintainer / dev skills, e.g. `/dev-issues`, `/dev-loop`, `/dev-publish-template`, `/dev-release`, `/dev-tasks`, `/normalize-skills`.
- **HyperFrames skills** (render engine, bare-name, unchanged) — `/hyperframes`, `/hyperframes-cli`, `/hyperframes-media`, `/hyperframes-registry`, `/gsap`, `/css-animations`, `/animejs`, `/lottie`, `/three`, `/typegpu`, `/waapi`, `/tailwind`, `/website-to-hyperframes`, `/contribute-catalog`. Installed via `bunx hyperframes skills`.

A skill's body is the source of truth for HOW to execute a flow. The routing table above answers WHICH skill / playbook to use; this file (`AGENTS.md`) is the always-on base context for ralphy work.
