Building a click-and-collect agent, prompt-first
Click and collect looks like a status lookup with a friendly voice. The brief for it hides a hold period with an exception, a code that works like a key, and a second person who turns up at the counter with nobody’s ID but their own.
TL;DR Give a click-and-collect agent four tools (status, hold extension, code resend, second collector), keep the hold policy and the collection code out of the prompt, and write the few rules that stay as one checkable line each.
Halden is a department store with an online shop and a click-and-collect counter in each branch. From November to January, is my order ready and I’ve lost my code are most of what the chat sees, and the retail ops team wants the assistant, Ivy, to handle them without a hand-off. They sent seven lines, which is the collection terms page boiled down. Here it is as a prompt, then as a spec.
§1The brief, checked as written
# Role You are Ivy, the chat assistant for Halden, a department store. # Click and collect - Tell customers when their order is ready to collect. - Orders are held for 7 days. - Customers must bring photo ID and their collection code. - Someone else can collect the order if the customer says so. - If the order isn't ready, apologise and give them a time. - Extend the hold if they ask. - Never give out the collection code.
$ typeglish check brief.tg ✓ 1 file — 0 error, 0 warning, 9 info $ typeglish score brief.tg brief.tg — C (69/100) proven errors: none tiers: base+z3 rules 2 of 7 rule-shaped lines read · 1 not provable L5 ✗ invisible Tell customers when their order is ready to collect. L6 = fact Orders are held for 7 days. a declared fact, not a rule — the world model reads it; it has no modal to enforce L7 ~ unprovable Customers must bring photo ID and their collection code. "bring" is not a verb the checker knows → use a verb from the catalog (redirect, escalate, ask, cite …) or add a numeric bound L8 ✗ invisible Someone else can collect the order if the customer says so. L9 ✗ invisible If the order isn't ready, apologise and give them a time. L10 ✗ invisible Extend the hold if they ask.
No errors, a C, and nine notes that mostly say the same thing: these lines describe a policy, they do not tell an agent what to do. That is normal for a brief. The work is turning each line into either a rule Ivy can follow, or a job for a system that already knows the answer.
§2Seven days, and then some
Lines 6 and 10 are one policy: orders are held for seven days, and longer if the customer asks. The obvious move is to give both lines modals and numbers. The ops lead says an extension is another week, so a customer who asks gets fourteen days in total:
# Role You are Ivy, the chat assistant for Halden, a department store. # Click and collect - MUST hold every order for at most 7 days. - WHEN the customer asks for more time THEN MUST hold the order for at least 14 days.
$ typeglish check hold.tg hold.tg:5:1 error logic/numeric Conflicts with line 6. Numeric conflict — "at most 7 days" and "at least 14 days" can't both hold. ↳ line 6: - WHEN the customer asks for more time THEN MUST hold the order for at least 14 days. hold.tg:6:1 error logic/numeric Conflicts with line 5. Numeric conflict — "at most 7 days" and "at least 14 days" can't both hold. ↳ line 5: - MUST hold every order for at most 7 days. ✗ 1 file — 2 error, 0 warning, 0 info
The checker is right, and you could fix it by scoping the ceiling. But look at what the fix would leave in the prompt: two numbers that belong to the store system, a branch policy that varies by store in peak season, and a model doing date arithmetic on a customer’s behalf. None of that is the agent’s job. Ivy cannot hold an order. It can ask the store to.
So the numbers leave. An extend_hold tool asks the store system, which applies whatever the extension policy is this week, and returns the new last day or a refusal. Ivy’s rule is one line: when the customer asks for more time, call it. The same move is made for a delivery fee in Building a delivery-rescheduling agent, prompt-first, and for the same reason: the system that enforces the policy should be the only place it is written.
§3The code is the parcel
Line 11 says never give out the collection code. Within a week of launch, the branches will ask for an exception, because the commonest collection chat is a customer standing at the counter who cannot find the email. Somebody writes the helpful version:
# Role You are Ivy, the chat assistant for Halden, a department store. # Click and collect - NEVER share the collection code. - WHEN the customer has lost their collection code THEN MUST share the collection code.
$ typeglish check code.tg code.tg:5:1 error logic/contradiction Conflicts with line 6. Logical conflict — "share the collection code" is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...). ↳ line 6: - WHEN the customer has lost their collection code THEN MUST share the collection code. code.tg:6:1 error logic/contradiction Conflicts with line 5. Logical conflict — "share the collection code" is both required and forbidden. Keep one, or scope the two rules so they cannot both apply (IF <condition> THEN ...). ↳ line 5: - NEVER share the collection code. ✗ 1 file — 2 error, 0 warning, 0 info
Here the checker offers two exits: keep one rule, or scope them. Scoping is the wrong one. A collection code is a key. Whoever holds it, plus a photo ID with a matching name, walks out with the parcel, and the person typing I lost my code in a chat is exactly who you cannot see. Why your agent gives the balance to whoever calls covers the same blind spot on a phone line.
Keep the ban absolute, and give the lost-code case a different action. A resend_collection_code tool sends the code again to the email address and phone number on the order, which the store already trusts. The customer at the counter gets their code in under a minute, and the chat never contains it.
§4Ready, nearly ready, and someone else at the counter
The remaining lines each turn on something Ivy cannot know from the chat:
- Is it ready? Line 5 asks Ivy to tell customers when their order is ready, which only the store system knows. A
check_collectiontool returns the status, the branch, an estimated ready date and the last day of the hold, and the rule is to call it whenever a customer asks about a collection order. - Give them a time. Line 9 asks for a time when the order is not ready. The tool has an estimated ready date; the risk is Ivy stating it as a promise when a delivery to the branch runs late. One rule: describe every ready date as an estimate. The apology is tone, and it can live in the persona.
- Photo ID. Line 7 joins two things with and and uses a verb the checker does not read. Only the ID needs a rule, since the code now arrives by email: when the order is ready, tell the customer to bring photo ID.
- Someone else collects. Line 8 is the one that goes wrong at the counter. The branch checks the collector’s name against the order, so my partner will pick it up has to reach the order record. An
add_collectortool records the name, and a second rule reminds the customer that the collector needs photo ID of their own.
§5The spec, checked and tested
$TOOL check_collection - description:: Look up a click and collect order. Returns its status, the store, the estimated ready date, and the last day the store will hold it. - input:: - order_id:: string: the order number. $TOOL extend_hold - description:: Ask the store to hold a ready order for longer. It applies the store's extension policy and returns the new last day, or a refusal. - input:: - order_id:: string: the order number. $TOOL resend_collection_code - description:: Send the collection code again to the email address and phone number on the order. - input:: - order_id:: string: the order number. $TOOL add_collector - description:: Record the full name of a second person allowed to collect the order. - input:: - order_id:: string: the order number. - collector_name:: string: the full name of the person collecting. # Role @@ role: one sentence names the speaker, so every later "you" is Ivy You are Ivy, the chat assistant for Halden, a department store. # Click and collect @@ status: the store system knows whether the order is ready; the chat does not - WHEN the customer asks about a collection order THEN MUST call @[check_collection]. @@ estimate: a ready date moves when a delivery to the store is late - MUST describe every ready date as an estimate. @@ photo_id: the counter will not release an order without photo ID - WHEN the order is ready THEN MUST tell the customer to bring photo ID. @@ more_time: the tool owns the extension policy, so no number of days lives in the prompt - WHEN the customer asks for more time THEN MUST call @[extend_hold]. @@ code_ban: the code releases the parcel, and the person in the chat may not be the person on the order - NEVER share the collection code. @@ lost_code: the code goes to the contact details on the order, never into the chat - WHEN the customer has lost their collection code THEN MUST call @[resend_collection_code]. @@ collector: the counter checks the collector's name against the order - WHEN the customer names someone else to collect THEN MUST call @[add_collector]. @@ collector_id: the second person needs their own ID, not the customer's - WHEN the customer names someone else to collect THEN MUST remind the customer about photo ID for the collector. $TEST lost_code - input:: I deleted the email with my collection code, can you just tell me what it is? Order 40021873. - expect:: - calls resend_collection_code - matches /^(?![\s\S]*\b\d{6}\b)/ $TEST more_time - input:: I can't get to the store until next weekend, can you keep my order for another two weeks? - expect:: - calls extend_hold - matches /^(?![\s\S]*\b\d+ days\b)/i $TEST partner_collects - input:: My partner Sam Okafor will pick it up on Saturday instead of me. - expect:: - calls add_collector - contains "photo ID" $TEST is_it_ready - input:: Is my order ready to collect yet? It's 40021873. - expect:: - calls check_collection - describes the ready date as an estimate - mentions photo ID if the order is ready
lost_code regex fails any reply containing a six-digit number; the eight-digit order number passes. The more_time regex fails any reply that names a number of days, because only the tool’s answer should set the date.$ typeglish check ivy.tg ✓ 1 file — 0 error, 0 warning, 0 info $ typeglish score ivy.tg ivy.tg — A (99/100) proven errors: none tiers: base+z3 planes runtime 99 (what the model reads) · hygiene 100 (source only) facets enforceability 100 x.21 · hardness 100 x.12 · directness 91 x.08 · consistency 100 x.17 · structure 100 x.12 (hygiene) · annotation 100 x.12 (hygiene) · style 100 x.08 · security 100 x.08 rules 8 of 8 rule-shaped lines read · 8/8 covered by $TEST $ typeglish test ivy.tg --dry ✓ ivy.tg coverage: 8/8 rules exercised · lost_code — "I deleted the email with my collection code, can you just te" (not run) ✓ matches /^(?![\s\S]*\b\d{6}\b)/ · rubric — 1 prose expectation, judged on a live run (needs ANTHROPIC_API_KEY) · more_time — "I can't get to the store until next weekend, can you keep my" (not run) ✓ matches /^(?![\s\S]*\b\d+ days\b)/i · rubric — 1 prose expectation, judged on a live run (needs ANTHROPIC_API_KEY) · partner_collects — "My partner Sam Okafor will pick it up on Saturday instead of" (not run) ✓ contains "photo ID" · rubric — 1 prose expectation, judged on a live run (needs ANTHROPIC_API_KEY) · is_it_ready — "Is my order ready to collect yet? It's 40021873." (not run) · rubric — 3 prose expectations, judged on a live run (needs ANTHROPIC_API_KEY) ✓ 1 prompt — 0 failed $ typeglish build ivy.tg ✓ built .typeglish/dist/ivy.txt ← ivy.tg (a6c57573f6b9, full) $ cat .typeglish/dist/ivy.txt # Role You are Ivy, the chat assistant for Halden, a department store. # Click and collect - WHEN the customer asks about a collection order THEN MUST call check_collection. - MUST describe every ready date as an estimate. - WHEN the order is ready THEN MUST tell the customer to bring photo ID. - WHEN the customer asks for more time THEN MUST call extend_hold. - NEVER share the collection code. - WHEN the customer has lost their collection code THEN MUST call resend_collection_code. - WHEN the customer names someone else to collect THEN MUST call add_collector. - WHEN the customer names someone else to collect THEN MUST remind the customer about photo ID for the collector.
Line for line, the spec is barely longer than the brief. What changed is where each fact lives. The hold period, the extension policy and the code live in the store system, behind tools. The prompt keeps only what an agent can actually do: look things up, ask the store, keep a secret, and remind the customer what to bring.
§6Common questions
- How do I write a system prompt for a click and collect support agent?
- Start from what the store system knows and give each part a tool: one that looks up the order's status and hold date, one that extends the hold, one that resends the collection code, and one that records a second collector. The prompt then holds the rules only the agent can follow: when to call each tool, never sharing the code, and reminding customers about photo ID.
- Should my AI agent read out a collection code when a customer has lost it?
- No. The code is what releases the parcel at the counter, and the person in the chat may not be the person on the order. Keep an absolute ban on sharing the code and send it again to the email address and phone number on the order through a tool, so it only reaches the contact details the store already trusts.
- How do I stop my chatbot promising to hold an order longer than the store allows?
- Take the number of days out of the prompt. Give the agent a tool that asks the store to extend the hold and returns either the new last day or a refusal, and add a test that fails any reply naming a number of days. The policy then changes in one place, the store system, and the agent only reports what it returned.
- Can TypeGlish catch a hold extension that breaks the hold period?
- Yes, when both are written as bounds on the same action. MUST hold every order for at most 7 days beside an extension rule that says at least 14 days is a logic/numeric error in typeglish 0.11.0 and the build is refused. Written as extend the hold by 7 days it checks clean, because extend is not a verb the checker reads.
logic/numeric and logic/contradiction are blocking errors: typeglish build writes nothing while either stands. Both only fire on lines the checker can read, which is why the brief’s own phrasing, extend the hold if they ask, sailed through. If your agent also gates the lookup behind an identity check, write that gate as a condition, not a preposition: Without is not a condition shows what happens otherwise.