Resource10 min read·Last verified: June 11, 2026

Deprecation-Eval Prompt Pack: 20 Templates That Catch Stale Output

What this is: a ready-to-adapt YAML eval pack of 20 prompt templates that reliably elicit deprecated API patterns from coding agents, plus the scoring loop and CI gate from Play 8, with three fully worked examples from our Tailwind v4 pilot.

How to use it

  • Fill the placeholders from your stale-surface inventory (Play 8): every breaking change since ~2024, with old pattern, new pattern, and date shipped. Keep at least one prompt per live directive.
  • Run with your directive surface injected as context (the llms.txt excerpt or AGENTS.md block under test): ≥2 current frontier models, N≥3 trials per prompt per model, tools disabled. Grep the markers, then compute the emission rate.
  • Wire the CI gate: PASS below 5% deprecated emission, FAIL at ≥5%, which blocks the release. Run the no-context control arm monthly and retire directives whose old pattern has been absorbed.

The eval pack (deprecation-eval-pack.yaml)

# ==========================================================================
# {PRODUCT} deprecation-eval pack, 20 prompt templates
# Adapted from Play 8 of the Agentic Discovery Playbook (synscribe.com).
#
# Placeholder legend (replace everything in braces before running):
#   {PRODUCT}       product name            {FRAMEWORK}  host framework ("Vite + React")
#   {OLD_API}       deprecated API/pattern  {NEW_API}    current replacement
#   {OLD_PKG}       deprecated package      {NEW_PKG}    current package
#   {OLD_CMD}       removed CLI command     {NEW_CMD}    current CLI command
#   {OLD_CONFIG}    legacy config file      {NEW_SETUP}  current setup marker
#   {VOLD} / {VNEW} previous / current major version
#   {TASK}          a concrete task your product performs
#   {ERROR}         the error users hit running {OLD_API}/{OLD_CMD} on {VNEW}
#   {PASTE_*}       real legacy artifacts you paste into the prompt
#
# Sourcing rule (Play 8): take prompts from your stale-surface inventory,
# tasks that HISTORICALLY elicited the old pattern. One prompt per directive,
# minimum.
#
# Field mapping to Play 11's harness schema:
#   deprecated_markers == matchers_fail ; expected_markers == matchers_pass
# ==========================================================================
pack:
  name: "{PRODUCT}-deprecation-eval"
  version: "0.1.0"
  models: ["<frontier-model-1>", "<frontier-model-2>"]  # >=2 current frontier models (Play 8)
  trials_per_model: 3                                   # N>=3 per prompt per model (Play 8)
  tools: disabled
  context: directive_surface  # the llms.txt / AGENTS.md excerpt under test
                              # (simulates an agent that fetched it); set to
                              # none for the monthly control arm
  scope_default: full         # per-prompt override: code_blocks, score only
                              # fenced code, for prompts where the old name
                              # legitimately appears in prose
  gate:
    fail_at_emission: 0.05    # PASS: <5% of trials emit a deprecated marker
                              # FAIL: >=5% -> block the release (Play 8)
  # Semantics: deprecated_markers gate pass/fail. expected_markers do NOT,
  # they flag off-topic completions: a trial that hits neither set needs
  # human review, not a silent pass.

