Study guides / CCDV-F / Domain 4

Prompt & Context Engineering · Lesson 1 of 4

4.1 - System Prompts with Explicit Criteria

Write system prompts that state the actual decision criteria, not just a general goal, to cut down on inconsistent judgment calls.

A system prompt that states a goal ("flag inappropriate messages", "summarise professionally", "be conservative") without the criteria behind it leaves Claude to infer a threshold on every single call, and an inferred threshold drifts between similar cases — not because the model is unreliable, but because you never actually told it where the line is. Two requests that a human would judge identically can get different verdicts because the prompt left the actual decision rule unstated.

The fix isn't a longer, more earnest-sounding prompt. It's explicit, categorical criteria: what specifically counts, what specifically doesn't, and what to do with the cases in between. Compare:

Wrong: "Review this code. Be conservative. Only report high-confidence findings."

Right: "Report: bugs, security vulnerabilities, logic errors.
Skip: style preferences, formatting choices, local naming patterns.
Flag a comment only when the claimed behaviour contradicts the actual code behaviour."

The first sentence gives the model nothing to apply consistently — "conservative" and "high-confidence" are both undefined thresholds that the model has to invent for itself, and it will invent a slightly different one each time depending on the surrounding context. The second gives concrete report/skip categories and a specific, checkable trigger condition. It's also considerably shorter. Length was never the lever — specificity is.

Precision vs. recall: pick a side on purpose

Almost every classification-shaped system prompt (flagging, extraction, routing, moderation) is making a precision/recall trade-off whether you name it or not. Naming it is what makes it a design decision instead of an accident. "When in doubt, don't flag" biases toward precision (fewer false positives, more missed cases); "when in doubt, flag for review" biases toward recall (fewer missed cases, more false alarms that a human has to clear). Neither is universally correct — a spam filter and a fraud-detection trigger want opposite defaults — but leaving the choice implicit means the model picks inconsistently on a case-by-case basis, which is worse than picking either side deliberately.

Say the trade-off out loud in the prompt: name at least one borderline case explicitly and state which way to err on it. "Sarcastic complaints framed as jokes: treat as a genuine complaint, not as banter" resolves an entire category of inconsistency that no amount of general wording about being "careful" or "thoughtful" would touch.

The false-positive trust problem

When a system prompt defines multiple output categories (e.g. a code-review tool that flags "security", "correctness", and "documentation" issues, or a support triage prompt that tags "billing", "technical", and "account" tickets), a high false-positive rate in one category poisons trust in all of them for the humans reading the output. If your documentation-mismatch findings are wrong 40% of the time, reviewers stop reading your security findings too, even if those run at 98% precision — trust isn't scoped per-category, it's scoped per-source.

The counterintuitive fix: when a category's false-positive rate is out of line, temporarily disable that category in the system prompt (stop asking Claude to report on it at all) while you rework its criteria with concrete examples, rather than leaving it half-broken alongside categories that already work. Trust in the working categories recovers immediately; you re-enable the reworked category once its precision improves. This is a system-prompt maintenance pattern the exam expects you to recognise, not just a one-off fix.

Calibrate with examples, not adjectives

When criteria involve severity or a graded scale, a prose description of each level ("critical: could cause data loss"; "minor: a style nit") still forces the model to interpret where a given real case lands. A concrete example per level is more decidable: showing an actual unsanitised-SQL-query snippet labelled "critical" and an inconsistent-variable-naming snippet labelled "minor" removes the interpretation step entirely — the model pattern-matches against a known instance instead of reasoning about the definition of "could cause data loss" from scratch each time. This is the same principle as few-shot prompting (4.3), applied specifically to calibrating a threshold rather than a format.

Common exam distractor

"Only report high-confidence findings" reads like sound engineering — filter by confidence, keep the strong signals — but a model's self-reported confidence is poorly calibrated: it can be confidently wrong and hesitantly right. Confidence-based routing (sending low-confidence findings to a human) is a legitimate secondary technique once explicit criteria already exist; it is never a substitute for defining what counts as a valid finding in the first place. The exam tests this ordering directly.

Key concept

If you can't write down the criteria a human reviewer would actually use to judge a borderline case, the model can't reliably infer it either — the ambiguity in the prompt becomes inconsistency in the output. Explicit criteria beat prompt length every time; a short prompt with a decidable rule outperforms a long prompt that's still vague about the actual threshold.

Exam traps

Practice question

A content-moderation feature's system prompt says "flag messages that are inappropriate." Reviewers notice inconsistent flagging on borderline sarcastic comments. What's the most effective fix?

  • A Replace "inappropriate" with a longer synonym-rich description of bad content in general terms.

    More general wording, even if longer, doesn't resolve the actual ambiguity around the borderline case (sarcasm) that's causing inconsistency.

  • B Add explicit criteria naming the categories that count, and state how to handle sarcastic/ambiguous cases specifically. Correct

    This directly targets the source of inconsistency - an unstated threshold on a known borderline case - with an explicit, decidable rule.

  • C Lower the model's temperature to reduce randomness.

    Inconsistency here comes from ambiguous criteria, not from sampling randomness - this doesn't address the actual cause.

  • D Switch to a larger context window.

    Context window size is unrelated to how clearly the moderation criteria are specified.

