How the add-ons get picked
This is the heart of the system, and the place its guardrails matter most. A model is good at spotting that a road bike and a track pump go together; it is hopeless at knowing whether the pump is in stock, priced right, or already in the customer’s garage. So the model proposes a shortlist and Python disposes of it through hard catalogue rules — and a suggestion the model loves but the rules reject never reaches anyone.
Key takeaways
- One Bedrock Haiku 4.5 call per order proposes a ranked shortlist of add-ons with a short line for each — the only place a model runs.
- The model proposes; Python disposes. Deterministic catalogue rules are the hard filter that makes the actual choice.
- The rules drop anything out of stock, not genuinely complementary, wrongly priced, or already owned — run in that order, over the model’s list.
- The top surviving candidate wins. If the whole shortlist is filtered out, no offer is made — silence is a valid, common outcome.
- A suggestion the model loves but the rules reject can never reach a customer, because the rules run after the model, not before.
The one place judgement is shared
By the time this step runs, the gate in Part 2 has proved the order is real and new, cleared the customer against opt-out and the frequency cap, and gathered the basket and history. The job on the queue carries what was bought, what the customer already owns, and how much the order was worth. What’s left is the actual recommendation — and this is the one place in the system where a model and deterministic code share the work, so it’s worth being precise about who does which half.
A model is genuinely good at one thing here: knowing that a road bike goes with a track pump and mudguards, that a bag of single-origin beans goes with a burr grinder and a descaler, that a dog bed goes with a matching blanket. That’s associative knowledge, and it’s exactly what an off-the-shelf catalogue lacks. But a model is hopeless at the things that decide whether a suggestion is any good in practice: it doesn’t know what’s in stock this morning, what the margins are, what this customer already bought last year, or what price looks sensible against this order. So the split is clean. The model proposes; the catalogue rules dispose.
The model proposes a shortlist
One Bedrock call, using Claude Haiku 4.5, is handed the basket, a short summary of the customer’s history, and a slice of the catalogue — the candidate add-ons in the relevant categories, with their names and tags but not the final say. It is asked to return a ranked shortlist of the add-ons that best complement what was bought, each with a one-line pitch in the shop’s voice. Haiku is the right tool because the task is small and bounded: rank a handful of candidates and write a sentence each, no reasoning chain, no tools. It’s fast and it costs a fraction of a penny — which matters, because unlike the rest of the system this call runs on every eligible order, not just the ones that convert.
Crucially, the model’s output is a proposal, not a decision. It returns something like “1. commuter lights-and-lock bundle — ‘your new bike will want lights and a lock’; 2. mudguards — ‘keep the road spray off’; 3. track pump — ‘top up before every ride’”. Every one of those is a candidate SKU with a pre-written line attached. Nothing is sent yet. Nothing is even chosen yet. All the model has done is put its shortlist on the table, best first, for the rules to work through.
The rules dispose of it
Now the deterministic half runs, and it is deliberately unglamorous: a short list of hard filters applied to the model’s shortlist in order, each reading the catalogue mirror. In stock — the SKU must have positive stock right now, or it’s dropped; there is no faster way to lose a customer’s trust than to offer them something you can’t sell. Complementary — the candidate must carry a catalogue tag that genuinely pairs it with something in the basket; the model might free-associate its way to a suggestion, but the tag is the shop’s own considered opinion, and it wins. Price appropriate — the add-on must sit inside a sensible band relative to the order value, so a £300 wheelset is never offered against a £40 order, and a 50p valve cap is never the headline suggestion on a £520 bike. Not already owned — anything in the customer’s history is removed, because suggesting the pump they bought last spring makes the whole system look like it isn’t paying attention.
The candidates are walked in the model’s ranked order, and the first one that passes every filter is the winner — the shop’s preferences (the tags, the stock, the price bands) acting as a hard gate on the model’s preferences (the ranking). The chosen SKU, its price, and the model’s pre-written line are written to the offers table with a send-at time, ready for Part 4. And here is the guardrail that matters most: because the rules run after the model and not before, there is no path by which a suggestion the model liked but the rules reject can reach a customer. The model never touches stock, price, or ownership; it only ever ranks. The filter is the last word.
When nothing fits
Sometimes every candidate is struck out — the complementary items are all out of stock, or the customer already owns them, or the model’s shortlist was thin because the order genuinely has no natural add-on (a gift card, a lone spare part, a one-off clearance item). When that happens, the system does the disciplined thing and makes no offer. The order is recorded as “no fit” so it can be counted later, and nothing is sent. This is not a failure mode; it’s a feature. A system that always finds something to suggest is a system that will cheerfully suggest rubbish, and a customer who gets one weak nudge trusts the next one less. By making silence a first-class outcome — and a common one — every nudge that does go out has earned its place. The frequency cap stops the system nudging too often; the empty-shortlist rule stops it nudging when it has nothing worth saying.
Design rules that shaped the pick
- One call, one shortlist. A single Haiku call proposes ranked candidates — no chains, no second opinions, no tools.
- The model proposes, Python disposes. The model ranks; the deterministic catalogue rules make the actual choice.
- The rules run last. Filters apply after the model, so an out-of-stock or already-owned pick can never slip through.
- In stock, complementary, priced right, not owned. Four hard filters, in that order; the first survivor wins.
- No fit, no offer. An empty shortlist sends nothing and is recorded — silence is a valid, frequent outcome.
- The catalogue is the authority. Stock, tags and price bands are the shop’s opinion, and they outrank the model every time.