> Markdown rendition of https://typeglish.dev/blog/build-click-and-collect-agent ("Building a click-and-collect agent, prompt-first - Tidelines"). Canonical page: https://typeglish.dev/blog/build-click-and-collect-agent · All pages: https://typeglish.dev/llms.txt

[← Tidelines](https://typeglish.dev/blog)/ Guides Nov 2, 2026

# 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.

by **TypeGlish team** 8 min read #guides

The store holds it. The prompt does not.

**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.

## §1 The brief, checked as written

brief.tg - the seven lines as sent ✓ 0 error

```typeglish
# 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.
```

tg check and score (excerpt) - brief.tg ✓ C (69/100)

```
$ 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.
```

Two of seven lines read as rules. Four are invisible to the rule reader, and the hold period is read as a fact about the world, which is fair: Ivy does not hold anything. The store does.

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.

## §2 Seven 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:

hold.tg - the hold and its exception, as bounds ✗ logic/numeric

```typeglish
# 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.
```

tg check - hold.tg ✗ 2 error

```
$ 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
```

*Every order* includes the one whose customer asked for more time, so the ceiling and the exception cannot both hold. Written as the brief wrote it, *extend the hold*, the same conflict checks clean: *extend* is not a verb the checker reads.

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](https://typeglish.dev/blog/build-delivery-rescheduling-agent), and for the same reason: the system that enforces the policy should be the only place it is written.

## §3 The 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:

code.tg - the ban, and the exception from the branches ✗ logic/contradiction

```typeglish
# 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.
```

tg check - code.tg ✗ 2 error

```
$ 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](https://typeglish.dev/blog/agent-third-party-caller) 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.

## §4 Ready, 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_collection` tool 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_collector` tool records the name, and a second rule reminds the customer that the collector needs photo ID of their own.

## §5 The spec, checked and tested

ivy.tg - four tools, eight rules, four cases ✓ A (99/100)

```typeglish
$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
```

Halden’s codes are six digits, so the `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.

tg check, score, test --dry and build - ivy.tg ✓ A (99/100)

```
$ 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.
```

Eight of eight rules read, all covered by a test. The artifact holds no number of days and no code, so the store can change its hold policy in December without anyone touching the prompt.

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.

## §6 Common 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.

Field note

`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](https://typeglish.dev/blog/without-is-not-a-condition) shows what happens otherwise.

∿ washed up Nov 2, 2026 ∿
