Skip to main content

Giving ShopBot a role: the system prompt

Run the harness from Running the loop for real and ask "Where's my order?", and something surprising happens: instead of calling look_up_orders, the model politely asks you for your email or customer id.

It's not broken. It's being honest — and fixing it teaches us the last piece of the model call: the system prompt.

Why it asks instead of acting

Look at what we actually told the model: the customer's question, and the list of tools. We never told it who it is, who it's serving, or how to behave. So it's just a generic, helpful assistant.

And look_up_orders requires a customer argument (that's the schema, from What is a tool?). The model has no idea who the customer is — nothing in the conversation says — so it can't fill that argument in. The honest move is to ask, which is exactly what it did. (Guessing an identifier would be the hallucination we warned about in lesson one.)

To make ShopBot act like ShopBot, we have to give it a role and some context.

The system prompt: standing instructions

A system prompt is a block of instructions you give the model that sit above the conversation. It's not a turn in the chat — it's the model's standing brief, applied to every round: who it is, what it should do, what rules to follow, and any context it needs.

System message vs. user message

The conversation (messages) is the back-and-forth — the customer's words and the tool results. The system prompt is separate: it's not something the "customer said," it's what you, the developer, told the assistant before the conversation started. The API takes it as its own system parameter for exactly that reason.

What we put in ShopBot's brief

Here's the system prompt we add to the harness:

SYSTEM_PROMPT = (
"You are ShopBot, a friendly customer-support assistant for an online store. "
"The signed-in customer is alex@example.com. Use that identifier when you "
"look up their orders — never ask the customer for it. Always use the "
"available tools to fetch real order and tracking information, and base your "
"answers only on what the tools return."
)

Three jobs in those few sentences:

  • Role"You are ShopBot…" It's a store support agent, not a generic chatbot.
  • Context"The signed-in customer is alex@example.com." Now the model can fill in look_up_orders's customer argument without asking.
  • Rules — always use the tools, and answer only from what they return. That pushes it toward grounded answers and away from guessing.

The harness supplies the identity — not the user

Notice where the customer's identity comes from: the harness puts it in the system prompt. The customer never typed their email, and the model didn't pick it. That's deliberate and it's a safety point.

In a real app the customer is already authenticated — they're logged in, and the harness knows who they are. So the harness injects that identity. If we instead let the customer say "look up orders for someone@else.com," they could read another person's orders. Identity is the harness's job, not the model's and not the customer's. (This is the same control idea as permissions: the harness decides what's allowed.)

Wiring it in

The system prompt goes into the model call as its own argument — one line added to the create(...) from the previous lesson:

response = client.messages.create(
model=MODEL,
max_tokens=1024,
system=SYSTEM_PROMPT, # the standing instructions
tools=TOOL_SCHEMAS,
messages=conversation,
)

That's the whole change. Everything else about the loop stays the same.

The difference it makes

Before — no role, no context:

--- Round 1: asking the model ---
=== ShopBot's final answer ===
I'd be happy to help! Could you share the email or customer id on the order?

After — with the system prompt:

--- Round 1: asking the model ---
[harness] running look_up_orders({'customer': 'alex@example.com'})
--- Round 2: asking the model ---
[harness] running get_tracking_status({'tracking_number': '1Z999'})
--- Round 3: asking the model ---
=== ShopBot's final answer ===
Your running shoes (order #4521) are out for delivery — they arrive today!

Same loop, same tools, same question. The only thing that changed is that the model now knows who it is and who it's helping — so it acts instead of asking.

The system prompt is high-leverage

A few sentences here change the agent's whole behavior. It's where you set tone, boundaries ("never share another customer's data"), and fallback behavior ("if a tool fails, apologise and offer to follow up"). When an agent misbehaves, the system prompt is the first place to look.

Recap

  • By default the model is a generic assistant — it doesn't know its role or who it's serving, so it asks instead of acting.
  • A system prompt is the model's standing brief: role, context, and rules, applied every round, passed as the API's own system argument (separate from the conversation).
  • The harness supplies the authenticated customer's identity in that prompt — not the user — which is both what makes the tools usable and a safety boundary.
  • One added line (system=SYSTEM_PROMPT) turns a generic chatbot into ShopBot.

Next up: with the core loop complete, we harden it — handling tool errors, validating arguments, and requiring approval for sensitive actions.