Kill a bad product idea before you spend a week building it

An agent runs six adversarial critique passes in two minutes and surfaces objections a solo founder never asks themselves, where a human review panel takes three days to assemble and half the panel will be polite.

You have a PRD you're excited about. You start building. Four days in you realize it solves the wrong problem.

Opening thesis

You will build a Python script that kills a bad product idea before you spend a week building it. The script feeds a PRD through six adversarial critique passes using the Anthropic API, each pass wearing a different hostile hat. An agent runs six adversarial critique passes in two minutes and surfaces objections a solo founder never asks themselves, where a human review panel takes three days to assemble and half the panel will be polite. The output is a ranked list of the three objections most likely to kill the product idea before any code gets written.

Before

You finished your PRD on Sunday night. It felt sharp. Monday morning you started building: database schema, auth flow, the first API route. By Wednesday you had 40 files. Thursday you showed it to a friend who builds in the same space. She said one sentence: "Your user doesn't have that problem." You sat with it. She was right. Four days of code went into the trash. The worst part is you knew the question existed. You just never asked it, because you were the author and the reviewer at the same time. Nobody plays devil's advocate on their own work. Not honestly. Not at 1 a.m. when the idea feels alive. You needed six skeptics in a room. You had zero.

Architecture

The system is a single Python script that reads a PRD from a local file, sends it through six sequential API calls to Claude, and writes a final verdict file. Each call uses a different adversarial persona encoded in the system prompt. A final synthesis call ranks the objections by severity.

Adversarial PRD Review PipelineSix critique passes feed into one synthesis pass, producing a ranked kill list. Nodes: prd.md (The raw PRD file on disk); critique_runner.py (Python script that orchestrates all API calls); Persona 1: Customer Skeptic (Challenges whether the user actually has the stated problem); Persona 2: Market Cynic (Challenges whether the market is real and reachable); Persona 3: Technical Saboteur (Finds architectural risks the author glossed over); Persona 4: Unit Economics Auditor (Asks if the business math works at scale); Persona 5: Competitor Analyst (Identifies existing solutions the author ignored); Persona 6: Scope Creep Detector (Finds hidden complexity that doubles the timeline); Claude API (Anthropic) (The model endpoint processing each persona call); synthesis call (One final call that ranks all objections); verdict.md (The output file with ranked objections).prd.mdThe raw PRD file on diskcritique_runner.pyPython script that orchestrates all API callsPersona 1: Customer SkepticChallenges whether the user actually has the stated problemPersona 2: Market CynicChallenges whether the market is real and reachablePersona 3: Technical SaboteurFinds architectural risks the author glossed overPersona 4: Unit Economics AuditorAsks if the business math works at scalePersona 5: Competitor AnalystIdentifies existing solutions the author ignoredPersona 6: Scope Creep DetectorFinds hidden complexity that doubles the timelineClaude API (Anthropic)The model endpoint processing each persona callsynthesis callOne final call that ranks all objectionsverdict.mdThe output file with ranked objections
  • prd.md is read by critique_runner.py
  • critique_runner.py sends prd.md content to Claude API six times, once per persona
  • Each persona response is collected into a list
  • critique_runner.py sends all six responses to Claude API as a synthesis call
  • Synthesis response is written to verdict.md

Step-by-step implementation

Step 1: Set up the project and install the Anthropic SDK

Create a directory and install the one dependency. The Anthropic Python SDK wraps the Messages API. Get your API key from https://console.anthropic.com/settings/keys and export it.

mkdir adversarial-review && cd adversarial-review
python3 -m venv venv
source venv/bin/activate
pip install anthropic
export ANTHROPIC_API_KEY="sk-ant-...your-key-here"

Step 2: Create a sample PRD to test against

You need a real PRD to feed the system. This sample describes a plausible but flawed idea: an AI meal planner for college students. Save it as prd.md. Replace it with your own PRD when you run this for real.

