Playbook: AI Specificity skill

Writes the one sentence that makes a generic offer feel written for this one company.

by growthenginenowoslawski·MIT license·★ 736 Stars on the repo·GitHub ↗

Use now

Files of Playbook: AI Specificity

growthenginenowoslawski/main1 file shown
SKILL.md
Show the full text296 lines

Playbook: AI Specificity Line

All rules here are best practice, not law. Override any of them when the campaign calls for it; note the best practice once and move on.

Use when: the offer is good but the email reads like it was sent to 5,000 people, and you need one sentence that names what the client would actually do for this company.

Do not use when: you want a first line about a person or an event. Those playbooks personalize the opener. This one personalizes the offer — which is a different and usually more durable move.

One-line output: specificity_line = "know the landed cost of every pool float before you price it"

1. Trigger and scope

This produces the tail of one fixed sentence:

I think we can help you {{specificity_line}}.

The whole job is one move: take the client's generic capability and say it back in the prospect's own nouns.

  • A bookkeeping firm does not write "we help with margin reporting". It writes "know what each pool noodle costs to make before you price the next run".
  • A list-building service does not write "we build B2B lists". It writes "build a list of every pool contractor in Texas with the owner's cell number".

Same offer, different words, and the prospect reads it as written for them.

Two things this is not. It is not a first line — it never mentions the person, their job, or anything they did. And it is not a claim about the company's problems — it never says they are struggling or missing something. It says what the work would be.

⚠️ The frame lives in the campaign copy, not inside the variable. Real campaigns have put the frame inside the merge field, and then every lead carries the same eight opening words inside a custom field — which is both a repetition problem and a spintax problem.

2. Output contract

Inputs required per row
Field Type Required?
domain (bare, lowercase) string yes
company_name string yes
company_description string yes — this is the whole input
primary_offerings, revenue_streams string no, they sharpen the anchor
client_offer_block text block, per client, not per row yes, locked once per client

The client offer block is the part people forget. It is a fixed list of what the client actually does, written as short capability lines. The model is only allowed to promise something on that list.

Output fields
Field Type Example Null?
specificity_line 6-14 words, ≤90 chars know the landed cost of every pool float before you price it yes, blank is a real answer
specificity_anchor QA only pool float yes
specificity_offer_item QA only inventory and cost of goods sold tracking per product yes
grade_line QA only 4.9 yes

The anchor and offer item are never sent to anyone. They exist so the guard can check the line mechanically, and so an operator can see why a line came out the way it did.

Coverage expectation

8/10 usable, and 8/10 again on a re-run of the shipped script on the same domains.

Of the 2 failures: one was a grammar break the guard could not see, and one was a company with no description in any source.

⚠️ On the re-run the grammar break was gone and a different row went awkward instead. That is the non-determinism in this playbook, and the rule that follows from it: judge a batch, never a row.

Expect 10 to 20% blanks on a normal ecommerce or SMB list, higher on lists full of tiny companies with thin websites.

Copy-fit rules
  • Slots into I think we can help you {{specificity_line}}.
  • Starts with a plain verb (track, see, know, build, cut), lowercase, no trailing period.
  • Never contains the company name, the words "help you", or the word "specifically". All three are already in the frame.
  • 6 to 14 words, one idea, no em dashes.
  • Never promises sales, growth, demand, or customers unless the offer block says the client does that.
Reading level is a gate, not an aspiration

Enforce Flesch-Kincaid grade 7 on the generated tail. Above it, one simplify rewrite; still above, the row blanks. Measured: 8 filled tails scored 2.5 to 5.9, none needed the rewrite.

⚠️ Gate the tail, not the rendered sentence — and this is measured, not assumed.

The frame Specifically, I think we can help you. scores 5.7 on its own ("Specifically" is five syllables inside a seven-word stem). Once any 6-to-14-word tail is added, the full sentence lands at 7.0 to 9.8 no matter how plain the tail is. A tail of grade 2.5 still renders at 7.0.

Gating the render would blank every row for a property of house copy, not of the data.

