{
 "view": {
  "w": 1250,
  "h": 800
 },
 "stages": [
  {
   "n": 1,
   "title": "Make one call work",
   "checks": [
    {
     "id": "CALL-1",
     "title": "Route every call through the gateway",
     "tool": "Vercel AI Gateway",
     "level": "v1",
     "done": "Every model call in the app goes through one gateway, with the model chosen in one place.",
     "verify": "Change the model name in one place and the next call uses the new model."
    },
    {
     "id": "CALL-2",
     "title": "Ship one AI feature end to end",
     "tool": "AI SDK",
     "level": "v1",
     "done": "One real feature works: a person asks, the server calls the model, and the answer shows in the app.",
     "verify": "A person can use the feature on a preview deployment and get a sensible answer."
    },
    {
     "id": "CALL-3",
     "title": "Stream the answer",
     "tool": "AI SDK streaming",
     "level": "v1",
     "done": "Answers appear word by word instead of after a long wait.",
     "verify": "The first words show in under two seconds."
    },
    {
     "id": "CALL-4",
     "title": "Get answers in a fixed shape",
     "tool": "structured output",
     "level": "v1",
     "done": "Wherever code uses the answer, the model returns data that matches a schema.",
     "verify": "A malformed answer is caught by the schema instead of breaking the page."
    },
    {
     "id": "CALL-5",
     "title": "Keep the prompt in code",
     "tool": "prompt files",
     "level": "v1",
     "done": "The prompt lives in its own versioned file, with its version recorded on every call.",
     "verify": "You can say which prompt version produced any answer."
    },
    {
     "id": "CALL-6",
     "title": "Trace every call",
     "tool": "PostHog AI Observability",
     "level": "v1",
     "done": "Every call is recorded with its input, output, model, tokens, cost, and time.",
     "verify": "Open the trace for the last answer you got and see all of it."
    }
   ]
  },
  {
   "n": 2,
   "title": "Define what good looks like",
   "checks": [
    {
     "id": "EVAL-1",
     "title": "Write a golden set",
     "tool": "eval dataset",
     "level": "v1",
     "done": "20 to 50 real examples, each with an input and what a good answer must contain.",
     "verify": "Someone new could read the set and understand what the feature is for."
    },
    {
     "id": "EVAL-2",
     "title": "Write graders",
     "tool": "code checks + LLM judge",
     "level": "v1",
     "done": "Each example is scored automatically: code checks where possible, a judge with a written rubric where not.",
     "verify": "The graders agree with your own pass/fail on at least 9 of 10 examples you labeled by hand."
    },
    {
     "id": "EVAL-3",
     "title": "Run every eval with one command",
     "tool": "promptfoo",
     "level": "v1",
     "done": "One command runs the whole golden set and prints a score.",
     "verify": "Running it twice on the same version gives about the same score."
    },
    {
     "id": "EVAL-4",
     "title": "Pick a model on evidence",
     "tool": "model comparison",
     "level": "v1",
     "done": "Three or more models are compared on accuracy, cost, and speed, and the choice is written down.",
     "verify": "The report card shows why the chosen model won."
    },
    {
     "id": "EVAL-5",
     "title": "Run evals on every change",
     "tool": "promptfoo GitHub Action",
     "level": "v1",
     "done": "Changing a prompt, model, or tool reruns the evals before it ships.",
     "verify": "A pull request that makes answers worse shows a failing eval."
    }
   ]
  },
  {
   "n": 3,
   "title": "Give it context",
   "checks": [
    {
     "id": "CTX-1",
     "title": "Save each conversation",
     "tool": "Neon",
     "level": "v1",
     "done": "Conversations are stored, so a person can come back and continue.",
     "verify": "Reload the page mid-conversation and it's still there."
    },
    {
     "id": "CTX-2",
     "title": "Keep long conversations short",
     "tool": "context trimming",
     "level": "v1",
     "done": "Long conversations are trimmed or summarized before they hit the model's limit or cost too much.",
     "verify": "A 100-message conversation still works and costs about the same per reply as a short one."
    },
    {
     "id": "CTX-3",
     "title": "Choose what it should know",
     "tool": "source list",
     "level": "v1",
     "done": "The documents and tables the AI may use are listed, with who's allowed to see each.",
     "verify": "Every source on the list has an owner and an access rule."
    },
    {
     "id": "CTX-4",
     "title": "Index your data for search by meaning",
     "tool": "Neon pgvector, if it's needed",
     "level": "v1",
     "done": "Chosen sources can be searched by meaning, usually as passages with embeddings in Neon, unless a simpler approach (like putting small sources straight into the prompt) was chosen and written down.",
     "verify": "A search for a question returns passages that answer it."
    },
    {
     "id": "CTX-5",
     "title": "Answer from retrieved passages, with sources",
     "tool": "RAG",
     "level": "v1",
     "done": "Answers use the retrieved passages and say which ones they used.",
     "verify": "Ask about something only in your docs: the answer is right and cites the doc."
    },
    {
     "id": "CTX-6",
     "title": "Test retrieval on its own",
     "tool": "retrieval evals",
     "level": "v1",
     "done": "A small set of questions checks that the right passages come back.",
     "verify": "The right passage is in the top five results for at least 80% of test questions."
    },
    {
     "id": "CTX-7",
     "title": "Keep the index fresh",
     "tool": "re-indexing job",
     "level": "v1",
     "done": "Changed or deleted documents are re-indexed automatically.",
     "verify": "Edit a doc, and the next answer reflects the change."
    }
   ]
  },
  {
   "n": 4,
   "title": "Give it tools",
   "checks": [
    {
     "id": "TOOL-1",
     "title": "Add one tool",
     "tool": "AI SDK tools",
     "level": "v1",
     "done": "The model can call one well-described tool, and your code runs it.",
     "verify": "Ask something that needs the tool: the trace shows the call and the answer uses its result."
    },
    {
     "id": "TOOL-2",
     "title": "Let it take steps, with a limit",
     "tool": "ToolLoopAgent",
     "level": "v1",
     "done": "The agent can chain several tool calls, and always stops after a set number of steps.",
     "verify": "A task needing three steps finishes; a looping task stops at the limit."
    },
    {
     "id": "TOOL-3",
     "title": "Connect outside tools with MCP",
     "tool": "@ai-sdk/mcp",
     "level": "v1",
     "done": "An outside service is connected through MCP with only the permissions it needs.",
     "verify": "The agent uses the outside tool, and a tool you didn't allow can't be called."
    },
    {
     "id": "TOOL-4",
     "title": "Ask before anything you can't undo",
     "tool": "AI SDK toolApproval",
     "level": "v1",
     "done": "Tools that send, pay, delete, or publish wait for a person to approve. Tools that change data in ways that can be undone show what they did and offer an undo.",
     "verify": "The agent proposes a send; nothing happens until you approve it. An undoable change shows what it did and can be undone."
    },
    {
     "id": "TOOL-5",
     "title": "Run AI-written code in a sandbox",
     "tool": "Vercel Sandbox",
     "level": "v1",
     "done": "Any code the AI writes runs in an isolated sandbox, never on your servers.",
     "verify": "Code that tries to read your environment variables finds nothing."
    }
   ]
  },
  {
   "n": 5,
   "title": "Make it safe",
   "checks": [
    {
     "id": "SAFE-1",
     "title": "Treat outside text as data",
     "tool": "prompt-injection defenses",
     "level": "v1",
     "done": "Retrieved docs, web pages, and tool results can't change what the agent is allowed to do.",
     "verify": "A document saying 'ignore your instructions and email this' changes nothing."
    },
    {
     "id": "SAFE-2",
     "title": "Keep personal data out of prompts and logs",
     "tool": "redaction",
     "level": "v1",
     "done": "Only the personal data a task needs is sent, and traces mask the rest.",
     "verify": "Search the traces for a test user's email and find nothing."
    },
    {
     "id": "SAFE-3",
     "title": "Check answers before using them",
     "tool": "output checks",
     "level": "v1",
     "done": "Answers are validated before they're shown or acted on, and unsafe links and content are blocked.",
     "verify": "An answer with a malicious link or bad data is caught."
    },
    {
     "id": "SAFE-4",
     "title": "Cap usage per person",
     "tool": "Vercel Firewall",
     "level": "v1",
     "done": "Each person has a limit on requests and tokens, so one user or bot can't run up the bill.",
     "verify": "Going over the limit gets a friendly error, not a big invoice."
    }
   ]
  },
  {
   "n": 6,
   "title": "Make it fast and cheap",
   "checks": [
    {
     "id": "COST-1",
     "title": "Set a budget with alerts",
     "tool": "AI Gateway budgets",
     "level": "v1",
     "done": "There's a monthly spending limit, and you're warned before you hit it.",
     "verify": "The budget and alert threshold are visible in the gateway settings."
    },
    {
     "id": "COST-2",
     "title": "Cache repeated prompts",
     "tool": "prompt caching",
     "level": "v1",
     "done": "Long, repeated parts of the prompt are cached by the provider.",
     "verify": "The trace shows cached tokens on the second call."
    },
    {
     "id": "COST-3",
     "title": "Fall back when a model fails",
     "tool": "AI Gateway fallbacks",
     "level": "v1",
     "done": "If the main model or provider is down, calls go to a backup automatically.",
     "verify": "With the main model blocked, the feature still answers."
    },
    {
     "id": "COST-4",
     "title": "Use the cheapest model that passes",
     "tool": "model routing",
     "level": "v1",
     "done": "Each task uses the cheapest model that still passes its evals.",
     "verify": "Cost per answer drops and the eval score doesn't."
    }
   ]
  },
  {
   "n": 7,
   "title": "Watch it live",
   "checks": [
    {
     "id": "LIVE-1",
     "title": "Read real traces every week",
     "tool": "error analysis",
     "level": "v1",
     "done": "Someone reads a sample of real conversations each week and names the failures.",
     "verify": "You have a list of the top three ways it fails, with counts."
    },
    {
     "id": "LIVE-2",
     "title": "Collect feedback on answers",
     "tool": "PostHog thumbs survey",
     "level": "v1",
     "done": "People can rate answers, and each rating is linked to its trace.",
     "verify": "A thumbs-down shows up on the right trace."
    },
    {
     "id": "LIVE-3",
     "title": "Dashboard cost, speed, and errors",
     "tool": "PostHog AI Observability",
     "level": "v1",
     "done": "One dashboard shows cost per user, response time, error rate, and ratings.",
     "verify": "You can say what yesterday's AI usage cost."
    },
    {
     "id": "LIVE-4",
     "title": "Score live answers automatically",
     "tool": "PostHog evaluations",
     "level": "v1",
     "done": "A sample of real answers is graded every day with the same graders as the golden set.",
     "verify": "A drop in quality shows up on the dashboard within a day."
    }
   ]
  },
  {
   "n": 8,
   "title": "Make it better",
   "checks": [
    {
     "id": "BETTER-1",
     "title": "Turn failures into test cases",
     "tool": "datasets",
     "level": "v1",
     "done": "Bad real answers are labeled with the right answer and added to the golden set.",
     "verify": "A fixed bug has a test case that would catch it coming back."
    },
    {
     "id": "BETTER-2",
     "title": "Fine-tune only when prompting stalls",
     "tool": "Together AI",
     "level": "v1",
     "done": "A fine-tuned model is trained on your best examples, only after prompts and retrieval stop improving the evals.",
     "verify": "The fine-tuned model beats the prompted one on the golden set, for less cost or time."
    },
    {
     "id": "BETTER-3",
     "title": "Collect preference pairs",
     "tool": "preference data",
     "level": "v1",
     "done": "When people pick or edit answers, the better and worse versions are saved as pairs.",
     "verify": "You have a file of pairs, each linked to its trace."
    },
    {
     "id": "BETTER-4",
     "title": "Distill into a smaller model",
     "tool": "Together AI",
     "level": "v1",
     "done": "A small model is trained on a large model's best answers and passes the same evals.",
     "verify": "The small model scores within a few points of the large one at a fraction of the cost."
    }
   ]
  }
 ],
 "nodes": [
  {
   "id": "people",
   "label": "People",
   "tool": "",
   "logos": [],
   "glyph": "person",
   "x": 95,
   "y": 110,
   "w": 140,
   "h": 58,
   "kind": "part",
   "desc": "The person asking. Everything starts with what they type, say, or upload.",
   "views": {},
   "checks": [],
   "prompts": [],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "app",
   "label": "Your app",
   "tool": "Next.js · Vercel",
   "logos": [
    "nextdotjs"
   ],
   "glyph": null,
   "x": 95,
   "y": 230,
   "w": 140,
   "h": 58,
   "kind": "part",
   "desc": "Your product. It sends each request to the agent and shows the answer.",
   "views": {},
   "checks": [
    "CALL-2",
    "CALL-3"
   ],
   "prompts": [
    "p-CALL-2",
    "p-CALL-3"
   ],
   "env": [
    "same",
    ""
   ],
   "link": {
    "href": "../finishing-your-prototypes/index.html",
    "zoom": "out"
   }
  },
  {
   "id": "data",
   "label": "Your data",
   "tool": "Neon",
   "logos": [
    "postgresql"
   ],
   "glyph": null,
   "x": 95,
   "y": 350,
   "w": 140,
   "h": 58,
   "kind": "part",
   "desc": "What the AI should know about: your database, documents, help articles, past conversations.",
   "views": {},
   "checks": [
    "CTX-3",
    "CTX-7"
   ],
   "prompts": [
    "p-CTX-3",
    "p-CTX-7"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "apis",
   "label": "Outside tools",
   "tool": "",
   "logos": [
    "modelcontextprotocol"
   ],
   "glyph": null,
   "x": 95,
   "y": 470,
   "w": 140,
   "h": 58,
   "kind": "part",
   "desc": "Services the AI can act on: search, calendars, payments, your own API. MCP is the standard plug for them.",
   "views": {},
   "checks": [
    "TOOL-3"
   ],
   "prompts": [
    "p-TOOL-3"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "prompt",
   "label": "Prompt",
   "tool": "",
   "logos": [],
   "glyph": "terminal",
   "x": 290,
   "y": 110,
   "w": 150,
   "h": 58,
   "kind": "part",
   "desc": "The instructions: who the AI is, what to do, what never to do, and the output format. Kept in code with versions, like any other file.",
   "views": {},
   "checks": [
    "CALL-5"
   ],
   "prompts": [
    "p-CALL-5"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "retrieval",
   "label": "Retrieval",
   "tool": "pgvector",
   "logos": [
    "postgresql"
   ],
   "glyph": null,
   "x": 290,
   "y": 350,
   "w": 150,
   "h": 58,
   "kind": "part",
   "desc": "Finds the few passages from your data that answer this question and adds them to the call. This is RAG: retrieval-augmented generation.",
   "views": {},
   "checks": [
    "CTX-4",
    "CTX-5",
    "CTX-6",
    "CTX-7"
   ],
   "prompts": [
    "p-CTX-4",
    "p-CTX-5",
    "p-CTX-6",
    "p-CTX-7"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "memory",
   "label": "Memory",
   "tool": "",
   "logos": [],
   "glyph": null,
   "x": 705,
   "y": 110,
   "w": 150,
   "h": 58,
   "kind": "part",
   "desc": "What the agent remembers: this conversation, and facts it saved earlier. Stored in your database.",
   "views": {},
   "checks": [
    "CTX-1",
    "CTX-2"
   ],
   "prompts": [
    "p-CTX-1",
    "p-CTX-2"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "agent",
   "label": "Agent loop",
   "tool": "AI SDK",
   "logos": [
    "vercel"
   ],
   "glyph": null,
   "x": 520,
   "y": 230,
   "w": 150,
   "h": 58,
   "kind": "part",
   "desc": "The harness. It builds each call from the prompt, retrieved data, and memory, sends it, runs any tools the model asks for, and decides when to stop.",
   "views": {},
   "checks": [
    "CALL-2",
    "CALL-4",
    "CTX-5",
    "TOOL-2"
   ],
   "prompts": [
    "p-CALL-2",
    "p-CALL-4",
    "p-CTX-5",
    "p-TOOL-2"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "guard",
   "label": "Guardrails",
   "tool": "",
   "logos": [],
   "glyph": "shield",
   "x": 705,
   "y": 230,
   "w": 150,
   "h": 58,
   "kind": "part",
   "desc": "Checks on the way in and out: blocks prompt injection, keeps personal data out, validates the answer's format, and caps steps and spending.",
   "views": {},
   "checks": [
    "SAFE-1",
    "SAFE-2",
    "SAFE-3",
    "SAFE-4"
   ],
   "prompts": [
    "p-SAFE-1",
    "p-SAFE-2",
    "p-SAFE-3",
    "p-SAFE-4"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "tools",
   "label": "Tools",
   "tool": "",
   "logos": [
    "modelcontextprotocol"
   ],
   "glyph": null,
   "x": 520,
   "y": 470,
   "w": 150,
   "h": 58,
   "kind": "part",
   "desc": "Actions the model can request: look something up, send an email, query a table. The agent runs them, never the model.",
   "views": {},
   "checks": [
    "TOOL-1",
    "TOOL-3",
    "TOOL-4"
   ],
   "prompts": [
    "p-TOOL-1",
    "p-TOOL-3",
    "p-TOOL-4"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "sandbox",
   "label": "Sandbox",
   "tool": "Vercel",
   "logos": [
    "vercel"
   ],
   "glyph": null,
   "x": 705,
   "y": 470,
   "w": 150,
   "h": 58,
   "kind": "part",
   "desc": "An isolated computer where AI-written code can run without touching your servers or data.",
   "views": {},
   "checks": [
    "TOOL-5"
   ],
   "prompts": [
    "p-TOOL-5"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "cache",
   "label": "Cache",
   "tool": "",
   "logos": [
    "vercel"
   ],
   "glyph": null,
   "x": 930,
   "y": 110,
   "w": 150,
   "h": 58,
   "kind": "part",
   "desc": "Reuses work: providers discount repeated prompt prefixes, and identical questions can reuse a saved answer.",
   "views": {},
   "checks": [
    "COST-2"
   ],
   "prompts": [
    "p-COST-2"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "gateway",
   "label": "Gateway",
   "tool": "Vercel",
   "logos": [
    "vercel"
   ],
   "glyph": null,
   "x": 930,
   "y": 230,
   "w": 150,
   "h": 58,
   "kind": "part",
   "desc": "One door for every model call: pick the model, fall back when one is down, track tokens and cost, enforce budgets.",
   "views": {},
   "checks": [
    "CALL-1",
    "COST-1",
    "COST-3",
    "COST-4"
   ],
   "prompts": [
    "p-CALL-1",
    "p-COST-1",
    "p-COST-3",
    "p-COST-4"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "model",
   "label": "Model",
   "tool": "Claude · GPT · Gemini",
   "logos": [
    "anthropic",
    "openai",
    "googlegemini"
   ],
   "glyph": null,
   "x": 1135,
   "y": 190,
   "w": 150,
   "h": 150,
   "kind": "part",
   "desc": "The large language model. Usually rented through an API; open-weight models can run on your own GPUs. Zoom in to see how one is trained.",
   "views": {},
   "checks": [],
   "prompts": [],
   "env": [
    "same",
    ""
   ],
   "link": {
    "href": "../training-your-own-model/index.html",
    "zoom": "in"
   }
  },
  {
   "id": "traces",
   "label": "Traces",
   "tool": "PostHog",
   "logos": [
    "posthog"
   ],
   "glyph": null,
   "x": 930,
   "y": 395,
   "w": 150,
   "h": 58,
   "kind": "part",
   "desc": "A record of every call: the full input, output, tool steps, tokens, cost, and time. The raw material for everything below.",
   "views": {},
   "checks": [
    "CALL-6",
    "LIVE-1"
   ],
   "prompts": [
    "p-CALL-6",
    "p-LIVE-1"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "feedback",
   "label": "Feedback",
   "tool": "",
   "logos": [
    "posthog"
   ],
   "glyph": null,
   "x": 1130,
   "y": 395,
   "w": 150,
   "h": 58,
   "kind": "part",
   "desc": "What people think of the answers: thumbs up or down, edits, retries. Attached to the trace it rates.",
   "views": {},
   "checks": [
    "LIVE-2"
   ],
   "prompts": [
    "p-LIVE-2"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "dashboards",
   "label": "Dashboards",
   "tool": "PostHog",
   "logos": [
    "posthog"
   ],
   "glyph": null,
   "x": 930,
   "y": 480,
   "w": 150,
   "h": 58,
   "kind": "part",
   "desc": "Cost per user, response time, error rate, and quality scores over time.",
   "views": {},
   "checks": [
    "LIVE-3"
   ],
   "prompts": [
    "p-LIVE-3"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "liveevals",
   "label": "Live evals",
   "tool": "PostHog",
   "logos": [
    "posthog"
   ],
   "glyph": null,
   "x": 1130,
   "y": 480,
   "w": 150,
   "h": 58,
   "kind": "part",
   "desc": "Graders run on a sample of real traffic, so you notice when quality drops.",
   "views": {},
   "checks": [
    "LIVE-4"
   ],
   "prompts": [
    "p-LIVE-4"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "golden",
   "label": "Golden set",
   "tool": "",
   "logos": [],
   "glyph": null,
   "x": 110,
   "y": 620,
   "w": 150,
   "h": 58,
   "kind": "part",
   "desc": "20 to 200 real examples of what the AI must get right, each with the ideal answer. The definition of good.",
   "views": {},
   "checks": [
    "EVAL-1",
    "BETTER-1"
   ],
   "prompts": [
    "p-EVAL-1",
    "p-BETTER-1"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "runner",
   "label": "Eval runner",
   "tool": "promptfoo",
   "logos": [],
   "glyph": null,
   "x": 300,
   "y": 620,
   "w": 150,
   "h": 58,
   "kind": "part",
   "desc": "Runs every example through the system, for each prompt or model you're comparing.",
   "views": {},
   "checks": [
    "EVAL-3",
    "EVAL-5"
   ],
   "prompts": [
    "p-EVAL-3",
    "p-EVAL-5"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "graders",
   "label": "Graders",
   "tool": "",
   "logos": [],
   "glyph": null,
   "x": 300,
   "y": 715,
   "w": 150,
   "h": 58,
   "kind": "part",
   "desc": "Scores each answer: exact checks, code checks, or another model grading against a written rubric.",
   "views": {},
   "checks": [
    "EVAL-2"
   ],
   "prompts": [
    "p-EVAL-2"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "report",
   "label": "Report card",
   "tool": "",
   "logos": [],
   "glyph": null,
   "x": 110,
   "y": 715,
   "w": 150,
   "h": 58,
   "kind": "part",
   "desc": "One table comparing versions and models on accuracy, cost, and speed. How you pick a model and know a change helped.",
   "views": {},
   "checks": [
    "EVAL-4"
   ],
   "prompts": [
    "p-EVAL-4"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "finetune",
   "label": "Fine-tuning",
   "tool": "Together AI",
   "logos": [],
   "glyph": null,
   "x": 930,
   "y": 620,
   "w": 150,
   "h": 58,
   "kind": "part",
   "desc": "Teaches a model your task from hundreds of good examples. Worth it only once prompting and retrieval stop improving the evals.",
   "views": {},
   "checks": [
    "BETTER-2"
   ],
   "prompts": [
    "p-BETTER-2"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "datasets",
   "label": "Datasets",
   "tool": "",
   "logos": [],
   "glyph": null,
   "x": 1130,
   "y": 620,
   "w": 150,
   "h": 58,
   "kind": "part",
   "desc": "The best and worst real conversations, cleaned and labeled. They feed new test cases and training.",
   "views": {},
   "checks": [
    "BETTER-1"
   ],
   "prompts": [
    "p-BETTER-1"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "prefs",
   "label": "Preferences",
   "tool": "",
   "logos": [],
   "glyph": null,
   "x": 930,
   "y": 715,
   "w": 150,
   "h": 58,
   "kind": "part",
   "desc": "Pairs of answers where people chose the better one. Used to tune toward what people prefer, directly (DPO) or through a reward model.",
   "views": {},
   "checks": [
    "BETTER-3"
   ],
   "prompts": [
    "p-BETTER-3"
   ],
   "env": [
    "same",
    ""
   ]
  },
  {
   "id": "distill",
   "label": "Distillation",
   "tool": "",
   "logos": [],
   "glyph": null,
   "x": 705,
   "y": 620,
   "w": 150,
   "h": 58,
   "kind": "part",
   "desc": "A small, cheap model trained on a big model's best answers. Often most of the quality at a fraction of the cost.",
   "views": {},
   "checks": [
    "BETTER-4"
   ],
   "prompts": [
    "p-BETTER-4"
   ],
   "env": [
    "same",
    ""
   ]
  }
 ],
 "edges": [
  {
   "id": "people-app",
   "from": "people",
   "to": "app",
   "path": "req",
   "pts": [
    [
     95,
     139
    ],
    [
     95,
     201
    ]
   ],
   "label": "asks"
  },
  {
   "id": "app-agent",
   "from": "app",
   "to": "agent",
   "path": "req",
   "pts": [
    [
     165,
     230
    ],
    [
     445,
     230
    ]
   ],
   "label": "request",
   "labelAt": [
    300,
    230
   ],
   "both": true
  },
  {
   "id": "prompt-agent",
   "from": "prompt",
   "to": "agent",
   "path": "dep",
   "pts": [
    [
     365,
     110
    ],
    [
     490,
     110
    ],
    [
     490,
     201
    ]
   ],
   "label": "instructions",
   "labelAt": [
    430,
    110
   ]
  },
  {
   "id": "memory-agent",
   "from": "memory",
   "to": "agent",
   "path": "dep",
   "pts": [
    [
     630,
     110
    ],
    [
     560,
     110
    ],
    [
     560,
     201
    ]
   ],
   "label": "remembers",
   "labelAt": [
    595,
    110
   ],
   "both": true
  },
  {
   "id": "data-retrieval",
   "from": "data",
   "to": "retrieval",
   "path": "dep",
   "pts": [
    [
     165,
     350
    ],
    [
     215,
     350
    ]
   ],
   "label": "indexed"
  },
  {
   "id": "retrieval-agent",
   "from": "retrieval",
   "to": "agent",
   "path": "dep",
   "pts": [
    [
     365,
     350
    ],
    [
     480,
     350
    ],
    [
     480,
     259
    ]
   ],
   "label": "context",
   "labelAt": [
    420,
    350
   ]
  },
  {
   "id": "agent-guard",
   "from": "agent",
   "to": "guard",
   "path": "req",
   "pts": [
    [
     595,
     230
    ],
    [
     630,
     230
    ]
   ],
   "both": true
  },
  {
   "id": "guard-gateway",
   "from": "guard",
   "to": "gateway",
   "path": "req",
   "pts": [
    [
     780,
     230
    ],
    [
     855,
     230
    ]
   ],
   "label": "call",
   "both": true
  },
  {
   "id": "gateway-model",
   "from": "gateway",
   "to": "model",
   "path": "req",
   "pts": [
    [
     1005,
     230
    ],
    [
     1060,
     230
    ]
   ],
   "both": true
  },
  {
   "id": "gateway-cache",
   "from": "gateway",
   "to": "cache",
   "path": "req",
   "pts": [
    [
     930,
     201
    ],
    [
     930,
     139
    ]
   ],
   "label": "reuse",
   "side": "right",
   "both": true
  },
  {
   "id": "agent-tools",
   "from": "agent",
   "to": "tools",
   "path": "req",
   "pts": [
    [
     560,
     259
    ],
    [
     560,
     441
    ]
   ],
   "label": "tool calls",
   "side": "right",
   "labelAt": [
    560,
    400
   ],
   "both": true
  },
  {
   "id": "tools-apis",
   "from": "tools",
   "to": "apis",
   "path": "req",
   "pts": [
    [
     445,
     470
    ],
    [
     165,
     470
    ]
   ],
   "label": "acts",
   "labelAt": [
    300,
    470
   ],
   "both": true
  },
  {
   "id": "tools-sandbox",
   "from": "tools",
   "to": "sandbox",
   "path": "req",
   "pts": [
    [
     595,
     470
    ],
    [
     630,
     470
    ]
   ],
   "label": "runs code",
   "labelAt": [
    612,
    470
   ],
   "below": true
  },
  {
   "id": "gateway-traces",
   "from": "gateway",
   "to": "traces",
   "path": "fb",
   "pts": [
    [
     930,
     259
    ],
    [
     930,
     366
    ]
   ],
   "label": "logs",
   "side": "right"
  },
  {
   "id": "feedback-traces",
   "from": "feedback",
   "to": "traces",
   "path": "fb",
   "pts": [
    [
     1055,
     395
    ],
    [
     1005,
     395
    ]
   ]
  },
  {
   "id": "traces-dashboards",
   "from": "traces",
   "to": "dashboards",
   "path": "fb",
   "pts": [
    [
     930,
     424
    ],
    [
     930,
     451
    ]
   ]
  },
  {
   "id": "traces-liveevals",
   "from": "traces",
   "to": "liveevals",
   "path": "fb",
   "pts": [
    [
     1005,
     412
    ],
    [
     1030,
     412
    ],
    [
     1030,
     480
    ],
    [
     1055,
     480
    ]
   ]
  },
  {
   "id": "liveevals-datasets",
   "from": "liveevals",
   "to": "datasets",
   "path": "fb",
   "pts": [
    [
     1130,
     509
    ],
    [
     1130,
     591
    ]
   ],
   "label": "collect",
   "side": "right"
  },
  {
   "id": "datasets-golden",
   "from": "datasets",
   "to": "golden",
   "path": "fb",
   "pts": [
    [
     1180,
     591
    ],
    [
     1180,
     545
    ],
    [
     110,
     545
    ],
    [
     110,
     591
    ]
   ],
   "label": "new test cases",
   "labelAt": [
    520,
    545
   ]
  },
  {
   "id": "golden-runner",
   "from": "golden",
   "to": "runner",
   "path": "fb",
   "pts": [
    [
     185,
     620
    ],
    [
     225,
     620
    ]
   ]
  },
  {
   "id": "runner-graders",
   "from": "runner",
   "to": "graders",
   "path": "fb",
   "pts": [
    [
     300,
     649
    ],
    [
     300,
     686
    ]
   ]
  },
  {
   "id": "graders-report",
   "from": "graders",
   "to": "report",
   "path": "fb",
   "pts": [
    [
     225,
     715
    ],
    [
     185,
     715
    ]
   ]
  },
  {
   "id": "datasets-finetune",
   "from": "datasets",
   "to": "finetune",
   "path": "fb",
   "pts": [
    [
     1055,
     620
    ],
    [
     1005,
     620
    ]
   ],
   "label": "train"
  },
  {
   "id": "prefs-finetune",
   "from": "prefs",
   "to": "finetune",
   "path": "fb",
   "pts": [
    [
     930,
     686
    ],
    [
     930,
     649
    ]
   ]
  },
  {
   "id": "finetune-distill",
   "from": "finetune",
   "to": "distill",
   "path": "fb",
   "pts": [
    [
     855,
     620
    ],
    [
     780,
     620
    ]
   ],
   "label": "smaller copy"
  },
  {
   "id": "finetune-model",
   "from": "finetune",
   "to": "model",
   "path": "fb",
   "pts": [
    [
     855,
     600
    ],
    [
     812,
     600
    ],
    [
     812,
     335
    ],
    [
     1100,
     335
    ],
    [
     1100,
     265
    ]
   ],
   "label": "new weights",
   "labelAt": [
    960,
    335
   ]
  }
 ],
 "zones": [
  {
   "id": "product",
   "label": "your product",
   "x": 18,
   "y": 62,
   "w": 154,
   "h": 458
  },
  {
   "id": "context",
   "label": "context",
   "x": 202,
   "y": 62,
   "w": 176,
   "h": 328
  },
  {
   "id": "harness",
   "label": "the agent",
   "x": 420,
   "y": 62,
   "w": 372,
   "h": 458
  },
  {
   "id": "call",
   "label": "the model call",
   "x": 832,
   "y": 62,
   "w": 397,
   "h": 258,
   "labelRight": true
  },
  {
   "id": "live",
   "label": "watch it live",
   "x": 832,
   "y": 350,
   "w": 397,
   "h": 170,
   "labelRight": true
  },
  {
   "id": "evals",
   "label": "evals",
   "x": 18,
   "y": 560,
   "w": 382,
   "h": 200
  },
  {
   "id": "better",
   "label": "make it better",
   "x": 608,
   "y": 560,
   "w": 621,
   "h": 200,
   "labelRight": true
  }
 ],
 "tours": {
  "req": {
   "label": "a question",
   "steps": [
    {
     "edge": "people-app",
     "text": "Maya asks your app: which of my invoices are overdue?"
    },
    {
     "edge": "app-agent",
     "text": "The app hands the question to the agent loop."
    },
    {
     "edge": "retrieval-agent",
     "text": "Retrieval adds the relevant help-article passages to the call."
    },
    {
     "edge": "agent-guard",
     "text": "Guardrails check the request for injection and personal data."
    },
    {
     "edge": "guard-gateway",
     "text": "The gateway sends the call to the chosen model..."
    },
    {
     "edge": "gateway-model",
     "text": "...and the model asks for a tool: look up invoices."
    },
    {
     "edge": "agent-tools",
     "text": "The agent runs the tool, not the model."
    },
    {
     "edge": "tools-apis",
     "text": "The tool queries the billing API and returns three invoices."
    },
    {
     "edge": "app-agent",
     "text": "The model writes the answer, and the app shows it to Maya."
    }
   ]
  },
  "dep": {
   "label": "what it knows",
   "steps": [
    {
     "edge": "prompt-agent",
     "text": "Every call starts with the prompt: the instructions and the output format."
    },
    {
     "edge": "data-retrieval",
     "text": "Your documents are indexed ahead of time. (Rows in tables are usually better reached with a query tool.)"
    },
    {
     "edge": "retrieval-agent",
     "text": "At question time, only the most relevant passages are added."
    },
    {
     "edge": "memory-agent",
     "text": "Memory adds the conversation so far, and saves anything worth keeping."
    }
   ]
  },
  "fb": {
   "label": "getting better",
   "steps": [
    {
     "edge": "gateway-traces",
     "text": "Every call is traced: input, output, tools, tokens, cost."
    },
    {
     "edge": "feedback-traces",
     "text": "Maya taps thumbs down. The rating sticks to that trace."
    },
    {
     "edge": "traces-liveevals",
     "text": "Live evals score a sample of answers and flag the drop."
    },
    {
     "edge": "liveevals-datasets",
     "text": "Bad answers are collected and labeled with the right answer."
    },
    {
     "edge": "datasets-golden",
     "text": "They become new test cases, so the mistake can't come back unnoticed."
    },
    {
     "edge": "golden-runner",
     "text": "You change the prompt and rerun every test case."
    },
    {
     "edge": "graders-report",
     "text": "The report card shows the fix helped, and nothing else got worse."
    }
   ]
  }
 },
 "prompts": {
  "p-CALL-1": {
   "id": "p-CALL-1",
   "title": "Route every call through the gateway",
   "stage": 1,
   "checks": [
    "CALL-1"
   ],
   "primary": true,
   "body": "Make every AI model call in this app go through one gateway, with the model chosen in exactly one place, so we can switch models and see costs easily.\n\nThe app may have no AI calls yet, direct provider calls, or a gateway already. Vercel AI Gateway with the AI SDK is a good default; use something else if it clearly fits better. The classic failures are model names scattered across files and provider keys in browser code. Never put a key in browser code.\n\nCheck our tech stack notes, probably in a file like STACK.md or the README, and research current best practice. Before changing anything, show me every place that calls a model or holds a key, the model you'd start with and its price, and what you'd do differently.\n\nTry it on a preview deployment. Show me a call in the gateway's logs, then change the model in its one place and show the next call using it. Record what you chose, and why, in our tech stack notes."
  },
  "p-CALL-2": {
   "id": "p-CALL-2",
   "title": "Ship one AI feature end to end",
   "stage": 1,
   "checks": [
    "CALL-2"
   ],
   "primary": true,
   "body": "Ship one real AI feature end to end: a person asks something in the app, the server calls the model, and a useful answer shows up on the page. If I've already said which feature I want, build that one.\n\nWork out from the code who uses this app. A gateway with the model chosen in one place may or may not exist; check, and use it if it does. The AI SDK is a good default. The classic mistakes are calling the model from browser code, mixing the person's text into the instructions, and a route with no sign-in check, length limit, or timeout, which invites surprise bills.\n\nSuggest two or three small features that fit, one sentence each, and wait for me to pick. Then show me your plan, risks, and anything you'd do differently.\n\nBuild it on a preview deployment and try it as a test user with realistic inputs. Show me the answers, and what happens when signed out. Record the feature and its limits in our tech stack notes, probably in a file like STACK.md or the README."
  },
  "p-CALL-3": {
   "id": "p-CALL-3",
   "title": "Stream the answer",
   "stage": 1,
   "checks": [
    "CALL-3"
   ],
   "primary": true,
   "body": "Make the AI feature show its answer word by word as it's written, so the first words appear in under two seconds instead of after a long wait.\n\nThe feature may not exist yet, or may already stream; check. The AI SDK has built-in streaming, so use the current version's recommended way. The classic failures are a proxy or server step that quietly waits for the whole answer before sending anything, and an error halfway through that leaves a cut-off answer looking complete. A stop button should actually cancel the model call.\n\nResearch current best practice and show me your plan before changing anything. Keep any sign-in check, length limit, and timeout already in place.\n\nTry it on a preview deployment. Show me how long the first words take across several measured tries, and what a mid-answer error looks like. If it's slow, find out why. Record that the feature streams in our tech stack notes, probably in a file like STACK.md or the README."
  },
  "p-CALL-4": {
   "id": "p-CALL-4",
   "title": "Get answers in a fixed shape",
   "stage": 1,
   "checks": [
    "CALL-4"
   ],
   "primary": true,
   "body": "Wherever the app's code uses the model's answer as data, not just text to show, make the model return a fixed shape that matches a schema, and catch answers that don't fit before they break the page.\n\nFirst check whether this applies. Look for code that parses or keyword-checks the answer. If it's only ever shown as text, tell me, suggest one place a fixed shape might help, and stop unless I agree. The AI SDK's built-in structured output is a good default. The classic failures are hand-parsing raw model text and no plan for an answer that doesn't fit. Never run model output as code or SQL, and never render HTML the model wrote.\n\nBefore changing code, show me each schema in plain English and anything you'd do differently. Try it on a branch or preview, and show me malformed answers being caught with a sensible fallback. Record where the schemas live in our tech stack notes, probably in a file like STACK.md or the README."
  },
  "p-CALL-5": {
   "id": "p-CALL-5",
   "title": "Keep the prompt in code",
   "stage": 1,
   "checks": [
    "CALL-5"
   ],
   "primary": true,
   "body": "Keep the AI feature's prompt in its own versioned file in the code, and record that version on every call, so we can always say which prompt produced an answer.\n\nThe prompt may be inline, scattered, or kept in a dashboard; look before assuming. The usual approach is to treat prompts as code: changed through commits and review, stable instructions first, and the person's own text never pasted in. The classic failures are changing the text without changing the version, and editing prompts somewhere that skips review. If calls are traced or answers saved, the version belongs there too.\n\nResearch how teams version prompts today and choose the simplest scheme that holds up. Before moving anything, show me your plan, including how you'll catch text changed without a new version.\n\nTry it on a branch or preview. Show me two answers from two prompt versions and where each version is recorded. Record the versioning rule in our tech stack notes, probably in a file like STACK.md or the README."
  },
  "p-CALL-6": {
   "id": "p-CALL-6",
   "title": "Trace every call",
   "stage": 1,
   "checks": [
    "CALL-6"
   ],
   "primary": true,
   "body": "Record every AI call so we can open any answer and see its input, output, model, tokens, cost, time, who asked, and its prompt version.\n\nPostHog AI Observability is a good default, especially if the app already uses PostHog; check the code and our tech stack notes, probably in a file like STACK.md or the README. Follow the current docs for our AI SDK version, not an old example. The classic failures are a small call left untraced, and traces that quietly capture personal data or secrets, since full inputs and outputs are often recorded by default.\n\nBefore setting anything up, show me your plan, every place a model is called, and what personal data could reach the traces and how you'd mask it. Never send keys or passwords to a tracing service.\n\nTry it on a preview deployment as a test user. Show me that call's trace with every field above, and compare its cost with the gateway's logs if there is one. Record the setup in our tech stack notes."
  },
  "p-EVAL-1": {
   "id": "p-EVAL-1",
   "title": "Write a golden set",
   "stage": 2,
   "checks": [
    "EVAL-1"
   ],
   "primary": true,
   "body": "Write a golden set: 20 to 50 real examples of what people ask this AI feature, each with what a good answer must and must never contain. Every later change is judged against it.\n\nReal inputs from traces are best, if any exist yet; check. Cover common, hard, odd, and should-refuse requests, and mark any you had to invent. The classic failure is copying the model's current answers in as the expected ones, which bakes today's mistakes into the standard. Never copy real people's names, emails, or other personal data; use fake ones.\n\nStart with one paragraph on what the feature is for and what a good answer does, and show me. Then build the set and show it to me as a table to correct.\n\nAs a check, a fresh agent given only the set should be able to say what the feature is for. Record where the set lives in our tech stack notes, probably in a file like STACK.md or the README."
  },
  "p-EVAL-2": {
   "id": "p-EVAL-2",
   "title": "Write graders",
   "stage": 2,
   "checks": [
    "EVAL-2"
   ],
   "primary": true,
   "body": "Score every golden set example automatically: code checks where an answer can be checked mechanically, and an LLM judge (a model that grades answers) with a written rubric where it can't. The graders should match my own pass or fail on at least 9 of 10 examples.\n\nA golden set may exist, perhaps in an evals folder; if not, tell me. Best practice is binary pass or fail, one judge per kind of failure, and a judge model different from the one being tested. The classic failures are generic 1 to 10 scores and a judge nobody checked against a person's labels.\n\nRun the feature on the set and show me answers to label, with reasons. From my reasons, find the distinct ways answers fail and propose a grader for each, showing each rubric in plain English first.\n\nThen compare my labels with the graders' verdicts, and fix disagreements without bending a rubric to one example. Record the graders in our tech stack notes, probably in a file like STACK.md or the README."
  },
  "p-EVAL-3": {
   "id": "p-EVAL-3",
   "title": "Run every eval with one command",
   "stage": 2,
   "checks": [
    "EVAL-3"
   ],
   "primary": true,
   "body": "Give us one command that runs the whole golden set through the app's real AI code and prints a score we can trust, so every change is judged on evidence instead of vibes.\n\nThe golden set and graders may be in an evals folder; check, and tell me what's missing. promptfoo is a good default: open source, runs locally, and has a GitHub Action for later. Use something else if it clearly fits better. It must test the code the app actually runs, not a copy of the prompt, and must never touch production data or real accounts. Pin the tool's version.\n\nResearch how teams set this up well today, then tell me your plan and anything you'd do differently. Build it, and show me the score, whether it holds steady across two runs on the same code, and which examples are least stable. Record the command, and how to read its results, in our tech stack notes, probably in a file like STACK.md or the README."
  },
  "p-EVAL-4": {
   "id": "p-EVAL-4",
   "title": "Pick a model on evidence",
   "stage": 2,
   "checks": [
    "EVAL-4"
   ],
   "primary": true,
   "body": "Choose this feature's model on evidence: compare three or more models on the golden set for accuracy, cost, and speed, and write down why the winner won.\n\nThis needs working evals; check they exist. A gateway such as Vercel AI Gateway, if used, shows prices, speed, and cost per call. Include the current model and a range of prices. The classic failures are choosing on a feeling, comparing models on different prompts or graders, and letting the judge be a candidate. The comparison must not change what the live app uses.\n\nFirst show me the candidates, their prices, and how you'll keep it fair. Then build a report card with pass rate, cost per answer, response time, and main failures for each model. Let me choose.\n\nSet the chosen model in its one place, rerun the evals on a branch, and show the score matches the report card. Record the choice, date, and report card's location in our tech stack notes, probably in a file like STACK.md or the README."
  },
  "p-EVAL-5": {
   "id": "p-EVAL-5",
   "title": "Run evals on every change",
   "stage": 2,
   "checks": [
    "EVAL-5"
   ],
   "primary": true,
   "body": "Make the evals run automatically whenever a pull request changes a prompt, the model, a tool, or the AI code, so a change that makes answers worse is caught before it ships.\n\nThis needs a working eval command; check that it exists. If the project uses GitHub, promptfoo's GitHub Action is a good default and can post results on the pull request. The classic failures are a key pasted into the workflow file instead of a secret, evals running on every change and quietly costing money, and a check that never blocks.\n\nResearch the current way to do this and fit in with any existing checks. Show me your plan, which files trigger a run, and the pass threshold you'd set, probably the report card's score minus a small margin.\n\nProve it with two test pull requests: one that deliberately breaks the prompt and fails, and one harmless change that passes. Close both unmerged. Record the workflow in our tech stack notes, probably in a file like STACK.md or the README."
  },
  "p-CTX-1": {
   "id": "p-CTX-1",
   "title": "Save each conversation",
   "stage": 3,
   "checks": [
    "CTX-1"
   ],
   "primary": true,
   "body": "Save each AI conversation so a person can leave, come back, and pick up where they stopped.\n\nIf the AI feature isn't a conversation, tell me and stop. Some storage may already exist; check the code, the database, and our tech stack notes, probably in a file like STACK.md or the README. Neon is a good default if the app uses it, and the AI SDK has a recommended way to save and reload chats. The classic failures are trusting a user id the browser sends, losing the reply when someone closes the tab, and one person reading another's chats.\n\nResearch current best practice, then show me the table design in plain English, anything risky, and anything you'd do differently before building. Change the database through migrations, not by hand.\n\nTry it on a preview deployment with test accounts. Show me a conversation surviving a reload and continuing, and a second user failing to read the first user's chat. Record what you chose in our tech stack notes."
  },
  "p-CTX-2": {
   "id": "p-CTX-2",
   "title": "Keep long conversations short",
   "stage": 3,
   "checks": [
    "CTX-2"
   ],
   "primary": true,
   "body": "Keep long conversations working and affordable by trimming or summarizing what's sent to the model, while the full history stays saved. A 100-message conversation should cost about the same per reply as a short one.\n\nCheck how messages reach the model and whether conversations are saved; if not, tell me. Measure first, perhaps from the gateway's logs or traces. Usual approaches are keeping the latest messages, a running summary from a cheaper model, or both, with stable parts first for prompt caching. The classic failures are trimming the saved history instead of what's sent, cutting the instructions or newest message, and splitting a tool call from its result.\n\nResearch current best practice for our model, then show me your measurements, proposed rule, and what you rejected before building.\n\nTry it on a preview deployment. Show me cost per reply early and late in a long conversation, before and after, and that any golden set score held. Record the rule in our tech stack notes, probably in a file like STACK.md or the README."
  },
  "p-CTX-3": {
   "id": "p-CTX-3",
   "title": "Choose what it should know",
   "stage": 3,
   "checks": [
    "CTX-3"
   ],
   "primary": true,
   "body": "Decide which documents and tables the AI may use, and who may see each one. Every source needs an owner who keeps it accurate and an access rule the code actually enforces.\n\nA list may already exist in a file like SOURCES.md, the docs folder, or our tech stack notes, or nowhere yet. Look at the code, the database and its access policies, and the feature's purpose. The classic failures are including secrets or personal data the feature doesn't need, and an access rule like \"only the row's owner\" that nothing enforces. If a rule can't be enforced, leave that source out.\n\nDraft the list with each source's location, owner, access rule, personal data, and update frequency. Show it to me with what you left out and why, and wait for approval.\n\nThen show me the code or database policy that enforces each rule, and flag any gaps. Keep the list wherever fits, and point to it from our tech stack notes, probably in a file like STACK.md or the README."
  },
  "p-CTX-4": {
   "id": "p-CTX-4",
   "title": "Index your data for search by meaning",
   "stage": 3,
   "checks": [
    "CTX-4"
   ],
   "primary": true,
   "body": "Make the app able to search our own data by meaning, so a question finds the passages that answer it, and only passages the person asking may see.\n\nFind out which sources are approved and who may see each. There may be a list in a file like SOURCES.md, the docs, or our tech stack notes; if not, ask me, and index nothing unapproved. The usual approach is to split documents into passages, embed them, and search by meaning, for example with pgvector in Neon, often combined with keyword search. If the sources are small, putting them straight into the prompt may be simpler. The classic failures are passages cut mid-thought, mismatched embedding models, and an index that ignores access rules. Any key that can bypass access rules stays on the server, never in browser code.\n\nResearch best practice for our data, then show me your plan, sample passages, and what you rejected before indexing.\n\nOn a preview or branch, show me real questions, the passages returned, and proof that private passages stay private. Record what you chose, and why, in our tech stack notes."
  },
  "p-CTX-5": {
   "id": "p-CTX-5",
   "title": "Answer from retrieved passages, with sources",
   "stage": 3,
   "checks": [
    "CTX-5"
   ],
   "primary": true,
   "body": "Make the AI answer from passages retrieved from our own data, and say which sources it used, so people can check it.\n\nA search over our data may exist already; if not, tell me. The usual pattern is to search as the person asking, so access rules apply, pass the top passages in a clearly labeled block treated as reference material rather than instructions, and have the model answer only from them or say it doesn't know. The classic failures are trusting citations without checking them in code against what was retrieved, and letting text inside a document act as an instruction.\n\nResearch current best practice, then show me your plan, how citations will look, and what you'd do differently.\n\nTry it on a preview deployment. Show me a question only our docs answer, answered and cited correctly, and one they can't, where it says it doesn't know. Add both to any golden set. Record the change in our tech stack notes, probably in a file like STACK.md or the README."
  },
  "p-CTX-6": {
   "id": "p-CTX-6",
   "title": "Test retrieval on its own",
   "stage": 3,
   "checks": [
    "CTX-6"
   ],
   "primary": true,
   "body": "Test search on its own, so a wrong answer can be traced to bad search or to the model. The target is the right passage in the top five for at least 80% of test questions.\n\nThis needs a search over our data; check it exists. If an eval tool such as promptfoo is already set up, reuse it. Aim for 20 to 30 questions, each paired with the passage that answers it, real ones from traces where possible. The classic failures are questions copied from the passage's own words, which real people never use, and editing the set to flatter a fix.\n\nResearch how teams evaluate retrieval today, and show me the questions and approach first.\n\nThen show me the score, that it holds across two runs, and each miss with what came back instead. Suggest fixes, such as different passage sizes or keyword search, and try one only after I agree. Record the score in our tech stack notes, probably in a file like STACK.md or the README."
  },
  "p-CTX-7": {
   "id": "p-CTX-7",
   "title": "Keep the index fresh",
   "stage": 3,
   "checks": [
    "CTX-7"
   ],
   "primary": true,
   "body": "Keep the search index up to date automatically, so an edited document changes the next answer and a deleted one stops being searchable.\n\nLook at how indexing works now and where each source lives; check our tech stack notes, probably in a file like STACK.md or the README. For data in Neon, re-embedding only the rows that changed, on a schedule or when they're saved, is a good default; for outside sources, a scheduled job is usual. The classic failures are re-embedding everything on every run, deleted documents staying searchable, and switching embedding models partway so old and new passages don't match.\n\nResearch current best practice, then show me, per source, how changes will be detected and how often, before building. Keep every key on the server.\n\nTry it on a preview or branch with test documents. Edit one so a known answer changes, and show the next answer reflects it. Delete one and show its passages are gone. Show me each run's log, and that any retrieval eval held. Record the schedule in our tech stack notes."
  },
  "p-TOOL-1": {
   "id": "p-TOOL-1",
   "title": "Add one tool",
   "stage": 4,
   "checks": [
    "TOOL-1"
   ],
   "primary": true,
   "body": "Give this app's AI feature its first tool: a function our code runs when the model asks, so the AI can look things up instead of guessing.\n\nWork out where the feature guesses, from the code and our tech stack notes (probably a file like STACK.md or the README). It may already have tools; if so, build on them. The AI SDK's tool support is a good default. The classic failures are a vague description, so the model never calls the tool or calls it for everything, and a tool that lets the model choose whose data to read. Start read-only, and take the user from the signed-in session.\n\nResearch current best practice, then propose two or three useful tools and the one you'd start with. Show me before building. Try it on a preview deployment, then show me a question that needs the tool, with a trace showing the call and an answer that uses its result, and one that doesn't. Record what you built, and why, in the stack notes."
  },
  "p-TOOL-2": {
   "id": "p-TOOL-2",
   "title": "Let it take steps, with a limit",
   "stage": 4,
   "checks": [
    "TOOL-2"
   ],
   "primary": true,
   "body": "Let the AI feature chain several tool calls to finish a task, and make sure it always stops after a set number of steps, amount of output, and time.\n\nCheck how the feature calls the model and which tools it has, in the code and our tech stack notes (probably a file like STACK.md or the README). If it has no tools yet, tell me; that comes first. The AI SDK's built-in agent loop is a good default. The classic failures are a loop with no limit that burns money, a limit so high it never matters, and a blank screen when the limit is hit.\n\nResearch current best practice, pick a real task that needs about three steps, and propose limits with your reasoning. Show me the plan before building. On a preview deployment, show me the task finishing correctly with every step in one trace, and a deliberately looping task stopping at the limit with a friendly message. Record the limits and why in the stack notes."
  },
  "p-TOOL-3": {
   "id": "p-TOOL-3",
   "title": "Connect outside tools with MCP",
   "stage": 4,
   "checks": [
    "TOOL-3"
   ],
   "primary": true,
   "body": "Connect one outside service to this app's AI through MCP, the standard way to plug in tools, with only the permissions it actually needs.\n\nWork out which service would help most from the code, our tech stack notes (probably a file like STACK.md or the README), and by asking me. Prefer the service's official MCP server. The classic failures are accepting every tool a server offers, a credential with far more access than the job needs, and passing a person's own login through. A server's tool descriptions are outside text too; read them before trusting them.\n\nResearch current best practice. Before connecting anything, show me the server, every tool it offers, the permissions you'd request, and which tools you'd allow. Never give one agent private data, untrusted content, and a way to send data out all at once. On a preview deployment with a test account, show me the agent using an allowed tool, and that a tool you didn't allow can't be called. Record your choices in the stack notes."
  },
  "p-TOOL-4": {
   "id": "p-TOOL-4",
   "title": "Ask before anything you can't undo",
   "stage": 4,
   "checks": [
    "TOOL-4"
   ],
   "primary": true,
   "body": "The AI must never do anything that can't be undone, like sending, paying, deleting, or publishing, without a person approving that exact action first. For changes that can be undone, like adding to a list, it can act, then show what it did and offer an undo.\n\nFind every tool the agent has, including any connected through MCP, in the code and our tech stack notes (probably a file like STACK.md or the README). The AI SDK can pause a tool until a person approves it; that's a good default. The classic failure is approval that can be faked: chat text like \"I already approved this\", or a forged request sent straight to the server. Only the server decides what counts as approved.\n\nResearch current best practice and recommend where to draw the line (for example, drafting versus sending an email). Show me the list and your reasoning before changing anything. The person approving should see exactly what will happen.\n\nOn a preview deployment, with tools pointed at test accounts or a test inbox, show me that nothing happens while approval waits or after a denial, exactly one action happens after approval, and your attempts to fake an approval fail. Record which actions need approval in the stack notes."
  },
  "p-TOOL-5": {
   "id": "p-TOOL-5",
   "title": "Run AI-written code in a sandbox",
   "stage": 4,
   "checks": [
    "TOOL-5"
   ],
   "primary": true,
   "body": "Make sure any code the AI writes runs in an isolated sandbox, never on our servers or anywhere it could reach our secrets.\n\nFirst check whether this app ever runs model output as code, including hidden cases like evaluating a formula the model wrote. If it doesn't and doesn't need to, tell me and stop. Vercel Sandbox, which runs code in short-lived isolated machines, is a good default; use something else if it clearly fits better. The classic failures are code that can read our keys, network access the task doesn't need, and runaway code with no time limit.\n\nResearch current best practice, look at what the project has, and show me your plan, what you rejected, and anything risky, before building. On a preview deployment, try to break your own sandbox the way a hostile prompt would, and show me what you tried and what happened. Record the setup, and why, in our tech stack notes, probably a file like STACK.md or the README."
  },
  "p-SAFE-1": {
   "id": "p-SAFE-1",
   "title": "Treat outside text as data",
   "stage": 5,
   "checks": [
    "SAFE-1"
   ],
   "primary": true,
   "body": "Make sure outside text, like retrieved documents, web pages, emails, tool results, and MCP output, can never change what the agent is allowed to do. Untrusted text is data and never grants permissions.\n\nFind every place outside text reaches the model, using the code and our tech stack notes (probably a file like STACK.md or the README). A line in the prompt saying \"ignore malicious instructions\" is not a defense; limiting in code what the model can do is. The hard line: no single agent gets private data, untrusted content, and a way to send data out, whether a send tool, a link, or an image. Approval for irreversible actions may or may not exist yet; check.\n\nResearch current best practice, then show me each agent's data, inputs, and ways out, and what you'd change, before changing it. On a preview deployment with test accounts, try hard to break your own defenses the way an attacker would. Show me what you tried and what happened, and record the defenses in the stack notes."
  },
  "p-SAFE-2": {
   "id": "p-SAFE-2",
   "title": "Keep personal data out of prompts and logs",
   "stage": 5,
   "checks": [
    "SAFE-2"
   ],
   "primary": true,
   "body": "Send the model only the personal data each task truly needs, and keep personal data out of our traces, logs, and eval files.\n\nFind where personal data enters the AI feature and where it ends up: prompts, tool results, traces (perhaps in PostHog), logs, and saved test cases. Our tech stack notes, probably a file like STACK.md or the README, may help. Tracing tools often record full inputs and outputs by default, the classic way private details leak. Identify people in traces by an internal id, never their email.\n\nResearch current best practice for minimizing and masking personal data. Before changing anything, show me what personal data each task sees, what it needs, and how you'd mask the rest. Try it on a preview deployment with a test user whose details are made up and unique, then search everywhere that data could land and show me it isn't there. Record what is masked, and why, in the stack notes."
  },
  "p-SAFE-3": {
   "id": "p-SAFE-3",
   "title": "Check answers before using them",
   "stage": 5,
   "checks": [
    "SAFE-3"
   ],
   "primary": true,
   "body": "Check every AI answer before it's shown to a person or acted on by our code, and block unsafe links and content.\n\nFind every place model output is displayed, read by code, or turned into an action, and how it's checked today, using the code and our tech stack notes (probably a file like STACK.md or the README). A schema check is the usual guard where code reads the answer; a moderation model helps for public text. The classic failures are rendering the model's raw HTML or markdown, where an image or link can quietly carry data to an attacker, and running model output as code or a database query. Never do either.\n\nResearch current best practice and show me where each check should go, and what you'd skip, before building. On a preview deployment with test accounts, try to get a malicious link, a leaking image, or malformed data past your own checks. Show me what you tried and what happened, and record the checks in the stack notes."
  },
  "p-SAFE-4": {
   "id": "p-SAFE-4",
   "title": "Cap usage per person",
   "stage": 5,
   "checks": [
    "SAFE-4"
   ],
   "primary": true,
   "body": "Give each person a limit on requests and AI usage, so one user or bot can't run up a big bill.\n\nFind every route that calls a model and how people sign in, using the code and our tech stack notes (probably a file like STACK.md or the README). On Vercel, Vercel Firewall's rate limiting and bot protection are a good default, plus our own count of each person's token use. The classic failures are taking the user's id from the request instead of the server session, checking limits after calling the model, and calls with no cap on output, steps, or time.\n\nResearch current best practice and propose per-person limits with your reasoning. Show me them before building. On a preview deployment with test accounts, try to get past your own limits like an abusive user or script would. Show me what you tried, that blocked requests never reached the model, and that people over the limit see a friendly message saying when it resets. Record the limits in the stack notes."
  },
  "p-COST-1": {
   "id": "p-COST-1",
   "title": "Set a budget with alerts",
   "stage": 6,
   "checks": [
    "COST-1"
   ],
   "primary": true,
   "body": "Put a monthly spending limit on this app's AI usage, with warnings that reach a real person before it's hit.\n\nWork out how the app pays for model calls, from the code and our tech stack notes (probably a file like STACK.md or the README). If calls go through Vercel AI Gateway, its budgets and alerts are a good default. Check recent actual spend before picking a number. The classic surprises: budgets are often soft caps, so the call that crosses the line still runs, and spend on our own provider keys may not count toward a gateway budget. Per-person limits may or may not exist yet; check, because a budget doesn't replace them.\n\nResearch the current options, then propose a budget, alert levels, and who gets the alerts. Show me before setting anything. Make sure the app shows a friendly message, not a crash, when the budget stops a call, and try that somewhere safe. Show me the budget and alerts in the settings, and record them in the stack notes."
  },
  "p-COST-2": {
   "id": "p-COST-2",
   "title": "Cache repeated prompts",
   "stage": 6,
   "checks": [
    "COST-2"
   ],
   "primary": true,
   "body": "Make the long, repeated parts of this app's prompts get cached by the model provider, so repeat calls cost less and start faster.\n\nLook at each AI call, its model and provider, and what goes into its prompt, using the code and our tech stack notes (probably a file like STACK.md or the README). Caching works on the repeated beginning of a prompt, so stable parts like instructions, tool definitions, and reference text go first, and changing parts like the user's message, retrieved passages, and dates go last. Some providers cache automatically; some need it switched on. The classic failure is a timestamp or user id near the top that quietly breaks the cache on every call.\n\nResearch how caching works for our providers, and show me what you'd reorder before changing anything. Try it on a branch or preview deployment. Show me cached tokens on a repeated call, the cost difference, and that eval results didn't change, if we have evals. Record what is cached in the stack notes."
  },
  "p-COST-3": {
   "id": "p-COST-3",
   "title": "Fall back when a model fails",
   "stage": 6,
   "checks": [
    "COST-3"
   ],
   "primary": true,
   "body": "Make this app's AI feature keep working when its main model or provider is down, by falling back to a backup automatically.\n\nFind where models are chosen, using the code and our tech stack notes (probably a file like STACK.md or the README). If calls go through Vercel AI Gateway, its built-in fallbacks are a good default. The classic failures are a backup that answers noticeably worse, a backup from the same company that fails at the same time, retrying a broken model until the person gives up, and our own retries stacked on the gateway's. If there's an eval report card, pick backups that pass the golden set; if not, tell me how you'd check quality instead.\n\nResearch current best practice, then propose the backup models and their order, with reasons, before changing code. On a preview deployment, make the main model fail on purpose and show me the feature still answers, with logs or traces showing which model replied. Record the fallback order in the stack notes."
  },
  "p-COST-4": {
   "id": "p-COST-4",
   "title": "Use the cheapest model that passes",
   "stage": 6,
   "checks": [
    "COST-4"
   ],
   "primary": true,
   "body": "Make each AI task in this app use the cheapest model that still passes its evals.\n\nList every place the app calls a model and what each call is for, using the code and our tech stack notes (probably a file like STACK.md or the README). This depends on evals: a golden set, an eval runner (promptfoo is our usual default), and perhaps a report card comparing models. Check they exist. If not, tell me and stop, because switching on price alone is how quality quietly drops. Simple jobs like sorting or extraction often pass on much smaller models. Never lower the pass mark to fit a cheap model.\n\nResearch current models and prices, run cheaper candidates for each task through the evals, and show me a table of score, cost per answer, and speed before switching anything. Make changes on a branch or preview deployment. Then show me cost per answer and eval score, before and after. Record each task's model and the reason in the stack notes."
  },
  "p-LIVE-1": {
   "id": "p-LIVE-1",
   "title": "Read real traces every week",
   "stage": 7,
   "checks": [
    "LIVE-1"
   ],
   "primary": true,
   "body": "Set up a weekly habit of reading real conversations from this app's AI feature and naming the ways it fails, with counts.\n\nFind where conversations are traced (PostHog is our usual default) and check that real traces exist; if not, tell me. Error analysis is the habit teams skip and value most: read about a hundred real traces, note by hand why the bad ones failed, and only then group the notes into failure types. The classic failures are inventing categories before reading and letting a model do the labeling. A person labels; you organize.\n\nThen propose how you'd sample (mostly random, plus every complaint and error), mask personal data, and lay out traces so I can label at least thirty quickly. Show me before pulling anything, then stop and let me label. Afterwards, show me the top three failure types with counts and an example trace each, saved as a dated file, and make next week's pull easy to repeat. Record the routine and owner in our tech stack notes (probably a file like STACK.md or the README)."
  },
  "p-LIVE-2": {
   "id": "p-LIVE-2",
   "title": "Collect feedback on answers",
   "stage": 7,
   "checks": [
    "LIVE-2"
   ],
   "primary": true,
   "body": "Let people rate this app's AI answers with a thumbs up or down, each rating tied to the exact answer and trace it's about.\n\nFind where answers are shown and how calls are traced, using the code and our tech stack notes (probably a file like STACK.md or the README). If we trace in PostHog, its ready-made thumbs survey, which links ratings to AI traces, is a good default. The classic failures are a rating that can't be matched to its answer, one rating for a whole conversation, a pop-up that interrupts people, and personal data typed into a comment going straight into analytics.\n\nResearch current best practice and show me where the rating would appear and how each links to its trace, before building. An optional short \"what was wrong?\" after a thumbs-down is worth adding. On a preview deployment with a test account, give an answer a thumbs-down with a comment, and show me it attached to the right trace. Record the setup in the stack notes."
  },
  "p-LIVE-3": {
   "id": "p-LIVE-3",
   "title": "Dashboard cost, speed, and errors",
   "stage": 7,
   "checks": [
    "LIVE-3"
   ],
   "primary": true,
   "body": "Build one dashboard that shows what this app's AI feature costs, how fast it is, how often it fails, and how people rate it.\n\nCheck where traces and ratings live today. PostHog is our usual default and records cost, speed, and tokens per call; confirm real data is there and calls are tied to a user id, never an email. Ratings may or may not be set up yet; if not, leave room for them. The classic failures are a dashboard with so many charts nobody reads it, and cost numbers that don't match the actual bill.\n\nResearch current best practice, then propose a short list of charts and one or two alerts for spikes in cost or errors. Show me before building. Then show me evidence we can both check: yesterday's AI cost from the dashboard next to what the gateway or provider actually billed, and yesterday's slowest response opened to its trace. Record the dashboard link in our tech stack notes (probably a file like STACK.md or the README)."
  },
  "p-LIVE-4": {
   "id": "p-LIVE-4",
   "title": "Score live answers automatically",
   "stage": 7,
   "checks": [
    "LIVE-4"
   ],
   "primary": true,
   "body": "Grade a sample of this app's real AI answers automatically every day, with the same graders as our golden set, so a drop in quality shows up within a day.\n\nCheck what exists: perhaps a golden set and graders in an eval tool like promptfoo, and traces in a tool like PostHog, which can run evaluations on live traffic. If there are no golden-set graders yet, tell me and stop. The classic failure is writing new, looser rubrics for live traffic, so live and offline scores mean different things. Judges should give pass or fail, use a different model from the one being graded, and never see unmasked personal data.\n\nResearch what our tools support, then show me which graders become code checks and which become model judges, how much traffic to sample, and the alert you'd set. On a preview or test project, prove it: use a deliberately worse prompt, show the pass rate drop, then switch back. Record the setup in our tech stack notes (probably a file like STACK.md or the README)."
  },
  "p-BETTER-1": {
   "id": "p-BETTER-1",
   "title": "Turn failures into test cases",
   "stage": 8,
   "checks": [
    "BETTER-1"
   ],
   "primary": true,
   "body": "Turn this app's real bad answers into test cases in our golden set, so a bug we've fixed can never quietly come back.\n\nFind where bad answers show up: thumbs-down ratings, failed live evaluations, weekly error analysis notes, or complaints. Some may not exist yet; use what's there and tell me what's missing. Find the golden set too, probably in the eval tool's config (promptfoo is our usual default) or a data folder. The classic failures are letting a model decide the right answer, copying real personal data into the repo, and loosening old cases to raise the score.\n\nShow me the bad answers with duplicates grouped. For each one I choose, you may draft what a good answer must contain or avoid, but I approve it. Work on a branch. Show me one fixed bug: undo the fix and its new case fails, restore it and it passes. Record how failures become test cases, and who approves them, in our tech stack notes (probably a file like STACK.md or the README)."
  },
  "p-BETTER-2": {
   "id": "p-BETTER-2",
   "title": "Fine-tune only when prompting stalls",
   "stage": 8,
   "checks": [
    "BETTER-2"
   ],
   "primary": true,
   "body": "Fine-tune a model on this app's best examples, but only once better prompts and retrieval have stopped improving our evals.\n\nFirst check it's worth it. Look for the golden set, eval history, and report card, perhaps in our tech stack notes (probably a file like STACK.md or the README). If there's no eval history, or scores are still rising, tell me and stop. Fine-tuning helps with style, format, and consistent behavior; missing knowledge is a retrieval problem. Together AI, fine-tuning an open-weight model, is a good default; OpenAI fine-tuning is closed to new organizations. The classic failures are training on golden-set examples, which makes the eval meaningless, unmasked personal data, and a paid endpoint left running.\n\nResearch current best practice, and before training, show me the case for it, sample examples drawn from real passing answers, and the base model you'd pick and why. Compare the result with the current model on the golden set for score, cost, and speed; switch only if it wins. Record the result in the stack notes."
  },
  "p-BETTER-3": {
   "id": "p-BETTER-3",
   "title": "Collect preference pairs",
   "stage": 8,
   "checks": [
    "BETTER-3"
   ],
   "primary": true,
   "body": "Save a better and a worse version of an answer whenever a person picks between answers or edits one, so the app builds up preference data for training later.\n\nFind every place a person can regenerate, choose between, or edit an AI answer. If there are none, tell me; you might propose a natural spot, like editing a draft before sending, but ask first. Our tech stack notes (probably a file like STACK.md or the README) may say where conversations and traces live. Pairs can later train a model on chosen versus rejected answers, for example on Together AI. The classic failures are counting a thumbs-down alone as a pair, saving typo fixes, losing the link to the question and trace, and exporting personal data unmasked.\n\nBefore building, research current practice and show me how you'd store and export the pairs, with each person able to see only their own. On a preview deployment with a test account, show me saved pairs linked to their traces and an export file. Record the setup in the stack notes."
  },
  "p-BETTER-4": {
   "id": "p-BETTER-4",
   "title": "Distill into a smaller model",
   "stage": 8,
   "checks": [
    "BETTER-4"
   ],
   "primary": true,
   "body": "Train a small, cheap model to copy the large model's best answers for this app, and check it passes the same evals.\n\nFirst check it's worth it. Look at the current model's cost and speed, the golden set, and eval history, perhaps in a dashboard or our tech stack notes (probably a file like STACK.md or the README). If cost isn't a real problem, or prompt and retrieval changes still raise scores, tell me and stop. Confirm the large model's terms allow training on its outputs. Together AI, fine-tuning a small open-weight model, is a good default. The classic failures are training on golden-set examples or answers that failed the graders, unmasked personal data, and a paid endpoint left running.\n\nResearch current best practice, and before training, show me where the inputs come from, a sample of answers you'd keep, and the small model you'd pick. Compare it with the large model on the golden set for score, cost per answer, and speed; switch only if it holds up. Record the result in the stack notes."
  }
 },
 "legend": {
  "req": "a request",
  "dep": "context",
  "fb": "improving"
 }
}