<!--
Multi-Stage AI Business Constraints
Part of the Bles Software agent skills repository:
https://www.bles-software.com/claude-skills

Install for Claude Code:
  mkdir -p ~/.claude/skills/multi-stage-ai-business-constraints
  mv multi-stage-ai-business-constraints.md ~/.claude/skills/multi-stage-ai-business-constraints/SKILL.md

Install for Codex:
  mkdir -p ~/.codex/skills/multi-stage-ai-business-constraints
  mv multi-stage-ai-business-constraints.md ~/.codex/skills/multi-stage-ai-business-constraints/SKILL.md

For Gemini CLI and other agents, drop the body of this file into the
project instruction file the agent already reads.
-->

---
name: multi-stage-ai-business-constraints
description: How to enforce business-owner constraints (dietary law, brand voice, factual data like phone/address) across a multi-stage AI generation pipeline (LLM planner → media generator → branded composer). Splits responsibility cleanly — LLM is creative, system is authoritative for facts and safety.
---

# Multi-Stage AI Business-Constraint Enforcement

## When to use this skill

You're building an AI pipeline where a creative LLM (Claude/GPT) generates content (stories, dialogue, prompts) that gets handed downstream to media-generation models (Veo, image gen) and finally a UI compositor (Remotion, Figma export, etc).

The business owner has stored settings that the output MUST respect:
- Dietary law / regulatory constraints ("never show meat with cheese")
- Factual data (real phone, real address, real business hours)
- Brand voice / tone
- Audience boundaries

Without this skill, the LLM **invents** plausible-but-wrong things ("Big Burger in Tel Aviv" when the real address is Yehud-Monoson) or **violates business rules** ("a juicy cheeseburger with melted cheddar" for a kosher restaurant).

## The four-layer model

```
Layer 1: USER SETTINGS (DB)            ← authoritative facts + constraints
            │
            ▼
Layer 2: ORCHESTRATOR (your service)   ← reads settings, builds business context
            │
            ▼
Layer 3: LLM PLANNER (Claude/GPT)      ← creative-only, given facts as HARD CONSTRAINTS
            │
            ▼
Layer 4: MEDIA RENDERER + COMPOSITOR   ← system overwrites factual fields, renders branded card
```

**The split**: LLM owns *creative* output (dialogue, scene descriptions, taglines). System owns *factual* output (phone digits, address spelling, business name). Never let the LLM be authoritative for facts.

## The 5 patterns

### Pattern 1: Settings beat caller-supplied context

API callers can ENRICH but never OVERRIDE business facts:

```javascript
// WRONG: caller can override real address
location: businessContext?.address || settings?.address || '',

// RIGHT: settings always win
location: settings?.address || businessContext?.address || '',
```

This stops your own internal API from accidentally injecting wrong data when you forget to query settings first.

### Pattern 2: Hard-constraint injection into the system prompt

Don't suggest. Don't include "as guidance". Make it explicit:

```
=== HARD CONSTRAINTS (from business owner — VIOLATING THESE INVALIDATES THE PLAN) ===
{settings.systemContext}

Apply these constraints to EVERY image_prompt and EVERY video_prompt.
If the constraint says "no X with Y", do not put X with Y in any frame, ever.
If the constraint specifies kashrut or dietary law, every depicted food must comply.
Re-read the constraints before writing each scene.
```

The aggressive language ("VIOLATING THESE INVALIDATES THE PLAN", "ever", "Re-read") raises compliance significantly versus passive "please consider".

### Pattern 3: Authoritative-bio block to anchor location/menu/hours

When the planner needs business facts for the script (city name, hours, menu items), inject the verified bio as a separate authoritative block:

```
=== AUTHORITATIVE BUSINESS INFO (use these facts; do NOT invent location, hours, menu, etc.) ===
{settings.posterBio}
```

This stops the model from saying "Big Burger in Tel Aviv" when the bio clearly says Yehud-Monoson. The "do NOT invent" framing matters.

### Pattern 4: Sensitive data goes through the compositor, not the character

Phone numbers, URLs, and prices spoken by an AI character sound robotic and break immersion ("call zero-three-six-three-two-one-eight-one-eight"). Solution:

1. **Tell the LLM**: "Phone is shown ON-SCREEN in the branded outro card automatically. The TTS character must NEVER speak phone digits aloud."
2. **Build a Remotion (or equivalent) outro card** with the phone displayed as visual text.
3. **System overrides any planner-provided outro fields** with verified data.