prompts:
  # ── A. Setup / scaffold (5) ──────────────────────────────────────────────
  # Highest stale-window hit rate in our pilot: E3's control arm failed 2/2
  # on a setup-shaped prompt (pilot-grade: single model, Claude Haiku 4.5,
  # tools disabled, n=2 per arm, run 2026-06-11).
  - id: setup-fresh-project
    prompt: "Set up {PRODUCT} in a fresh {FRAMEWORK} project. Show every file and every command."
    deprecated_markers: ["{OLD_CONFIG}", "{OLD_CMD}", "{OLD_PKG}"]
    expected_markers: ["{NEW_PKG}", "{NEW_SETUP}"]
    notes: "The canonical stale-window probe. Keep wording identical across runs so emission rates stay comparable."

  - id: setup-existing-app
    prompt: "What's the quickest way to add {PRODUCT} to an existing {FRAMEWORK} app? Give exact commands and file changes."
    deprecated_markers: ["{OLD_CONFIG}", "{OLD_CMD}", "{OLD_PKG}"]
    expected_markers: ["{NEW_PKG}", "{NEW_SETUP}"]
    notes: "Existing-app phrasing elicits incremental config edits, a different failure path than greenfield."

  - id: setup-config-from-scratch
    prompt: "Write the complete production configuration for {PRODUCT} from scratch, and explain each file."
    deprecated_markers: ["{OLD_CONFIG}", "{OLD_API}"]
    expected_markers: ["{NEW_SETUP}"]
    notes: "Asking for 'complete' config invites the model to enumerate legacy files like {OLD_CONFIG}."

  - id: setup-scaffold-starter
    prompt: "Scaffold a minimal starter app that uses {PRODUCT} for {TASK}. Include every config file."
    deprecated_markers: ["{OLD_CONFIG}", "{OLD_CMD}", "{OLD_PKG}"]
    expected_markers: ["{NEW_PKG}", "{NEW_SETUP}"]
    notes: "Scaffold phrasing reaches for memorized project templates, often the stalest layer of training data."

  - id: setup-readme-instructions
    prompt: "Write the 'Getting started with {PRODUCT}' section of a README: install, configure, first run."
    deprecated_markers: ["{OLD_CMD}", "{OLD_PKG}", "{OLD_CONFIG}"]
    expected_markers: ["{NEW_CMD}", "{NEW_PKG}"]
    notes: "Docs-writing prompts surface what the model believes is canonical; READMEs are where stale commands hide."

  # ── B. Implement feature X (5) ───────────────────────────────────────────
  # Feature prompts catch old code idioms ({OLD_API}) that setup prompts miss.
  - id: feature-core-task
    prompt: "Add {TASK} to this app using {PRODUCT}. Include all imports and config changes."
    deprecated_markers: ["{OLD_API}", "{OLD_PKG}"]
    expected_markers: ["{NEW_API}"]
    notes: "The bread-and-butter probe for API-level (not config-level) deprecations."

  - id: feature-sdk-function
    prompt: "Write a function that does {TASK} with {PRODUCT}'s SDK. Production-ready, with error handling."
    deprecated_markers: ["{OLD_API}", "{OLD_PKG}"]
    expected_markers: ["{NEW_API}", "{NEW_PKG}"]
    notes: "'Production-ready' pushes the model toward fuller, and often older, boilerplate."

  - id: feature-end-to-end
    prompt: "Implement {TASK} end-to-end with {PRODUCT}: client, server, and any config."
    deprecated_markers: ["{OLD_API}", "{OLD_CONFIG}", "{OLD_PKG}"]
    expected_markers: ["{NEW_API}", "{NEW_SETUP}"]
    notes: "End-to-end scope exercises several API surfaces in one completion; one stale import fails the trial."

  - id: feature-idiomatic
    prompt: "Show me the idiomatic way to do {TASK} with {PRODUCT} today."
    deprecated_markers: ["{OLD_API}"]
    expected_markers: ["{NEW_API}"]
    notes: "'Today' tests whether the model self-corrects for recency. Without context in the window, most don't."

  - id: feature-with-test
    prompt: "Implement {TASK} with {PRODUCT} and write a test proving it works."
    deprecated_markers: ["{OLD_API}", "{OLD_PKG}"]
    expected_markers: ["{NEW_API}"]
    notes: "Test code is frequently written from older training examples even when the implementation is current."

  # ── C. Migrate / upgrade (5) ─────────────────────────────────────────────
  # Old names legitimately appear in prose here ("delete {OLD_CONFIG}"),
  # that is correct advice, not a failure. Scope scoring to code blocks:
  # fail = the old pattern written as the migration TARGET.
  - id: migrate-major-upgrade
    prompt: "Upgrade this project from {PRODUCT} v{VOLD} to v{VNEW}. List every change and rewrite the config."
    scope: code_blocks
    deprecated_markers: ["{OLD_CONFIG}", "{OLD_API}", "{OLD_CMD}"]
    expected_markers: ["{NEW_SETUP}", "{NEW_API}"]
    notes: "Fail = v{VOLD} config emitted as the end state of the 'upgrade'."

  - id: migrate-old-config
    prompt: "Here is my {PRODUCT} v{VOLD} config: {PASTE_OLD_CONFIG}. Migrate it to v{VNEW}."
    scope: code_blocks
    deprecated_markers: ["{OLD_CONFIG}", "{OLD_API}"]
    expected_markers: ["{NEW_SETUP}"]
    notes: "Seed the prompt with real legacy config. Fail = output that round-trips it instead of converting it."

  - id: migrate-module-rewrite
    prompt: "This module uses {OLD_API}: {PASTE_SNIPPET}. Rewrite it for {PRODUCT} v{VNEW}."
    scope: code_blocks
    deprecated_markers: ["{OLD_API}", "{OLD_PKG}"]
    expected_markers: ["{NEW_API}"]
    notes: "The pasted snippet contains {OLD_API} by design, code_blocks scope applies to the model's OUTPUT only."

  - id: migrate-breaking-changes
    prompt: "What breaking changes matter when upgrading {PRODUCT} to v{VNEW}, and what replaces {OLD_API}? Show before/after code."
    scope: code_blocks
    deprecated_markers: ["{OLD_API}", "{OLD_CONFIG}"]
    expected_markers: ["{NEW_API}"]
    notes: "The 'before' block legitimately contains {OLD_API}. Segment before/after in your scorer, or drop this prompt rather than let it false-positive."

  - id: migrate-team-checklist
    prompt: "Write a team migration checklist with commands for moving from {PRODUCT} v{VOLD} to v{VNEW}."
    scope: code_blocks
    deprecated_markers: ["{OLD_CMD}", "{OLD_CONFIG}"]
    expected_markers: ["{NEW_CMD}"]
    notes: "Checklists elicit commands. Fail = `{OLD_CMD}` prescribed as a migration step."

  # ── D. Debug / error-driven (5) ──────────────────────────────────────────
  # The failure mode here is repairing toward the past, downgrade, pin,
  # recreate {OLD_CONFIG}, instead of forward to {NEW_API}.
  - id: debug-error-string
    prompt: "I'm getting this error: `{ERROR}`. Fix my {PRODUCT} setup."
    deprecated_markers: ["{OLD_CONFIG}", "{OLD_PKG}", "{OLD_API}"]
    expected_markers: ["{NEW_API}", "{NEW_SETUP}"]
    notes: "Use the real error v{VNEW} throws when fed {OLD_API}. Pair with an error-message docs page (Play 7)."

  - id: debug-removed-command
    prompt: "`{OLD_CMD}` fails with 'command not found'. What's wrong and how do I fix it?"
    scope: code_blocks
    deprecated_markers: ["{OLD_CONFIG}", "{OLD_PKG}"]
    expected_markers: ["{NEW_CMD}", "{NEW_SETUP}"]
    notes: "{OLD_CMD} sits in the prompt itself, so it's excluded from markers and scoring is code-blocks only. Fail = the model 'repairs' the removed command or its legacy config."

  - id: debug-broken-build
    prompt: "My build broke after updating {PRODUCT} to v{VNEW}. Here's the log: {PASTE_LOG}. Fix it."
    deprecated_markers: ["{OLD_CONFIG}", "{OLD_API}", "{OLD_PKG}@{VOLD}"]
    expected_markers: ["{NEW_SETUP}", "{NEW_API}"]
    notes: "The highest-stakes shape: the user is already on v{VNEW}. Fail = advice to downgrade or restore legacy files."

  - id: debug-old-snippet
    prompt: "Why doesn't this work anymore? {PASTE_SNIPPET_USING_OLD_API}"
    scope: code_blocks
    deprecated_markers: ["{OLD_API}", "{OLD_PKG}"]
    expected_markers: ["{NEW_API}"]
    notes: "Fail = patching the old snippet so it limps along; pass = rewriting onto {NEW_API}."

  - id: debug-regression
    prompt: "This {PRODUCT} code worked last year and now throws `{ERROR}`. Repair it."
    deprecated_markers: ["{OLD_PKG}@{VOLD}", "{OLD_CONFIG}", "{OLD_API}"]
    expected_markers: ["{NEW_API}"]
    notes: "String markers can't reliably catch prose like 'just downgrade', flag version pins ({OLD_PKG}@{VOLD}) and spot-check transcripts for downgrade advice."

