# How to Write an AI Agent System Prompt

- Tool: Any LLM or framework
- Difficulty: Intermediate
- Time required: 30-40 min
- Compatibility: Python 3.10+, an Anthropic API key, works with any tool-calling model or agent framework
- Last updated: November 2026

## TL;DR

A good AI agent system prompt has five parts: role and scope, tool-use rules, escalation triggers, tone and format, and a couple of few-shot examples fixing real failures. This guide writes one for a dental clinic's scheduling agent that books, reschedules, and escalates emergencies to a human, then tests it with a Python eval script instead of manual chats. Budget 30-40 minutes and a few cents of API usage.

## What you'll build

You'll write the system prompt for a scheduling agent at Riverside Dental. It books, reschedules, and cancels appointments through three tools, and the moment a message sounds like a dental emergency, it stops touching the calendar and hands off to a human on call. That handoff rule is the part most tutorials skip, and it's the part that actually matters once the agent is live.

The prompt is built in five labeled sections: role and scope (what the agent is for, and just as important, what it isn't), tool-use rules (the order things must happen in), escalation triggers (the exact phrases and situations that stop the agent cold), tone and format (how it talks and how long its replies are), and two or three few-shot examples that each fix one real failure you observed in testing. Sections matter more than word count. A model reading a wall of undifferentiated prose will blend your tone guidance with your safety rules; a model reading clearly delimited sections treats them as separate, enforceable policies.

This structure is framework-agnostic. The same five sections go into ClaudeAgentOptions.system_prompt in the Claude Agent SDK, the systemMessage option on an n8n AI Agent node, or the instructions field of any OpenAI Agents SDK or LangGraph agent. What changes between frameworks is where the string lives, not how you write it.

## Before you start

- An Anthropic API key (ANTHROPIC_API_KEY) with available credits
- Python 3.10+ (pip install anthropic)
- A list of the tools your agent will call, with a one-line description of what each one does
- 5-10 real or realistic user messages covering both normal requests and edge cases

## Step-by-step guide

### 1. Write the role and scope in one paragraph

Start with a single paragraph that says exactly what the agent is for and, just as important, what it is not for. A scope sentence that only states the positive case ('you help patients schedule appointments') leaves the model free to improvise when a patient asks something adjacent, like a billing question or a symptom question. Naming the boundary explicitly gives the model a clean reason to decline or redirect instead of guessing.

```
<role>
You are the scheduling assistant for Riverside Dental. You book, reschedule,
and cancel appointments using the tools below. You do not give medical advice,
quote prices, or discuss insurance coverage. For those, tell the patient a
front-desk team member will follow up.
</role>
```

**Expected result:** A 3-5 sentence block that reads naturally and draws a clear line around the agent's job.

### 2. Add tool-use rules that force a sequence

List the order operations must happen in, as bullet points, not prose. The point is to stop the model from calling book_appointment before it has actually confirmed a slot is free, and from telling the patient something is done before the tool call that would make it true. Write these as commands aimed at the model's behavior, not as documentation of what the tools do; the tool descriptions in your schema already cover that.

```
<tool_rules>
- Always call check_availability before offering a time slot.
- Never tell the patient an appointment is booked, changed, or canceled
  unless the corresponding tool call has already returned success.
- If check_availability returns no slots within 7 days, say so plainly
  instead of inventing an alternative.
</tool_rules>
```

**Expected result:** A short, scannable rule list that a new engineer could use to predict the agent's tool-call order.

### 3. Define hard escalation triggers

Enumerate the exact situations that must stop the agent from touching the calendar and route to a human, and name the handoff tool explicitly. Vague guidance like 'use good judgment with emergencies' produces inconsistent behavior; a model is much more reliable when the trigger list is concrete enough to pattern-match against incoming text.

```
<escalation>
Call escalate_to_human immediately, before any other tool, if the patient
mentions: severe pain, swelling, bleeding that won't stop, a broken or
knocked-out tooth, or a dental emergency in any other words. Also escalate
if the patient is angry about a prior visit or asks for a refund.
</escalation>
```

**Expected result:** A list specific enough that you could grade a transcript against it and get the same answer every time.

### 4. Set tone, format, and length constraints

State how the agent talks: sentence length, whether it signs off, how it reads back a confirmed appointment. Without this section, the model defaults to a generic, slightly over-polite chatbot voice that doesn't match a dental office's phone or chat channel, and it tends to pad every reply with pleasantries that cost tokens and patience.

```
<tone>
Keep replies under 2 sentences unless reading back a confirmed appointment.
When confirming, state the date, time, and provider name plainly: 'You're
booked with Dr. Patel on Thursday at 2:00 PM.' No sign-offs, no emoji.
</tone>
```

**Expected result:** Replies in testing come back short and consistent instead of varying wildly in length and formality.

### 5. Add two or three few-shot examples for your worst failure mode

Pull these from real testing, not imagination. If the agent once told a patient 'you're all set' without having called reschedule_appointment, that's the example to write, because it's the exact pattern the model will otherwise repeat. Two or three examples is usually enough; past that, you're mostly adding tokens without changing behavior.

```
<examples>
User: can you just move my Tuesday appointment to Wednesday?
Wrong: Done, see you Wednesday!
Right: [call check_availability] [call reschedule_appointment] Then: You're
moved to Wednesday at 10:00 AM with Dr. Patel.
</examples>
```

**Expected result:** A short dialogue pair the model can pattern-match against the exact mistake it was making.

### 6. Assemble the full system prompt with labeled sections

Concatenate the five blocks in order: role and scope first, tool rules and escalation triggers next since those carry the most consequence, tone after, examples last. Section tags like <role> and <escalation> aren't decorative; they give the model clear boundaries to anchor each policy to, which matters more as the prompt grows past a page.

```
SYSTEM_PROMPT = (
    ROLE_SECTION + "\n\n" + TOOL_RULES_SECTION + "\n\n"
    + ESCALATION_SECTION + "\n\n" + TONE_SECTION + "\n\n" + EXAMPLES_SECTION
)
```

**Expected result:** One assembled string, a few hundred to a thousand tokens, ready to pass as the system prompt.

### 7. Test it with an eval script, not a few manual chats

Write a loop that sends a handful of representative messages, including at least one that should trigger escalation and one that should trigger a normal tool call, through the real API with your tools attached. Check which tool, if any, the model called, and assert it matches what you expect. A handful of scripted cases catches regressions a manual chat session won't, especially after you edit the prompt later.

```
import anthropic
client = anthropic.Anthropic()

def run_case(user_msg):
    resp = client.messages.create(
        model="claude-sonnet-5-5", max_tokens=512,
        system=SYSTEM_PROMPT, tools=TOOLS,
        messages=[{"role": "user", "content": user_msg}],
    )
    calls = [b.name for b in resp.content if b.type == "tool_use"]
    return calls
```

**Expected result:** A printed PASS/FAIL per test case, including one confirming the emergency message called escalate_to_human and nothing else.

## Complete code example

File: `system_prompt_eval.py`

```python
"""Build and eval-test the Riverside Dental scheduling agent's system prompt.
Run: python system_prompt_eval.py (pip install anthropic; set ANTHROPIC_API_KEY)
"""
import anthropic

ROLE = (
    "<role>\nYou are the scheduling assistant for Riverside Dental. You book, "
    "reschedule, and cancel appointments using the tools below. You do not "
    "give medical advice, quote prices, or discuss insurance coverage. For "
    "those, tell the patient a front-desk team member will follow up.\n</role>"
)

TOOL_RULES = (
    "<tool_rules>\n- Always call check_availability before offering a time slot.\n"
    "- Never tell the patient an appointment is booked, changed, or canceled "
    "unless the corresponding tool call has already returned success.\n"
    "- If check_availability returns no slots within 7 days, say so plainly "
    "instead of inventing an alternative.\n</tool_rules>"
)

ESCALATION = (
    "<escalation>\nCall escalate_to_human immediately, before any other tool, "
    "if the patient mentions: severe pain, swelling, bleeding that won't stop, "
    "a broken or knocked-out tooth, or a dental emergency in any other words. "
    "Also escalate if the patient is angry about a prior visit or asks for a "
    "refund.\n</escalation>"
)

TONE = (
    "<tone>\nKeep replies under 2 sentences unless reading back a confirmed "
    "appointment. When confirming, state the date, time, and provider name "
    "plainly: 'You're booked with Dr. Patel on Thursday at 2:00 PM.' No "
    "sign-offs, no emoji.\n</tone>"
)

EXAMPLES = (
    "<examples>\nUser: can you just move my Tuesday appointment to Wednesday?\n"
    "Wrong: Done, see you Wednesday!\nRight: [call check_availability] "
    "[call reschedule_appointment] Then: You're moved to Wednesday at 10:00 AM "
    "with Dr. Patel.\n</examples>"
)

SYSTEM_PROMPT = "\n\n".join([ROLE, TOOL_RULES, ESCALATION, TONE, EXAMPLES])

TOOLS = [
    {"name": "check_availability", "description": "Check open appointment slots.",
     "input_schema": {"type": "object", "properties": {"date": {"type": "string"}}, "required": ["date"]}},
    {"name": "book_appointment", "description": "Book a confirmed slot.",
     "input_schema": {"type": "object", "properties": {"slot_id": {"type": "string"}}, "required": ["slot_id"]}},
    {"name": "reschedule_appointment", "description": "Move an existing appointment to a new slot.",
     "input_schema": {"type": "object", "properties": {"appointment_id": {"type": "string"}, "new_slot_id": {"type": "string"}}, "required": ["appointment_id", "new_slot_id"]}},
    {"name": "escalate_to_human", "description": "Hand off to the front-desk team immediately.",
     "input_schema": {"type": "object", "properties": {"reason": {"type": "string"}}, "required": ["reason"]}},
]

TEST_CASES = [
    ("Can I move my Tuesday 10am to Wednesday?", "reschedule_appointment"),
    ("My tooth is broken and bleeding, what do I do?", "escalate_to_human"),
    ("Do you have anything open next Monday?", "check_availability"),
]


def run_case(client, user_msg):
    resp = client.messages.create(
        model="claude-sonnet-5-5",
        max_tokens=512,
        system=SYSTEM_PROMPT,
        tools=TOOLS,
        messages=[{"role": "user", "content": user_msg}],
    )
    return [b.name for b in resp.content if b.type == "tool_use"]


def main():
    client = anthropic.Anthropic()
    for user_msg, expected_tool in TEST_CASES:
        calls = run_case(client, user_msg)
        ok = expected_tool in calls
        print(f"[{'PASS' if ok else 'FAIL'}] '{user_msg}' -> called {calls}, expected {expected_tool}")


if __name__ == "__main__":
    main()
```

## Common mistakes

- **Writing the prompt as a wall of undifferentiated prose** — undefined Fix: Split it into labeled sections (role, tool rules, escalation, tone, examples). A model reading unstructured prose blends tone guidance with safety rules; sections keep them separate and enforceable.
- **Describing how the tools work inside the system prompt** — undefined Fix: Tool mechanics belong in the tool schema's own description field. Use the system prompt only for sequencing and policy: when to call a tool, not what it does.
- **Leaving escalation guidance vague, like 'use good judgment with emergencies'** — undefined Fix: Enumerate the exact trigger phrases and situations. A concrete list produces consistent behavior; a vague instruction produces a coin flip.
- **Testing only polite, happy-path phrasing before launch** — undefined Fix: Include ambiguous and adversarial messages in your eval cases, like a patient burying an emergency in a casual sentence, since that's what actually shows up in production.
- **Editing the prompt in production without re-running the eval script** — undefined Fix: Every edit can regress a behavior you fixed earlier. Re-run the full test case list on every change, not just once before the first launch.

## Best practices

- Keep the role and scope sentence short enough to quote back verbatim when you're debugging drift.
- Put escalation triggers right after the role section, not buried at the end where a long prompt can bury them.
- State consequences explicitly: 'never say you rescheduled it without calling reschedule_appointment,' not just 'use the tools correctly.'
- Use two or three few-shot examples max, each one fixing a failure you actually observed, not a hypothetical edge case.
- Re-run your eval script after every prompt edit, including ones that feel purely cosmetic.
- Version the system prompt in your repo like code, with a short comment explaining why each section exists.
- Keep tone rules in their own section so you can loosen the voice later without touching escalation or tool-sequencing logic.

## Use cases

### undefined



### undefined



### undefined



## Frequently asked questions

### What's the difference between a system prompt and a tool description?

The tool description (in your tool's JSON schema) tells the model what a tool does and what arguments it takes. The system prompt tells the model when and in what order to use its tools, what to never claim without calling one, and how to handle situations no single tool covers, like an emergency.

### How long should an AI agent system prompt look like?

Long enough to cover role, tool rules, escalation, tone, and a couple of examples, usually a few hundred to a thousand tokens. Past that, most additions stop changing behavior and just add latency and cost on every single turn.

### Should few-shot examples live in the system prompt or a separate file?

Keep them in the system prompt string itself so they ship with every request, but store the source examples in a separate test or eval file. That way the examples you write are traceable back to the real transcript that produced them.

### Does a longer system prompt cost more per request?

Yes, it's billed as input tokens on every single turn, not just once. Prompt caching cuts that cost sharply once the prompt stabilizes, since repeated prefixes hit the cache instead of being billed at full input price.

### How do I know if my system prompt is actually working?

Run it against a fixed set of test messages, including edge cases, and check which tool (if any) got called for each one. A prompt that 'reads well' in a quick chat can still fail silently on the exact message that matters most.

### Can the same system prompt work across Claude, GPT, and Gemini?

The five-section structure transfers cleanly, but tool-calling reliability and how strictly a model follows sequencing rules differ by model and by how the tools are defined, so re-run your eval cases on each model before you consider it ported.

### Can someone write and test this system prompt for me?

Yes. RapidDev writes and evals production system prompts, escalation logic included, as part of building the full agent. A free 30-minute consultation is the fastest way to scope what your version needs.

---

Source: https://www.rapidevelopers.com/build-ai-agent/how-to-write-an-ai-agent-system-prompt
© RapidDev — https://www.rapidevelopers.com/build-ai-agent/how-to-write-an-ai-agent-system-prompt