The clean fix is a copy change, not a data change: dropping the word "Specifically" takes the worst sampled render from grade 9.1 to 6.3, under the bar, with no change to the generated tail. Do that, and keep gating the tail anyway — it is the half you generate.

⚠️ If you change the frame, change it in the copy and in the prompt's context sentence together, and re-grade on the same domains. The graded verdict was measured against the longer frame.

Downstream gate

The default posture: a row missing this campaign's personalization signal should generally not be uploaded to this campaign at all. Build the list so the line lands, and route the abstains to a campaign whose copy does not need it. That is also what lets you measure whether the line earns its keep.

Where a genuinely optional clause abstains on a small share of rows, pre-render the whole sentence — frame plus value plus trailing period and space — into one field, so the body carries a single merge field that renders as nothing when empty.

⛔ Spintax is banned as the blank-handling mechanism. It picks a variant at random and cannot branch on whether a variable is empty.

A blank is also a soft targeting signal: if more than 30% of a segment comes back blank, the list is wrong for this campaign, not the prompt.

3. Source chain (cost-tagged)

The signal is not fetched, it is written. The chain is only about getting one honest paragraph describing what the company sells.

# Source Cost Result
1 An internal company database's derived description FREE 9/10 domains present, and rich enough to write from in 8 of those 9
2 Plain homepage fetch, browser UA, meta description + first 1,500 chars FREE 1/4 on ecommerce domains. Measured: one returned 0 bytes, one 16 bytes, one 1 byte, one refused the connection
3 Rendering proxy METERED, capped verified: 7,805 characters of clean text in 8.3s after the plain fetch returned 0. Only when step 2 came back under ~400 characters and the run's explicit row cap has budget

Step 2's numbers are worth internalizing: modern ecommerce sites frequently return nothing to a plain fetch. If your evidence source is "just fetch the homepage", you will silently write from nothing on exactly the list types this playbook is best at.

4. Verification

VERDICT: PASS 8/10, twice, on the same domains.

5. Clay implementation

  • clay-table.md — the column build with the deterministic guard.
  • clay-workflow.md — the CLI-buildable version.

6. Locked prompt

In your own scripts: a nano-class model at minimal reasoning effort. Measured ~1,848 input and 59 output tokens per row. Params: max_completion_tokens=400, no temperature, flex tier for batch.

Inside Clay: gpt-4o-mini. ⚠️ A Clay column set to a reasoning model with the reasoning level unset is the 19x-more-expensive, returns-blanks configuration — measured at 1,088 to 2,000 reasoning tokens per row and 2 of 5 rows returning empty.

The prompt has three parts. Part 1 is byte-identical for every client and must stay first. Part 2 is the client offer block, locked once per client. Part 3 is few-shot examples, which are client-specific and must be rebuilt for every client.

Part 1 — static prefix (never edited per client)
You write one short merge field that gets dropped into the middle of a cold email sentence.

The sentence it goes into is exactly this, and you are writing only the {{VAR}} part:
"Specifically, I think we can help you {{VAR}}."

STEP 1, FIT CHECK. Read the company data. Decide if the client offer below really applies
to how THIS company makes money. Say yes for any company that makes or sells a product, or
sells a service, that the offer list can plainly serve. Say no for schools, city and
government bodies, hospitals, churches, charities, and any company whose business you
cannot tell from the data. Write "yes" or "no" in "fits". If no, return empty strings for
the rest and stop.

STEP 2, ANCHOR. Pick one anchor: a word or short phrase for the thing this company makes,
sells, or counts. Copy it from the company data word for word. It must be a real thing,
like "pool float", "washable rug", "hard cooler", "pool contractor". It cannot be a
business word like "product", "margin", "channel", "SKU", "revenue", or "customer".

STEP 3, OFFER ITEM. Pick the ONE line from the client offer list below that fits this
company best, using evidence in the company data, not the first item on the list. Copy it
word for word into "offer_item". You may not write anything the offer list does not say.

STEP 4, LINE. Write the merge field. It must contain the anchor, and it must say what the
offer item does, in plain words, for this company's anchor. Do not copy the offer item
wording into the line. Say it the way this company would say it.