Build exercise: Rewrite a vague instruction with explicit criteria and measure the difference

Beginner · 30 minutes

You'll practice:

  1. Take a vague instruction (e.g. "flag risky support requests") and rewrite it naming at least three concrete criteria a human reviewer would actually check, including how to handle one specific borderline case (e.g. a customer joking about cancelling their account).

    Naming the borderline case explicitly is usually where the real ambiguity lived - general categories alone don't resolve it.

    You should see: A rewritten instruction specific enough that two different people applying it by hand would make the same call on the borderline case.

    Hints
    1. What would a human reviewer actually check line by line before deciding? Write that list down first, then turn it into the prompt.
    2. Structure it as report/skip categories plus one explicit rule for the borderline case, the same shape as the SQL-injection vs. style-nit example in the lesson.
    3. const criteria = `Flag: explicit cancellation requests, threats of chargeback, repeated unresolved complaints.\nDo not flag: general product feedback, jokes or hyperbole (\"I'll cancel if this happens again\" said in a positive-toned message) unless paired with an explicit action request.\nWhen genuinely unclear, flag for human review rather than silently skip.`;
  2. Send the vague version and the explicit-criteria version as separate system prompts against the same 5 borderline test messages using the Messages API, and log each response.

    Seeing both versions run against identical input is the only way to observe the actual behaviour difference - reasoning about it in the abstract hides how much the vague version drifts.

    You should see: Two sets of five classifications. The vague-prompt set should show at least one inconsistency (a case classified differently from a near-identical case); the explicit-criteria set should be uniform.

    Hints
    1. The system prompt goes in the top-level `system` parameter of messages.create, not as a user turn - keep the borderline test messages identical across both runs so it's a fair comparison.
    2. Loop over the same array of 5 message strings for both system prompts and store (systemVersion, input, output) tuples so you can diff them side by side afterward.
    3. const testMessages = [/* 5 borderline strings */];
      for (const sys of [vagueSystemPrompt, explicitSystemPrompt]) {
        for (const msg of testMessages) {
          const res = await client.messages.create({
            model: "claude-sonnet-5",
            max_tokens: 200,
            system: sys,
            messages: [{ role: "user", content: msg }],
          });
          results.push({ sys: sys === vagueSystemPrompt ? "vague" : "explicit", msg, out: res.content[0].text });
        }
      }
  3. Run the explicit-criteria version 3 times against the same 5 messages to check for run-to-run consistency, not just single-run correctness.

    A prompt can look fine on one run and still drift across repeated calls if the criteria aren't specific enough - consistency across repeats is the real test the exam cares about.

    You should see: The same classification for each message across all 3 runs. Any message that flips between runs still has residual ambiguity worth tightening further.

    Hints
    1. What would you compare to detect drift - the raw text, or a normalised label extracted from it?
    2. Extract a single label (e.g. "flag" / "no_flag") from each response with a small parsing step, then group by message and check that all 3 runs agree.
    3. const runs = await Promise.all([1, 2, 3].map(() => classifyAll(explicitSystemPrompt, testMessages)));
      for (let i = 0; i < testMessages.length; i++) {
        const labels = runs.map(r => r[i].label);
        console.log(testMessages[i], labels, new Set(labels).size === 1 ? "CONSISTENT" : "DRIFTED");
      }
  4. Add a second category to the system prompt (e.g. "tone issues" alongside "cancellation risk") and deliberately leave its criteria vague, then observe whether reviewers (or your own spot-check) start distrusting the well-defined category too.

    This recreates the false-positive trust problem in miniature - a noisy category sitting next to a solid one, in the same prompt, is the scenario the exam presents.

    You should see: The vague "tone issues" category produces inconsistent or clearly wrong flags on your test set, while "cancellation risk" stays accurate - demonstrating the two categories are independently reliable even though they'd read as equally untrustworthy to a downstream reviewer skimming both outputs together.

    Hints
    1. You don't need real reviewer feedback for this - just measure the false-positive rate of each category separately on your test set.
    2. Tag your test set with the expected category outcome ahead of time, then compute precision per category the same way you would for the code-review scenario in the lesson.
    3. const categories = { cancellation_risk: { tp: 0, fp: 0 }, tone_issues: { tp: 0, fp: 0 } };
      // after tagging outputs against expected labels:
      const precision = c => c.tp / (c.tp + c.fp || 1);
      console.log("cancellation_risk precision:", precision(categories.cancellation_risk));
      console.log("tone_issues precision:", precision(categories.tone_issues));

Sources