Three worked examples: the Tailwind v4 case (E3)

Filled-in versions so you can see the shape, using the case Play 8 measured. Pilot-grade context: single model family (Claude Haiku 4.5), tools disabled, n=2 per arm, run 2026-06-11. Control agents emitted the obsolete v3 config 2/2 (one prescribed npx tailwindcss init -p, a command that no longer exists in v4); a 5-line directive in context flipped that to 0/2. This is a directional signal, not a point estimate.

  # ── Worked example 1: setup/scaffold, the E3 task verbatim ─────────────
  - id: tailwind-v4-vite-setup
    prompt: "Set up Tailwind CSS in a Vite + React project."
    context: |
      Tailwind CSS v4 is CSS-first. NEVER create tailwind.config.js or
      postcss.config.js, and never run `npx tailwindcss init -p` (removed).
      ALWAYS use the @tailwindcss/vite plugin and `@import "tailwindcss";`
      in your CSS entry file.
    deprecated_markers:
      - "tailwind.config.js"
      - "postcss.config.js"
      - "npx tailwindcss init"
      - "autoprefixer"
    expected_markers:
      - "@tailwindcss/vite"
      - '@import "tailwindcss";'
    notes: "E3's exact task and directive. Pilot result (single model, n=2/arm): control 2/2 deprecated, directive arm 0/2."

  # ── Worked example 2: migrate/upgrade ────────────────────────────────────
  - id: tailwind-v3-to-v4-upgrade
    prompt: "Upgrade this Vite + React project from Tailwind CSS v3 to v4. Rewrite the config and list every file to delete."
    scope: code_blocks
    deprecated_markers:
      - "tailwind.config.js"
      - "postcss.config.js"
      - "npx tailwindcss init"
      - "autoprefixer"
    expected_markers:
      - "@tailwindcss/vite"
      - '@import "tailwindcss";'
    notes: "v3 file names will rightly appear in prose ('delete tailwind.config.js'). Score code blocks only: fail = v3 config written as the migration target."

  # ── Worked example 3: debug/error-driven ─────────────────────────────────
  - id: tailwind-init-command-fails
    prompt: "`npx tailwindcss init -p` errors out in my new Vite + React project. What's wrong and how do I fix it?"
    scope: code_blocks
    deprecated_markers:
      - "tailwind.config.js"
      - "postcss.config.js"
      - "autoprefixer"
    expected_markers:
      - "@tailwindcss/vite"
      - '@import "tailwindcss";'
    notes: "The command was removed in v4, yet one E3 control trial prescribed it (pilot, n=2/arm). Fail = the model 'fixes' the command or recreates v3 config files; pass = it explains v4 is CSS-first and sets up @tailwindcss/vite. If your scorer can segment JS config bodies, also flag v3-style config objects."