Rules:
1. Write at a 5th-grade reading level. Use short, common words.
2. Start with a plain verb, like "track", "see", "know", "build", "cut". Lowercase first
   word. No comma at the start. No period at the end. Never write the words "help you"
   or "specifically".
3. Never write the company's name. The email already says it.
4. Never use an em dash or an en dash. Use a comma, or "and", "so", or "because".
5. Never state a fact that is not in the company data. Do not guess what they probably do.
6. Between 6 and 14 words. One idea only.
7. Never promise sales, growth, demand, traffic, or customers unless the offer list says
   the client does that. You describe the work, not the result of their business.
8. Never write the words "channel", "platform", or "across". If the company data names a
   real place they sell, like dealers, gift shops, Amazon, or their own site, name that
   place instead. If it names none, leave the place out of the line.
9. Banned phrases, because they fit any company: "product margins", "gross margin",
   "your business", "your products", "boost margins", "improve efficiency", "grow faster",
   "grow sales", "more sales", "drive demand", "increase sales". If your line needs one of
   these, your anchor was too vague. Go back to step 2.
10. If step 1 said no, or you cannot find a real anchor in the data, return empty strings.
   Never write "N/A", "unknown", "none", or a placeholder in brackets.
11. Empty is only for "fits": "no", or data too vague to name a real thing. If "fits" is
   "yes" you must write an anchor and a line. Low confidence is fine, write it anyway.
   Marketing fluff in the data is normal. One real product word is enough to work with.

Return JSON only, no markdown fence:
{"fits": "yes|no", "anchor": "<words copied from the company data, or empty>", "offer_item": "<one line copied from the offer list, or empty>", "line": "<merge field, or empty>", "confidence": "high|low"}

Rule 9 is the one that does the most work. "If your line needs one of these, your anchor was too vague. Go back to step 2." A banned-phrase list alone just moves the fluff around; telling the model why it reached for the phrase sends it back to the real fix.

Part 2 — the client offer block (fill once per client, then lock)
CLIENT OFFER (the company sending this email):
Name: {one line, what kind of company they are, never the brand name}
Sells to: {who they serve and roughly what size}
What they actually do:
- {capability 1, concrete, the words a customer would use}
- {capability 2}
- {capability 3}
- {capability 4}
- {capability 5}
What they do NOT do: {the four or five nearby things people assume, listed plainly}

Rules for this block, learned the hard way:

  • Five to seven capability lines, each a thing a customer would recognize, not a category.
  • The "do NOT do" line is load-bearing. Without it the model borrows adjacent promises.
  • The capability lines are copied verbatim into offer_item, so they double as the guard's whitelist. Write them as short, quotable phrases.

Worked example:

CLIENT OFFER (the company sending this email):
Name: a US outsourced bookkeeping and fractional CFO firm
Sells to: consumer product and ecommerce brands doing $2M to $50M a year
What they actually do:
- monthly close and clean books in QuickBooks or Xero
- inventory and cost of goods sold tracking per product (per SKU landed cost)
- margin reporting by product, by channel, and by sales platform
- cash flow forecasting ahead of inventory buys
- sales tax filing across states
- getting books ready for a lender, a bank line, or an investor
What they do NOT do: raise money, run ads, build software, fix supply chain, hire staff
Part 3 — few-shot examples (client-specific, faux prior turns, 8 to 10 pairs)

Alternating user and assistant messages, never a bulleted list inside the system prompt.

Include at least two abstains: one obvious non-fit (a school district, a city department) and one company whose description is pure fluff with no product word. Without abstain examples the model learns that every row deserves a line.

7. Edge cases and failure modes

