---
title: "References and the ref-required gate"
description: "When a brief names a real person, brand, or IP, Ralphy refuses to generate without a reference."
---

The reference-required gate is the single most important refusal in Ralphy. When a brief names a real human ("Elon Musk"), a recognizable brand product ("Coca-Cola can"), or a recognizable IP ("Pikachu"), Ralphy refuses to generate until you attach a reference. The classifier is offline, deterministic, and lives at [`cli/lib/eval/refs.ts`](https://github.com/alecs5am/ralphy/blob/main/cli/lib/eval/refs.ts). It is one of the 13 hard invariants in [`AGENTS.md`](https://github.com/alecs5am/ralphy/blob/main/AGENTS.md) (#3). Generic briefs — "my coffee shop's new pastry," "a no-name workout app" — pass through without refs.

This page covers the rule, the classifier, the override, and the example flows.

## Why the gate exists

Three reasons.

**Identity fidelity.** AI image and video models cannot fabricate a specific real human from text alone. "A photo of Elon Musk" produces a *plausible-looking middle-aged tech man* — often with the wrong nose, wrong hair pattern, wrong eye color. The video then drifts further. A real reference photo of Musk solves this in one shot — the model has a target to lock to.

**Legal posture.** Generating videos of named real people, real brand products, or copyrighted IP without a reference is a risk surface. The gate forces a deliberate decision: either you have rights to use the reference (you took the photo, you own the brand, the IP is in the public domain), or you are explicitly accepting the risk via `--no-ref-consent` with a logged reason.

**Cost.** The most expensive failure mode in 10 shipped postmortems was "the model generated five drift-y variants of a famous face before the agent noticed and pulled a real ref." Ten seconds of reading + ten seconds of attaching a ref saves ~$2-10 of regen burn per project.

## What triggers the gate

The classifier groups triggers into three buckets, in priority order: `person` > `brand-product` > `ip`. The lexicons in `cli/lib/eval/refs.ts` are deliberately short — "catch the obvious 80%," not "complete name database." False negatives are preferred over false positives.

### `person` — named real humans

Match list excerpts (regex anchored on word boundaries, case-insensitive):

- **Tech**: Elon Musk, Mark Zuckerberg, Steve Jobs, Bill Gates, Jeff Bezos, Satya Nadella, Sundar Pichai, Sam Altman, Jensen Huang.
- **Politics**: Donald Trump, Joe Biden, Barack Obama, Vladimir Putin.
- **Entertainment / sport**: Taylor Swift, Beyoncé, Kanye West, Lionel Messi, Cristiano Ronaldo, LeBron James, Kim Kardashian, MrBeast, Dwayne Johnson.

A persona name in the scenario that contains a famous match (`character.name = "Elon Musk"`) triggers. A generic role label (`character.name = "the barista"`) does *not* trigger — the classifier filters against a `GENERIC_ARCHETYPE_TOKENS` set: `barista`, `founder`, `ceo`, `engineer`, `teacher`, `student`, `courier`, `doctor`, `nurse`, `athlete`, `musician`, `chef`, `it-remote`, `designer`, `developer`.

### `brand-product` — recognizable products and packaging

Match list excerpts:

- **Drinks**: Coca-Cola, Coke, Pepsi, Sprite, Dr Pepper, Red Bull, Monster Energy.
- **Coffee / fast food**: Starbucks, Dunkin', McDonald's, Burger King, KFC, Taco Bell, Chipotle.
- **Tech products**: iPhone (with optional model number / Pro / Plus / Max), iPad, MacBook, AirPods, Apple Watch, Galaxy S / Note, Pixel, PlayStation, PS4 / PS5, Xbox (Series S/X, One), Nintendo Switch, Steam Deck.
- **Fashion / cosmetics**: Nike, Adidas, Gucci, Louis Vuitton, Chanel, Rolex, Old Spice, Axe body spray/wash.
- **Cars**: Tesla Model / Cybertruck, Ferrari, Lamborghini, Porsche, BMW M-series.

"My coffee shop's new pastry" passes. "Starbucks's new pastry" triggers.

### `ip` — recognizable characters and franchises

Match list excerpts: Mickey Mouse, Donald Duck, Elsa (from Frozen), Pikachu, Pokémon, Darth Vader, Baby Yoda, Grogu, Luke Skywalker, Spider-Man, Iron Man, Batman, Superman, Wonder Woman, Deadpool, Goku, Naruto, Sailor Moon, Ronald McDonald, Colonel Sanders.

Italian Brainrot characters (Tralalero Tralala, Tung Tung Sahur, Ballerina Cappuccina, ...) are *not* in the IP lexicon because the [`ralphy-assets`](https://github.com/alecs5am/ralphy-assets) companion repo ships canonical references for them — the agent pulls from the pool rather than generating from text. See [`/concepts/asset-catalog`](/concepts/asset-catalog).

## What does NOT trigger the gate

The classifier errs toward false negatives. Generic nouns and unrecognizable proper nouns pass without refs.

- "My coffee shop's new pastry" — generic.
- "A no-name workout app" — generic.
- "The founder narrates" — generic archetype (`founder` is in the archetype set).
- "Megan Park, the founder" — passes (`Megan Park` is not in the famous-names lexicon).
- "A black sports car" — generic (no brand named).
- "A red running shoe" — generic.
- "A talking eggplant" — generic, even though anthropomorphic.

If the user names a fringe celebrity not in the lexicon (e.g. a regional politician, a small-niche creator), the classifier will *miss* — that is by design. The full LLM-based gate during intake catches these; the offline classifier is the floor that the CLI runs without spending a token.

## The classifier in detail

`needsReference(input)` from [`cli/lib/eval/refs.ts`](https://github.com/alecs5am/ralphy/blob/main/cli/lib/eval/refs.ts):

```ts
import { needsReference, checkReferenceGate } from "./lib/eval/refs.js";

const result = needsReference({
  name: "Coffee with Elon",
  description: "A morning coffee shop visit with Elon Musk",
  character: { name: "Elon Musk" },
  scenes: [
    { keyframe_prompt_brief: "Elon Musk holding a latte" },
  ],
});

// result === {
//   required: true,
//   kind: "person",
//   reason: "Named person referenced: Elon Musk",
//   matches: ["Elon Musk"],
// }
```

The classifier flattens the input — `name`, `description`, `brand`, `character.name`, `character.role`, `character.description`, every scene's `keyframe_prompt_brief`, `motion_prompt`, `voEn`, `voRu`, `notes` — into one searchable blob, then scans each lexicon in priority order (`person` → `brand-product` → `ip`). On the first hit, it returns `required: true` with the bucket and the matched span.

`checkReferenceGate(scenario, attachedRefs)` adds the satisfaction check: if `required: true` and `attachedRefs.length > 0`, the result has `satisfied: true`. The CLI floor only checks *presence* — granular per-kind matching (does this person ref actually depict Elon Musk?) is left to the agent in chat.

## The CLI floor

`ralphy ref check <project-id>` runs the offline classifier against the project's scenario and prints the gate result:

```bash
ralphy ref check coffee-with-elon-001
# {
#   "required": true,
#   "kind": "person",
#   "reason": "Named person referenced: Elon Musk",
#   "matches": ["Elon Musk"],
#   "satisfied": false,
#   "attachedRefs": []
# }
```

`ralphy generate ...` calls `checkReferenceGate` before spending an API token. If `required: true && satisfied: false`, the generation refuses with a non-zero exit code and prints the same JSON. The agent reads the refusal and stops.

## The override

When the user explicitly accepts the risk, the override is `--no-ref-consent "<reason>"`:

```bash
ralphy generate image coffee-with-elon-001 \
  --scene scene-01 \
  --no-ref-consent "user is making a parody, has reviewed legal posture"
```

The override:

1. Bypasses the gate for *this single generate call only*. The next call (without the flag) refuses again.
2. Appends an entry to `.ralphy/workspaces/<ws>/projects/<id>/logs/user-prompts.jsonl` with `stage: "no-ref-consent"` and `text: "<reason>"`. The audit trail is permanent.
3. Does not affect the offline classifier or the manifest — the project's scenario still has the named entity in it.

The reason field is not optional. If the user says "just generate," the agent asks for an actual reason ("the user is making a parody," "this is internal testing for an Old Spice client we have a contract with," etc.) before passing the flag.

## Attaching a reference

Refs are folders under `.ralphy/refs/<id>/`. Each ref has a `ref.json` (the metadata), the source media, and any derived artifacts (frames, transcript, vision tags, blueprint).

```bash
# Create a ref from a URL (TikTok, YouTube, Reels — uses yt-dlp inside).
ralphy ref create musk-twitter-photo --source https://twitter.com/.../photo.jpg

# Or from a local file.
ralphy ref create musk-twitter-photo --file ~/Downloads/musk-photo.jpg

# Attach to a project.
ralphy project attach coffee-with-elon-001 --ref musk-twitter-photo
```

Once attached, the next `ralphy generate` call sees `attachedRefs.length > 0` and the gate is satisfied. The agent then passes the ref's master image via `--ref` on the actual i2v call, so the model has a target to lock to.

## Example flows

### Flow 1 — generic brief, no gate

```bash
# Brief: "Make a 15s ad for my coffee shop's new pastry."
ralphy new coffee-shop
ralphy generate image coffee-shop-001 --scene scene-01 \
  --prompt "Warm overhead shot of a flaky croissant on a marble counter, morning light."
# OK — no named entities, gate not required.
```

### Flow 2 — named human, gate fires

```bash
# Brief: "Make a 15s video of Elon Musk drinking my coffee."
ralphy new coffee-with-elon
ralphy generate image coffee-with-elon-001 --scene scene-01 \
  --prompt "Elon Musk holding a latte in a Brooklyn coffee shop"
# Refuses with:
# {
#   "error": "ref-required",
#   "kind": "person",
#   "reason": "Named person referenced: Elon Musk",
#   ...
# }

# Agent attaches a ref.
ralphy ref create musk-press-photo --file ~/refs/musk-press.jpg
ralphy project attach coffee-with-elon-001 --ref musk-press-photo

# Re-run.
ralphy generate image coffee-with-elon-001 --scene scene-01 \
  --prompt "Elon Musk holding a latte in a Brooklyn coffee shop" \
  --ref musk-press-photo
# OK — gate satisfied.
```

### Flow 3 — brand product, override with logged reason

```bash
# Brief: "Make a parody Old Spice ad for our internal team meeting."
ralphy new old-spice-parody
ralphy generate image old-spice-parody-001 --scene scene-01 \
  --prompt "Confident shirtless man holding an Old Spice bottle on a yacht" \
  --no-ref-consent "internal parody for offsite, not for publication, user has reviewed legal"
# OK — gate bypassed for this call only.
# Appended to user-prompts.jsonl:
#   { "timestamp": "...", "text": "internal parody for offsite, not for publication, user has reviewed legal",
#     "stage": "no-ref-consent" }
```

## Where the agent decides nuance

The offline classifier is the floor. The agent during intake can do more:

- **Detect fringe names** the lexicon misses (regional politicians, small creators) and ask for a ref.
- **Detect implied brands** (e.g. "the red can with the white wave" is probably Coca-Cola — the lexicon does not match this, but a good agent does).
- **Suggest using a companion-repo asset** instead of generating from text when the user names an Italian Brainrot character.

The CLI floor exists so that even a careless agent (or a direct user call without the agent) cannot accidentally burn money generating a famous face. The agent adds nuance on top.

## Related

- [`cli/lib/eval/refs.ts`](https://github.com/alecs5am/ralphy/blob/main/cli/lib/eval/refs.ts) — the classifier source
- [AGENTS.md hard invariant #3](https://github.com/alecs5am/ralphy/blob/main/AGENTS.md) — the canonical rule
- [Brands and personas](/concepts/brands-personas) — generic archetype personas pass the gate
- [Asset catalog](/concepts/asset-catalog) — for IP characters that ship as canonical refs
- [Generation log](/concepts/generation-log) — `--no-ref-consent` entries land in `user-prompts.jsonl`