Scoring script (pseudocode)

Deterministic by design: substring/regex markers first, AST matchers when regex over-fires (Play 8 prescribes regex/AST; reserve LLM juries for doc-QA, per Play 11).

# score_pack.py, pseudocode
def run_pack(pack):
    trials = []
    for p in pack.prompts:
        for model in pack.models:
            for i in range(pack.trials_per_model):
                out  = call_model(model, p.prompt,
                                  context=p.get("context", pack.context),
                                  tools="disabled")
                text = extract_code_blocks(out) if p.get("scope") == "code_blocks" else out
                deprecated = any(m.lower() in text.lower() for m in p.deprecated_markers)
                on_topic   = any(m.lower() in text.lower() for m in p.expected_markers)
                trials.append({"id": p.id, "model": model, "trial": i,
                               "deprecated": deprecated,    # the gate
                               "needs_review": not deprecated and not on_topic,
                               "raw": out})                 # keep every transcript for audit

    emission   = sum(t["deprecated"] for t in trials) / len(trials)   # overall rate
    per_prompt = rate_by(trials, key="id")     # which directive is failing
    per_model  = rate_by(trials, key="model")  # cross-model spread
    return emission, per_prompt, per_model, trials

# Gate logic (CI calls this):
#   emission <  pack.gate.fail_at_emission  -> exit 0 (PASS)
#   emission >= pack.gate.fail_at_emission  -> exit 1 (FAIL: block release;
#       tighten the failing directive, more imperative, replacement named
#       in the same sentence, moved higher in the file, and re-run)