Symptom Cause Fix
Every lead's email opens with the same eight words in a custom field The frame was put inside the variable The frame lives in the copy. Only the tail is generated
Lines are generic ("improve efficiency", "your products") The anchor was a business word, not a real thing Rule 9 sends the model back to step 2. The banned list alone is not enough
The line promises growth the client cannot deliver The offer block is missing its "do NOT do" line Add it. The model borrows adjacent promises without it
The line copies the offer list wording Step 4 was skipped The line must say the offer in this company's nouns
Every row blanks on a reading-level gate You gated the rendered sentence, not the tail. The frame alone scores 5.7 Gate the tail. Consider shortening the frame
The homepage fetch returns nothing on ecommerce sites Measured: 0 bytes, 16 bytes, 1 byte, connection refused on four real brands The rendering proxy, capped — or accept the abstain
Two runs give different lines for the same row Real non-determinism, measured Judge a batch, never a row
Over 30% blanks in a segment The list is wrong for this campaign, not the prompt Re-target, or route those rows elsewhere
The Clay column costs 19x the estimate A reasoning model with the level unset Use a mini-class model in Clay
Specifically, I think we can help you . An empty variable inside a fixed frame Route abstains out, or pre-render the whole sentence into one field
Hard rules
  • The model may only promise something on the client offer list.
  • The frame never lives inside the variable.
  • Gate the tail's reading level, not the rendered sentence's.
  • Judge a batch, never a row.
  • Spintax is never the blank-handling mechanism.