cat > prd.md << 'EOF'
# Product Requirements Document: MealMind

**Problem.** College students eat poorly because they lack time and knowledge to plan meals around a tight budget.

**Solution.** A generative mobile app that produces weekly meal plans optimized for a student's budget, dietary restrictions, and dorm kitchen constraints.

**Target user.** US college students aged 18 to 22 living in dorms or off-campus apartments.

**Core features.**
1. Budget input: user sets weekly grocery budget (default $40).
2. Generated meal plan: 7 days, 3 meals per day.
3. Grocery list export: one-tap list synced to Instacart or Walmart.
4. Dietary filters: vegan, gluten-free, halal, nut-free.
5. Leftover tracking: mark unused ingredients, get next-day recipes.

**Revenue model.** Freemium. Free tier: 2 meal plans per month. Pro tier: $4.99/month for unlimited plans and grocery sync.

**Timeline.** MVP in 6 weeks. Solo founder. React Native frontend, Python backend, OpenAI API for generation.

**Success metric.** 1,000 paying subscribers within 6 months of launch.
EOF

Step 3: Define the six adversarial personas

Each persona is a system prompt that tells Claude to adopt a specific hostile stance. The key design choice: each persona must end with "List your top 3 objections, each with a severity rating of LOW, MEDIUM, or HIGH." This forces structured output.

# personas.py

PERSONAS = [
    {
        "name": "Customer Skeptic",
        "system": (
            "You are a ruthless customer researcher. Your job is to find evidence that the target user "
            "does not actually have the stated problem, or that they have it but will never pay to solve it. "
            "Assume nothing. Challenge every claim about user behavior. "
            "List your top 3 objections, each with a severity rating of LOW, MEDIUM, or HIGH."
        ),
    },
    {
        "name": "Market Cynic",
        "system": (
            "You are a market analyst who has seen 500 failed startups. Your job is to find reasons this "
            "market is fake, too small, or unreachable. Challenge the TAM, the distribution channel, and "
            "the timing. Be specific. "
            "List your top 3 objections, each with a severity rating of LOW, MEDIUM, or HIGH."
        ),
    },
    {
        "name": "Technical Saboteur",
        "system": (
            "You are a senior engineer who reviews architecture documents for hidden risk. Your job is to "
            "find the technical assumptions that will blow up during implementation. Focus on integration "
            "complexity, data dependencies, and scaling bottlenecks. "
            "List your top 3 objections, each with a severity rating of LOW, MEDIUM, or HIGH."
        ),
    },
    {
        "name": "Unit Economics Auditor",
        "system": (
            "You are a finance person who kills products that cannot make money. Your job is to calculate "
            "whether the revenue model covers the cost of acquisition, infrastructure, and API calls. "
            "Use rough estimates. Show your math. "
            "List your top 3 objections, each with a severity rating of LOW, MEDIUM, or HIGH."
        ),
    },
    {
        "name": "Competitor Analyst",
        "system": (
            "You are a competitive intelligence analyst. Your job is to name existing products, features, "
            "or free alternatives that already solve this problem. If the PRD does not mention competitors, "
            "that is itself a red flag. Be specific with product names and URLs where possible. "
            "List your top 3 objections, each with a severity rating of LOW, MEDIUM, or HIGH."
        ),
    },
    {
        "name": "Scope Creep Detector",
        "system": (
            "You are a project manager who has shipped 50 products. Your job is to find hidden complexity "
            "in the feature list that will double the stated timeline. Look for features that sound like "
            "one task but are actually five. Challenge the MVP definition. "
            "List your top 3 objections, each with a severity rating of LOW, MEDIUM, or HIGH."
        ),
    },
]

Step 4: Build the critique runner

This script reads the PRD, loops through all six personas, collects responses, and stores them for synthesis. Each call uses claude-sonnet-4-20250514 with a max of 1024 tokens. That keeps cost under $0.15 for the full run.

