Spec an AI app's inputs, outputs and model stages before any code

Turn a one-paragraph AI app idea into a spec of every input, output, model call, code check, cost and fallback, so the model's job stays narrow and testable.

Prompt intermediate Tested Opus 5.5 · Nov 2026May be dated

The prompt

Paste this into a Claude chat before you write any code. The answer runs to about 400 lines (around 11,000 output tokens), so use a strong model. Save it as docs/ai-spec.md in the new repo and build to it.

Fill in the blanks4 placeholders · empty ones stay as {{…}}

Example

Input:

  • app_idea: "A weekly dinner planner. The user enters household size, dietary rules (for example vegetarian, no nuts), what is already in the pantry, and how many minutes they can cook each weeknight. The app returns seven dinners with short recipes and one shopping list for the week, grouped by supermarket aisle. The user can lock dinners they like and regenerate the rest."
  • users: Home cooks on phones, cooking for 1 to 6 people. No sign-in needed for the first plan.
  • constraints: One serverless backend that calls the Claude API; key-value storage only, no database; metric units; English only; free to use, with a daily cap per visitor.
  • model_budget: At most 4 model calls per weekly plan and 1 per regenerate; a fast model wherever a call is small.

Output (trimmed to two rows of the section 4 model-stage table, one row of its call settings, the lines code sends for one unit, two rows of its fact table, and two rows of section 5, Checks in code):

Stage Tier (model) Effort Tools max_tokens Returns (structured-output JSON)
S4 Menu strong (claude-opus-5-5) medium none 3,000 {"dinners":[{"slot":1,"title":"","protein":"","base":"","key":[""]}]} where key = 2–6 main ingredient names
S6 Recipes fast (claude-haiku-5-5) low none 6,000 {"recipes":[{"slot":1,"ingredients":[{"name":"","qty":0,"unit":"","aisle":""}],"steps":[{"text":"","min":0,"hands":"","overlap":false}]}]}

…

Setting Value
Never asked of the model Ids, totals, scaled amounts, dates, weekdays, servings, summaries, verdicts

Lines code sends. These are computed per unit, short, and include what is already used.

household: 4 people · banned: peanut, almond, cashew, pesto, satay, … · avoid: <user_data>mushroom</user_data>
pantry (prefer, names only): <user_data>rice; tinned tomatoes; onion</user_data>
protein caps: legumes ≤3 · eggs ≤3 · dairy ≤3 · other_plant ≤3
used: legumes ×2 · rice ×2 · titles: Chickpea curry; Lentil ragù
slot 3 · Wed 2026-11-11 · weeknight · ≤30 min total for 4 people · concept: Spinach dal · legumes · rice
slot 3 fix: title contains "satay" (banned by No nuts); change the dish, keep the protein and base
Fact Model returns Code verifies against Named fallback
Aisle An aisle enum, or "" if unsure (never a guess) Aisle enum; the table's aisle wins when the ingredient is in the table TABLE_AISLE, else other
Amount per person A number; null only with to_taste Table min–max per person (matched); generic range (unmatched): g 1–400, ml 1–500, pc 0.05–3 Matched: table typical, shown "estimated". Unmatched: null, "amount not given".

…

Check Level Rule On failure
C04 Amount math P0 qtyTotal = qtyPerPerson × servings under the §3 rounding rule. Code: recompute
C07 Time math P0 totalMinutes = sum of non-overlapping steps; activeMinutes = sum of active steps. Code: recompute

The model returns one person's amounts and each step's minutes; code computes the totals, times and shopping list, and C04, C05 and C07 recompute them on every run. To fit the 4-call budget, this run spends one strong call on dish ideas, two fast calls on recipe batches and at most one on repair, and makes the second repair round code-only.

Why it works

  • "The model proposes; code derives." A model that writes both the lines and the total will sooner or later disagree with itself. Ask it for unit values only. Code computes every total, date and verdict, writes every sentence that quotes one, and looks up facts a library knows, so those numbers are right by construction.
  • Small, checkable stages. Each model call gets one job, one small JSON shape and constraints code computed for it, so a scripted mock model can test it. Structured-output modes can reject a large schema as too complex and don't enforce every range, so code still coerces and checks.
  • Repairs that touch only what failed. P0 to P3 checks run on every output. Re-prompting only the failing unit, with its current version and "change nothing else", fixes it without breaking what passed and keeps cost bounded. Origin marks stop a check or a re-run from "fixing" what the user wrote.
  • Open choices written down. One types file gives the build one vocabulary for every shape. Assumed rows turn each choice the idea left open into a visible line you can overrule, instead of a silent default such as the builder's own currency or units.

Variations

  • Retrofit an existing app. In Claude Code on the repo: "Read the code and write docs/ai-spec.md in this shape for what the app does today. Mark every place where the model produces a value code could compute or look up."
  • Keep it current. Add one line to the project's CLAUDE.md: "Change docs/ai-spec.md in the same commit as any change to an input, output, route or stage."
  • Checks first. Follow up with "Turn section 5 into failing tests against the mock model from section 10", then build until they pass.
  • Build the pipeline. Use Model proposes, code disposes to write the stage prompts for section 4 and wire in the checks from section 5.
  • Generalised from a trip-planner app.