Code shape:
```javascript
function buildOutroBlock(plannerOutro, business) {
  return {
    // Creative — from LLM
    tagline: plannerOutro.tagline,
    ctaText: plannerOutro.ctaText,
    // Factual — from settings (LLM cannot override these)
    businessName: business.name,
    phoneNumber: business.phone,
    address: business.address,
  };
}
```

### Pattern 5: Schema enforces the contract

If your LLM returns JSON, put `outro` in the required schema with ONLY the creative fields the LLM should produce. Omit factual fields entirely from the schema — the LLM literally can't add them, and your normalizer adds them post-validation.

```javascript
const outroSchema = {
  type: 'object',
  properties: {
    tagline: { type: 'string' },   // creative
    ctaText: { type: 'string' },   // creative
    // NO businessName, NO phoneNumber, NO address — system fills these
  },
  required: ['tagline', 'ctaText'],
};
```

## Real example — kosher burger restaurant reel

**Settings stored in DB**:
```json
{
  "businessDisplayName": "ביג בורגר",
  "businessPhoneNumber": "03-632-1818",
  "businessAddress": "צבי ישי 1",
  "systemContext": "קשרות: ביג בורגר הוא מסעדה כשרה. אסור בהחלט להציג גבינה עם בשר.",
  "posterBio": "ביג בורגר היא מסעדת המבורגרים ותיקה וכשרה הממוקמת באזור התעשייה של יהוד-מונוסון..."
}
```

**Without this skill** (first reel): planner placed business in "Tel Aviv" (invented), depicted melted cheddar on beef patties (kashrut violation), had character recite "אפס שלוש שש שלוש שתיים..." digits robotically.

**With this skill** (next reel):
- City automatically detected from posterBio regex → outro shows "צבי ישי 1, יהוד-מונוסון"
- systemContext injected as HARD CONSTRAINT → no cheese on beef in any image_prompt
- Planner instructed phone is displayed in outro card → character says "התקשרו אלינו" or "בואו לטעום" instead of digits
- Remotion BusinessOutro component automatically appended after final scene with phone number rendered as 130pt centered text

## Reference implementation

`/opt/autoposter/src/reelsService.js` — `buildBusinessContextFromSettings()` (Pattern 1)
`/opt/autoposter/src/reelsPlanner.js` — system prompt with HARD CONSTRAINTS block (Patterns 2-3) + `buildOutroBlock()` (Pattern 4) + `outroSchema` (Pattern 5)
`/opt/autoposter/reels-engine/remotion/outro-card/src/BusinessOutro.tsx` — branded outro composition
`/opt/autoposter/reels-engine/generate_from_plan.py` — `render_business_outro()` + auto-concat

## Quick checklist before shipping a multi-stage AI generation feature

- [ ] DB has a `systemContext` (or equivalent) field where the business owner can write hard rules
- [ ] Orchestrator reads from DB **first**, treats API context as enrichment only
- [ ] Hard constraints are injected into the LLM system prompt with aggressive language ("VIOLATES THE PLAN", "EVERY", "ever")
- [ ] Authoritative bio injected as separate block with "do NOT invent" framing
- [ ] Sensitive/factual fields (phone, address, business name) are NOT in the LLM's output schema
- [ ] System normalizer **overwrites** any planner-supplied factual fields with DB values
- [ ] Branded compositor (Remotion / similar) renders the verified data as on-screen UI
- [ ] Character dialogue is told explicitly NOT to speak phone digits / URLs / prices
- [ ] City detection from bio is heuristic + tolerant (regex fallback in case the structured field is just a street)

## Why this works

- **Trust separation**: LLMs hallucinate facts; humans hallucinate creativity. Match each layer to what it's good at.
- **Aggressive constraint language** raises compliance from ~70% to ~95% in tested generations.
- **Schema-level enforcement** is the strongest — if the LLM literally can't include `phoneNumber` in its output, it can't accidentally rewrite it.
- **Visual rendering of facts** (phone in big text on screen) is more memorable AND more accurate than spoken digits.

## Companion: when this skill applies vs when it doesn't

**Applies**: Any pipeline where an LLM produces content that ends up customer-facing AND the business has factual or regulatory constraints (kashrut, halal, gluten-free, regulated industries like financial advice, medical, legal).

**Doesn't apply**: Pure creative-writing tools where there's no factual ground truth (poetry, fiction). Or single-call APIs where the prompt is fully composed by the user (no DB layer).