# critique_runner.py

import os
import json
import anthropic
from personas import PERSONAS

client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY from env

def load_prd(path: str) -> str:
    with open(path, "r") as f:
        return f.read()

def run_critique(prd_text: str, persona: dict) -> dict:
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=1024,
        system=persona["system"],
        messages=[
            {
                "role": "user",
                "content": f"Here is a Product Requirements Document. Critique it.\n\n{prd_text}",
            }
        ],
    )
    text = response.content[0].text
    return {"persona": persona["name"], "critique": text}

def run_all_critiques(prd_path: str) -> list:
    prd_text = load_prd(prd_path)
    results = []
    for persona in PERSONAS:
        print(f"Running critique: {persona['name']}...")
        result = run_critique(prd_text, persona)
        results.append(result)
    return results

if __name__ == "__main__":
    critiques = run_all_critiques("prd.md")
    with open("critiques.json", "w") as f:
        json.dump(critiques, f, indent=2)
    print(f"Saved {len(critiques)} critiques to critiques.json")

Step 5: Run the six critiques

Execute the runner. On a typical PRD this takes 30 to 90 seconds depending on response length. You will see each persona name print as it completes.

python critique_runner.py

Step 6: Build the synthesis step

The synthesis call takes all six critiques and asks Claude to rank every objection by severity, then pick the top three. This is the step that turns raw noise into a decision.

# synthesize.py

import json
import anthropic

client = anthropic.Anthropic()

def load_critiques(path: str) -> list:
    with open(path, "r") as f:
        return json.load(f)

def synthesize(critiques: list) -> str:
    combined = ""
    for c in critiques:
        combined += f"### {c['persona']}\n{c['critique']}\n\n"

    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=2048,
        system=(
            "You are a product decision advisor. You have received six adversarial critiques of a PRD. "
            "Your job: read all 18 objections, deduplicate them, rank them by severity, and output "
            "the top 3 objections that are most likely to kill the product. For each objection, state "
            "the persona that raised it, the objection in one sentence, the severity (HIGH/MEDIUM/LOW), "
            "and one concrete action the founder should take before writing any code. "
            "Format the output as markdown with numbered items."
        ),
        messages=[
            {
                "role": "user",
                "content": f"Here are the six adversarial critiques:\n\n{combined}",
            }
        ],
    )
    return response.content[0].text

if __name__ == "__main__":
    critiques = load_critiques("critiques.json")
    verdict = synthesize(critiques)
    with open("verdict.md", "w") as f:
        f.write("# Adversarial Review Verdict\n\n")
        f.write(verdict)
    print("Verdict written to verdict.md")

Step 7: Run the synthesis

This produces the final verdict file. Open it and read the three objections. If any of them make you uncomfortable, good. That discomfort on day zero is worth more than the same realization on day four.

python synthesize.py
cat verdict.md

Step 8: Wire both steps into a single command

A one-line runner makes this something you actually use instead of something you bookmark and forget.

cat > review.sh << 'SCRIPT'
#!/usr/bin/env bash
set -e
echo "Starting adversarial review..."
python critique_runner.py
python synthesize.py
echo "Done. Read verdict.md."
SCRIPT
chmod +x review.sh
./review.sh

Breakage

If you skip the synthesis step, you get 18 objections with no ranking. A solo founder will read all 18, feel overwhelmed, and ignore every one of them. Raw critiques without triage are noise. Noise feels like due diligence but produces no decisions. The founder goes back to building the thing they already wanted to build, now with a vague sense of guilt instead of a concrete action list. The six personas did their jobs. The pipeline failed at the last mile.

