Claude for Developers

Lesson 3 of 14

System prompts that actually steer

The system prompt is where you stop being a user of Claude and start being the designer of a Claude-powered system. It is the job description: who the model is, what it is doing, what the constraints are, and what good output looks like. Most quality problems attributed to "the model" are actually system prompts that under-specify, over-shout, or contradict themselves.

Where it goes and what belongs there

python
response = client.messages.create(
    model="claude-opus-5",
    max_tokens=16000,
    system=(
        "You are a support engineer for Acme's payments API. "
        "You answer questions from Acme's own developers, not end customers. "
        "Ground every answer in the provided documentation; when the docs do not cover something, say so plainly rather than guessing. "
        "Answers are concise: lead with the fix, then the explanation. Code samples are runnable curl or Python."
    ),
    messages=[{"role": "user", "content": "Why am I getting 402 on /charges?"}],
)

A working system prompt usually carries four ingredients, and each earns its place:

  • Role and audience. "Support engineer answering Acme's own developers" changes vocabulary, assumed knowledge, and tone in one sentence — cheaper than fifty style rules.
  • Task and grounding rules. What the model is doing and what sources it may claim things from. The instruction to say so when the docs don't cover it is the single highest-value line in most production prompts: it converts hallucination into an honest gap.
  • Output contract. Shape, length, format. If a machine parses the output, you will graduate to structured outputs in lesson 5 — but even for human-read output, "lead with the fix" beats hoping.
  • Boundaries. What the model must not do — refund promises, legal advice, speculation about unreleased features.

Structure beats prose

For prompts beyond a paragraph, structure is not cosmetic — it is how the model (and the next engineer) parses your intent. XML-style tags are the house convention because they name each section and delimit it unambiguously, which also lets you refer to sections ("follow the rules in <escalation_policy>"):

text
<role>
You are a code reviewer for a Python data-engineering team.
</role>

<review_rules>
- Report every genuine bug, including ones you are unsure about; a later pass filters.
- For each finding: file, line, severity, one-sentence why, suggested fix.
- Style nits only when they hide a correctness risk.
</review_rules>

<output_format>
Markdown. One `## Finding` section per issue, ordered by severity.
</output_format>

The same principle governs data: when a user turn mixes instructions with pasted material, wrap the material — <document>…</document> — so the model can tell content to analyse from commands to follow. This is also your first prompt-injection defence, which the final lesson returns to.

Modern Claude is literal — write like it

Current Claude models follow instructions much more faithfully than early LLMs did, and prompts written to overcome old reluctance now overshoot. The aggressive style — CRITICAL: You MUST ALWAYS use the search tool — was scaffolding for models that under-triggered; on today's models it produces a system that searches when it obviously shouldn't. The calibration rules:

  • State conditions, not slogans. "Use the search tool when the answer depends on information not present in the conversation" outperforms "ALWAYS search".
  • Say what to do, not only what to avoid. "Answers are concise: lead with the fix" beats "don't be verbose" — prohibitions leave a vacuum where the desired behaviour should be.
  • Don't silently expect generalisation. If a formatting rule applies to every section, say "every section" — the model will not quietly extend an instruction beyond its literal scope, and that precision is a feature you can build on.
  • Show one example of great output. A single positive example is worth a page of adjectives.

Keep the system prompt frozen

Here is the rule that will matter enormously when you reach the caching lesson: the system prompt should be byte-identical across requests. No timestamps, no user names, no per-request flags interpolated into it. Dynamic context — today's date, the user's plan, feature toggles — belongs later in the request, injected as message content, where it invalidates nothing. Design this way from the start and caching will be a one-line change instead of a refactor.

What to take into the next lesson

Role, grounding, contract, boundaries; structure with tags; calibrated rather than shouted; frozen rather than interpolated. That is the durable core of system-prompt craft. The next lesson layers on the techniques that operate within that frame — examples, reasoning controls, and the handful of prompting moves that measurably change output quality.

← Previous