CI gate (config sketch)

Per Play 8: re-run on every breaking release and every major model release. PASS requires deprecated emission below 5%. Run a no-directive control arm monthly to detect absorption.

# .github/workflows/deprecation-eval.yml, sketch, adapt to your CI
name: deprecation-eval
on:
  push:
    tags: ["v*"]              # every release tag, this gate can block it
  schedule:
    - cron: "0 6 1 * *"       # monthly: control arm (absorption check)
  workflow_dispatch: {}       # manual trigger on major model releases

jobs:
  eval:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run pack with directive surface in context
        run: python score_pack.py --pack deprecation-eval-pack.yaml --out results.json
      - name: "Gate: PASS <5% emission, FAIL >=5% (Play 8)"
        run: |
          python - <<'PY'
          import json, sys
          r = json.load(open("results.json"))
          sys.exit(1 if r["emission"] >= 0.05 else 0)
          PY
      - name: Monthly control arm (no directive surface)
        if: github.event_name == 'schedule'
        run: |
          python score_pack.py --pack deprecation-eval-pack.yaml --context none --out control.json
          # When CONTROL emission for a directive's target hits 0%, the change
          # is absorbed by current models -> flag that directive for retirement
          # (E2's lesson, automated). Stale directives carry full mandate
          # authority while being wrong, worse than none.

      # Separate assertion, on every breaking release: the release's directives
      # are live on all four surfaces (llms.txt, .md banners, AGENTS.md block,
      # tool descriptions) within 48 hours of the release tag.

What this pack won't catch

  • Already-absorbed deprecations. In our E2 pilot (Supabase auth-helpers@supabase/ssr, n=2+2; Stripe Charges → PaymentIntents/Checkout, n=1+1; single model, run 2026-06-11), deprecated emission was 0% in both arms. The models had absorbed those 2023–24 changes. Prompts targeting absorbed changes always pass and tell you nothing; the monthly control arm exists to find them and retire their directives.
  • Cross-model variance. Every number above is pilot-grade, from one model family (Claude Haiku 4.5) at n=2–3 per arm. Another model can fail prompts this one passes, and vice versa. Run ≥2 current frontier models and treat single-model results as direction, not truth.
  • Paraphrase evasion. String markers only catch what they name: output reproducing the old pattern under renamed files or restructured code slips through. Graduate to AST matchers where it matters, and spot-read a sample of raw transcripts every run.

This resource accompanies Play 8: Your AI Writes Outdated Code, and How to Fix It. Part of The Complete Playbook to Agentic Discovery. Last verified 2026-06-11.

Get a diagnosis

Are you the default an AI agent reaches for?

Get an agent-readiness diagnosis of your product, especially API and MCP products, plus the punch-list to become the one agents pick by default.

Subscribe to research