Failure Without SynthesisSix critiques produce 18 unranked objections that the founder ignores. Nodes: prd.md (The raw PRD); critique_runner.py (Sends PRD through six personas); critiques.json (18 unranked objections); Founder's brain (Overwhelmed, defaults to original plan).prd.mdThe raw PRDcritique_runner.pySends PRD through six personascritiques.json18 unranked objectionsFounder's brainOverwhelmed, defaults to original plan
  • prd.md feeds into critique_runner.py
  • critique_runner.py produces critiques.json with 18 objections
  • Founder reads critiques.json, gets overwhelmed
  • Founder ignores objections and starts building anyway

The fix

The synthesis step is the fix. It already exists in Step 6 above, but here is the hardened version that also rejects a PRD outright if two or more HIGH severity objections survive synthesis. This version adds a go/no-go signal so the founder does not have to interpret the output.

# In synthesize.py, replace the __main__ block with this:

if __name__ == "__main__":
    critiques = load_critiques("critiques.json")
    verdict = synthesize(critiques)

    high_count = verdict.upper().count("HIGH")
    if high_count >= 2:
        signal = "NO-GO: Two or more HIGH severity objections found. Do not build until resolved."
    elif high_count == 1:
        signal = "CAUTION: One HIGH severity objection found. Resolve it before writing code."
    else:
        signal = "GO: No HIGH severity objections. Proceed with awareness of MEDIUM risks."

    with open("verdict.md", "w") as f:
        f.write("# Adversarial Review Verdict\n\n")
        f.write(f"## Signal: {signal}\n\n")
        f.write(verdict)

    print(f"\n{signal}")
    print("Full verdict written to verdict.md")

Fixed state

Complete Pipeline With Go/No-Go SignalSix critiques feed into synthesis, which produces a ranked verdict with a binary decision signal. Nodes: prd.md (The raw PRD); critique_runner.py (Sends PRD through six personas); Claude API (Processes each persona call); critiques.json (Six structured critique responses); synthesize.py (Deduplicates, ranks, and counts HIGH objections); verdict.md (Top 3 objections plus GO, CAUTION, or NO-GO signal).prd.mdThe raw PRDcritique_runner.pySends PRD through six personasClaude APIProcesses each persona callcritiques.jsonSix structured critique responsessynthesize.pyDeduplicates, ranks, and counts HIGH objectionsverdict.mdTop 3 objections plus GO, CAUTION, or NO-GO signal
  • prd.md feeds into critique_runner.py
  • critique_runner.py makes six calls to Claude API
  • Claude API returns six critiques stored in critiques.json
  • synthesize.py reads critiques.json and makes one synthesis call to Claude API
  • synthesize.py counts HIGH severity objections and assigns a signal
  • Output written to verdict.md with signal and ranked objections

After

You finished your PRD on Sunday night. It felt sharp. Monday morning you ran ./review.sh. Two minutes later you opened verdict.md. The signal said NO-GO. The top objection: "Your target user has a $40/week grocery budget but you are charging $4.99/month for a tool that competes with free recipe apps and TikTok meal prep videos. Customer acquisition cost will exceed lifetime value." The second objection: "Instacart and Walmart integrations each require partner agreements with 4 to 8 week approval cycles, making your 6-week MVP timeline impossible." You sat with it. The objections were right. You rewrote the PRD before lunch. You did not throw away four days of code because there was no code to throw away.

Takeaway

The pattern is adversarial decomposition: split one vague review task into multiple hostile perspectives, run them in parallel, and force a synthesis step that produces a decision, not a discussion. This works for PRDs, architecture docs, pitch decks, and pricing pages. Anything you wrote and plan to act on deserves six enemies before it gets a single friend.

Before you write any code, an agent runs six adversarial critiques against the PRD and surfaces the three objections that would have killed the idea on day four.

This tutorial is part of the Builder Weekly Tutorials corpus, licensed under CC BY 4.0. Fork it, reuse it, adapt it. Attribution required: link back to thebuilderweekly.com/tutorials or the source repository. Spot something wrong, or want to contribute a new tutorial? Open a PR at github.com/thebuilderweekly/ai-building-tutorials.