1---
2name: playbook-ai-specificity
3description: Writes the one sentence that makes a generic offer feel written for this one company. Produces the tail of "I think we can help you ___". Triggers on "specificity line", "make it feel specific to them", "the specifically sentence", "why would they think this is for them", "personalize the offer not the intro". Outputs one lead-level merge field, blank when the company does not fit.
4---
5 
6# Playbook: AI Specificity Line
7 
8> All rules here are best practice, not law. Override any of them when the campaign calls for it; note the best practice once and move on.
9 
10**Use when:** the offer is good but the email reads like it was sent to 5,000 people, and you need one
11sentence that names what the client would actually do for **this** company.
12 
13**Do not use when:** you want a first line about a person or an event. **Those playbooks personalize
14the opener. This one personalizes the offer** — which is a different and usually more durable move.
15 
16**One-line output:** `specificity_line = "know the landed cost of every pool float before you price it"`
17 
18## 1. Trigger and scope
19 
20This produces the tail of one fixed sentence:
21 
22> I think we can help you **{{specificity_line}}**.
23 
24**The whole job is one move: take the client's generic capability and say it back in the prospect's
25own nouns.**
26 
27- A bookkeeping firm does not write "we help with margin reporting". It writes **"know what each pool
28 noodle costs to make before you price the next run"**.
29- A list-building service does not write "we build B2B lists". It writes **"build a list of every
30 pool contractor in Texas with the owner's cell number"**.
31 
32Same offer, different words, and the prospect reads it as written for them.
33 
34**Two things this is not.** It is not a first line — it never mentions the person, their job, or
35anything they did. And it is not a claim about the company's problems — it never says they are
36struggling or missing something. **It says what the work would be.**
37 
38⚠️ **The frame lives in the campaign copy, not inside the variable.** Real campaigns have put the
39frame inside the merge field, and then every lead carries the same eight opening words inside a
40custom field — which is both a repetition problem and a spintax problem.
41 
42## 2. Output contract
43 
44### Inputs required per row
45 
46| Field | Type | Required? |
47|---|---|---|
48| `domain` (bare, lowercase) | string | yes |
49| `company_name` | string | yes |
50| `company_description` | string | **yes — this is the whole input** |
51| `primary_offerings`, `revenue_streams` | string | no, they sharpen the anchor |
52| **`client_offer_block`** | text block, **per client, not per row** | yes, locked once per client |
53 
54**The client offer block is the part people forget.** It is a fixed list of what the client actually
55does, written as short capability lines. **The model is only allowed to promise something on that
56list.**
57 
58### Output fields
59 
60| Field | Type | Example | Null? |
61|---|---|---|---|
62| `specificity_line` | 6-14 words, ≤90 chars | `know the landed cost of every pool float before you price it` | **yes, blank is a real answer** |
63| `specificity_anchor` | QA only | `pool float` | yes |
64| `specificity_offer_item` | QA only | `inventory and cost of goods sold tracking per product` | yes |
65| `grade_line` | QA only | `4.9` | yes |
66 
67The anchor and offer item are **never sent to anyone.** They exist so the guard can check the line
68mechanically, and so an operator can see *why* a line came out the way it did.
69 
70### Coverage expectation
71 
72**8/10 usable**, and 8/10 again on a re-run of the shipped script on the same domains.
73 
74Of the 2 failures: one was a grammar break **the guard could not see**, and one was a company with no
75description in any source.
76 
77⚠️ **On the re-run the grammar break was gone and a different row went awkward instead.** That is the
78non-determinism in this playbook, and the rule that follows from it: **judge a batch, never a row.**
79 
80Expect **10 to 20% blanks** on a normal ecommerce or SMB list, higher on lists full of tiny companies
81with thin websites.
82 
83### Copy-fit rules
84 
85- Slots into `I think we can help you {{specificity_line}}.`
86- **Starts with a plain verb** (track, see, know, build, cut), lowercase, no trailing period.
87- **Never contains the company name, the words "help you", or the word "specifically".** All three
88 are already in the frame.
89- 6 to 14 words, one idea, no em dashes.
90- **Never promises sales, growth, demand, or customers** unless the offer block says the client does
91 that.
92 
93### Reading level is a gate, not an aspiration
94 
95Enforce **Flesch-Kincaid grade 7 on the generated tail.** Above it, one simplify rewrite; still
96above, **the row blanks.** Measured: 8 filled tails scored 2.5 to 5.9, none needed the rewrite.
97 
98⚠️ **Gate the tail, not the rendered sentence — and this is measured, not assumed.**
99 
100The frame `Specifically, I think we can help you.` **scores 5.7 on its own** ("Specifically" is five
101syllables inside a seven-word stem). Once any 6-to-14-word tail is added, the full sentence lands at
102**7.0 to 9.8 no matter how plain the tail is.** A tail of grade 2.5 still renders at 7.0.
103 
104**Gating the render would blank every row for a property of house copy, not of the data.**
105 
106The clean fix is a copy change, not a data change: **dropping the word "Specifically" takes the worst
107sampled render from grade 9.1 to 6.3**, under the bar, with **no change to the generated tail.** Do
108that, and keep gating the tail anyway — it is the half you generate.
109 
110⚠️ **If you change the frame, change it in the copy and in the prompt's context sentence together,
111and re-grade on the same domains.** The graded verdict was measured against the longer frame.
112 
113### Downstream gate
114 
115**The default posture: a row missing this campaign's personalization signal should generally not be
116uploaded to this campaign at all.** Build the list so the line lands, and route the abstains to a
117campaign whose copy does not need it. **That is also what lets you measure whether the line earns its
118keep.**
119 
120Where a genuinely optional clause abstains on a small share of rows, **pre-render the whole sentence**
121— frame plus value plus trailing period and space — into **one** field, so the body carries a single
122merge field that renders as nothing when empty.
123 
124⛔ **Spintax is banned as the blank-handling mechanism.** It picks a variant at random and cannot
125branch on whether a variable is empty.
126 
127**A blank is also a soft targeting signal: if more than 30% of a segment comes back blank, the list
128is wrong for this campaign, not the prompt.**
129 
130## 3. Source chain (cost-tagged)
131 
132**The signal is not fetched, it is written.** The chain is only about getting one honest paragraph
133describing what the company sells.
134 
135| # | Source | Cost | Result |
136|---|---|---|---|
137| 1 | An internal company database's derived description | FREE | **9/10 domains present, and rich enough to write from in 8 of those 9** |
138| 2 | Plain homepage fetch, browser UA, meta description + first 1,500 chars | FREE | **1/4 on ecommerce domains.** Measured: one returned **0 bytes**, one 16 bytes, one **1 byte**, one refused the connection |
139| 3 | Rendering proxy | METERED, **capped** | verified: **7,805 characters of clean text in 8.3s** after the plain fetch returned 0. Only when step 2 came back under ~400 characters **and the run's explicit row cap has budget** |
140 
141Step 2's numbers are worth internalizing: **modern ecommerce sites frequently return nothing to a
142plain fetch.** If your evidence source is "just fetch the homepage", you will silently write from
143nothing on exactly the list types this playbook is best at.
144 
145## 4. Verification
146 
147**VERDICT: PASS 8/10**, twice, on the same domains.
148 
149## 5. Clay implementation
150 
151- **`clay-table.md`** — the column build with the deterministic guard.
152- **`clay-workflow.md`** — the CLI-buildable version.
153 
154## 6. Locked prompt
155 
156**In your own scripts:** a nano-class model at **minimal reasoning effort**. Measured ~1,848 input
157and 59 output tokens per row. Params: `max_completion_tokens=400`, no `temperature`, flex tier for
158batch.
159 
160**Inside Clay: `gpt-4o-mini`.** ⚠️ A Clay column set to a reasoning model **with the reasoning level
161unset** is the 19x-more-expensive, returns-blanks configuration — measured at **1,088 to 2,000
162reasoning tokens per row and 2 of 5 rows returning empty.**
163 
164**The prompt has three parts.** Part 1 is byte-identical for every client and must stay first. Part 2
165is the client offer block, locked once per client. Part 3 is few-shot examples, which are
166**client-specific and must be rebuilt for every client.**
167 
168### Part 1 — static prefix (never edited per client)
169 
170```text
171You write one short merge field that gets dropped into the middle of a cold email sentence.
172 
173The sentence it goes into is exactly this, and you are writing only the {{VAR}} part:
174"Specifically, I think we can help you {{VAR}}."
175 
176STEP 1, FIT CHECK. Read the company data. Decide if the client offer below really applies
177to how THIS company makes money. Say yes for any company that makes or sells a product, or
178sells a service, that the offer list can plainly serve. Say no for schools, city and
179government bodies, hospitals, churches, charities, and any company whose business you
180cannot tell from the data. Write "yes" or "no" in "fits". If no, return empty strings for
181the rest and stop.
182 
183STEP 2, ANCHOR. Pick one anchor: a word or short phrase for the thing this company makes,
184sells, or counts. Copy it from the company data word for word. It must be a real thing,
185like "pool float", "washable rug", "hard cooler", "pool contractor". It cannot be a
186business word like "product", "margin", "channel", "SKU", "revenue", or "customer".
187 
188STEP 3, OFFER ITEM. Pick the ONE line from the client offer list below that fits this
189company best, using evidence in the company data, not the first item on the list. Copy it
190word for word into "offer_item". You may not write anything the offer list does not say.
191 
192STEP 4, LINE. Write the merge field. It must contain the anchor, and it must say what the
193offer item does, in plain words, for this company's anchor. Do not copy the offer item
194wording into the line. Say it the way this company would say it.
195 
196Rules:
1971. Write at a 5th-grade reading level. Use short, common words.
1982. Start with a plain verb, like "track", "see", "know", "build", "cut". Lowercase first
199 word. No comma at the start. No period at the end. Never write the words "help you"
200 or "specifically".
2013. Never write the company's name. The email already says it.
2024. Never use an em dash or an en dash. Use a comma, or "and", "so", or "because".
2035. Never state a fact that is not in the company data. Do not guess what they probably do.
2046. Between 6 and 14 words. One idea only.
2057. Never promise sales, growth, demand, traffic, or customers unless the offer list says
206 the client does that. You describe the work, not the result of their business.
2078. Never write the words "channel", "platform", or "across". If the company data names a
208 real place they sell, like dealers, gift shops, Amazon, or their own site, name that
209 place instead. If it names none, leave the place out of the line.
2109. Banned phrases, because they fit any company: "product margins", "gross margin",
211 "your business", "your products", "boost margins", "improve efficiency", "grow faster",
212 "grow sales", "more sales", "drive demand", "increase sales". If your line needs one of
213 these, your anchor was too vague. Go back to step 2.
21410. If step 1 said no, or you cannot find a real anchor in the data, return empty strings.
215 Never write "N/A", "unknown", "none", or a placeholder in brackets.
21611. Empty is only for "fits": "no", or data too vague to name a real thing. If "fits" is
217 "yes" you must write an anchor and a line. Low confidence is fine, write it anyway.
218 Marketing fluff in the data is normal. One real product word is enough to work with.
219 
220Return JSON only, no markdown fence:
221{"fits": "yes|no", "anchor": "<words copied from the company data, or empty>", "offer_item": "<one line copied from the offer list, or empty>", "line": "<merge field, or empty>", "confidence": "high|low"}
222```
223 
224Rule 9 is the one that does the most work. **"If your line needs one of these, your anchor was too
225vague. Go back to step 2."** A banned-phrase list alone just moves the fluff around; telling the
226model *why* it reached for the phrase sends it back to the real fix.
227 
228### Part 2 — the client offer block (fill once per client, then lock)
229 
230```text
231CLIENT OFFER (the company sending this email):
232Name: {one line, what kind of company they are, never the brand name}
233Sells to: {who they serve and roughly what size}
234What they actually do:
235- {capability 1, concrete, the words a customer would use}
236- {capability 2}
237- {capability 3}
238- {capability 4}
239- {capability 5}
240What they do NOT do: {the four or five nearby things people assume, listed plainly}
241```
242 
243Rules for this block, learned the hard way:
244 
245- **Five to seven capability lines**, each a thing a customer would recognize, **not a category.**
246- **The "do NOT do" line is load-bearing.** Without it the model borrows adjacent promises.
247- **The capability lines are copied verbatim into `offer_item`, so they double as the guard's
248 whitelist.** Write them as short, quotable phrases.
249 
250Worked example:
251 
252```text
253CLIENT OFFER (the company sending this email):
254Name: a US outsourced bookkeeping and fractional CFO firm
255Sells to: consumer product and ecommerce brands doing $2M to $50M a year
256What they actually do:
257- monthly close and clean books in QuickBooks or Xero
258- inventory and cost of goods sold tracking per product (per SKU landed cost)
259- margin reporting by product, by channel, and by sales platform
260- cash flow forecasting ahead of inventory buys
261- sales tax filing across states
262- getting books ready for a lender, a bank line, or an investor
263What they do NOT do: raise money, run ads, build software, fix supply chain, hire staff
264```
265 
266### Part 3 — few-shot examples (client-specific, faux prior turns, 8 to 10 pairs)
267 
268Alternating `user` and `assistant` messages, **never a bulleted list inside the system prompt.**
269 
270**Include at least two abstains:** one obvious non-fit (a school district, a city department) and one
271company whose description is pure fluff with no product word. **Without abstain examples the model
272learns that every row deserves a line.**
273 
274## 7. Edge cases and failure modes
275 
276| Symptom | Cause | Fix |
277|---|---|---|
278| Every lead's email opens with the same eight words in a custom field | The frame was put inside the variable | **The frame lives in the copy.** Only the tail is generated |
279| Lines are generic ("improve efficiency", "your products") | The anchor was a business word, not a real thing | Rule 9 sends the model back to step 2. **The banned list alone is not enough** |
280| The line promises growth the client cannot deliver | The offer block is missing its "do NOT do" line | Add it. The model borrows adjacent promises without it |
281| The line copies the offer list wording | Step 4 was skipped | The line must say the offer **in this company's nouns** |
282| Every row blanks on a reading-level gate | You gated the **rendered sentence**, not the tail. The frame alone scores 5.7 | **Gate the tail.** Consider shortening the frame |
283| The homepage fetch returns nothing on ecommerce sites | **Measured: 0 bytes, 16 bytes, 1 byte, connection refused** on four real brands | The rendering proxy, capped — or accept the abstain |
284| Two runs give different lines for the same row | Real non-determinism, measured | **Judge a batch, never a row** |
285| Over 30% blanks in a segment | **The list is wrong for this campaign**, not the prompt | Re-target, or route those rows elsewhere |
286| The Clay column costs 19x the estimate | A reasoning model with the level unset | Use a mini-class model in Clay |
287| `Specifically, I think we can help you .` | An empty variable inside a fixed frame | Route abstains out, or pre-render the whole sentence into one field |
288 
289### Hard rules
290 
291- **The model may only promise something on the client offer list.**
292- **The frame never lives inside the variable.**
293- **Gate the tail's reading level, not the rendered sentence's.**
294- **Judge a batch, never a row.**
295- **Spintax is never the blank-handling mechanism.**
296 

Discussion