Personalization subagent pattern skill

Reusable approval-loop pattern for fanning out lead personalization across parallel Claude Code Task sub-agents.

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

Use now

Files of Personalization subagent pattern

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

Personalization Sub-Agent Pattern

Cold email personalization at scale requires per-lead generation. Claude Code's Task tool lets you fan out to many sub-agents in parallel, each personalizing a slice of the lead list. This skill defines the reusable approval-loop pattern.

Always Task tool — never an API key

This skill runs entirely inside Claude Code via the Task tool. No Anthropic SDK calls, no OpenAI calls. This is intentional:

  • No extra API spend. Uses your Claude Code plan.
  • No key management. Works out of the box.
  • Parallel by design. Claude Code spawns multiple Task sub-agents in one message, letting 100 leads finish in the time it takes to personalize 10.

At very large scale (1,000+ leads), the tuned prompt can optionally be shipped to the Anthropic API for throughput. But TUNING and normal campaign runs (under 500 leads) always go through the Task tool.

The approval loop (before full fan-out)

Don't personalize 500 leads and then discover the prompt is wrong. Loop first, then scale.

Round 0 — Sample on 1 lead
  1. Pick one lead from the batch with a rich company_description.
  2. Show the user: "Here's the company description I'm working with. Based on this, what would you say to personalize?"
  3. Display what YOU (Claude) would generate for situation_line, value_line, cta_soft.
  4. Ask: "Does this feel right? Edit it, and I'll re-tune."
Round 1-N — Batch of 10 with approval
  1. Spawn one Task sub-agent with the current prompt + 10 leads.
  2. Display all 10 results in a table:
    Lead                  | situation_line                          | value_line                    | cta_soft
    [email protected]         | You're building the only APM for Ruby.  | Our Ruby customers find ...   | Worth 10 min?
    [email protected]      | ...                                     | ...                           | ...
    
  3. Ask: "Any edits? Point at the row number and say what's wrong."
  4. If the user has edits, update the prompt (or add rules like "never use the word X") and re-run a new batch.
  5. If the user has zero edits for 2 consecutive rounds, the prompt is locked. Scale to the full list.
Scale — Full fan-out

Once locked:

  • Split remaining leads into batches of 10-20.
  • Launch 3-10 parallel Task sub-agents (one per variant × batch).
  • Merge results by lead_id.

When to use

  • Any campaign where per-lead custom variables are needed (beyond just {first_name})
  • When you have 50+ leads and want personalization without manual writing
  • When /auto-research-public or similar orchestration skills need parallel personalization

Don't use this for

  • Small batches (<10 leads) — just personalize inline in the main conversation (no fan-out needed)
  • Static copy (same email to every lead) — personalization wastes tokens

When to use

  • Any campaign where per-lead custom variables are needed (beyond just {first_name})
  • When you have 50+ leads and want personalization without manual writing
  • When /auto-research-public or similar orchestration skills need parallel personalization

Don't use this for

  • Small batches (<20 leads) — just personalize inline in the main conversation
  • Static copy (same email to every lead) — personalization wastes tokens

The pattern

1. Prepare the lead batch

Before fanning out, your lead batch should be a JSON array where each lead has:

{
  "lead_id": "<stable identifier>",
  "first_name": "<str>",
  "last_name": "<str>",
  "email": "<str>",
  "company_name": "<str>",
  "company_domain": "<str>",
  "company_description": "<1-3 sentences about what the company does>",
  "title": "<str>",
  "linkedin_url": "<optional>",
  "enrichment_data": { ... any extra signals ... }
}

The richer the company_description, the better the personalization. If you only have company names, the output will be generic.

2. Define the output schema

Decide up front what fields each sub-agent must return. Example:

{
  "lead_id": "<same id>",
  "situation_line": "<1 sentence — what you noticed about their company>",
  "value_line": "<1 sentence — connecting their situation to your offer>",
  "cta_soft": "<1 sentence — soft ask, e.g. 'worth a 15 min chat?'>"
}

Fewer fields = less that can go wrong. Default to 3 fields maximum per variant.

3. Split into variants (A/B/C)

If testing 3 copy variants, run 3 parallel sub-agents per company (or per batch). Each gets a different angle prompt:

  • Variant A: Lead with a pain observation. "Noticed X on your site..."
  • Variant B: Lead with a compliment + transition. "Your approach to Y is unique..."
  • Variant C: Lead with a question. "How are you thinking about Z?"

This gives you 3x the data from one list — you can A/B/C test which angle resonates.

4. Batch size
  • Small batches are the right default: 10-20 leads per sub-agent.
  • Bigger batches = fewer agents = cheaper but worse quality (agent loses context)
  • Smaller batches = more agents = higher quality but more context usage in parent

For 100 leads:

  • 10 sub-agents × 10 leads = good quality
  • 5 sub-agents × 20 leads = faster
  • 2 sub-agents × 50 leads = quality drops

For 1000 leads:

  • Consider running in rounds of 100 (to avoid hitting context limits in parent)
  • Each round launches 10 sub-agents of 10 leads each
5. The sub-agent prompt template

Every personalization sub-agent gets a prompt of this shape:

You are personalizing cold email fields for N leads.

CONTEXT:
- We sell: <one sentence from client-profile.yaml>
- Our ICP: <one sentence>
- Our offer: <the CTA we're asking them to respond to>
- Tone: <casual | formal | peer-to-peer>

FIELDS TO GENERATE (per lead):
- situation_line: <definition + 1 good example + 1 bad example>
- value_line: <definition + 1 good example + 1 bad example>
- cta_soft: <definition + 1 good example + 1 bad example>

RULES:
1. Never fabricate facts. If the company description is thin, say something generic but not false.
2. Never use em dashes (—). Use periods or commas.
3. Never use the word "leverage", "synergy", "ecosystem".
4. Maximum length: <N words per field>.
5. If a lead is missing company_description, return "<fields cannot be generated — skip>"

LEADS:
<JSON array>

RETURN:
A JSON array with the same lead_ids and the personalization fields. Save to /tmp/personalization-<batch-id>.json and print "DONE" when complete.
6. Fan-out code pattern

Pseudocode for the orchestrator (runs in the main Claude Code conversation):

leads = load leads from JSON
batches = chunk leads into groups of 10-20

for each batch:
  for each variant in [A, B, C]:
    Task(
      description: "Personalize batch <i> variant <v>",
      subagent_type: "general-purpose",
      prompt: <template above with variant-specific angle>
    )

# All tasks run in parallel (multiple tool calls in one message)

Wait for all tasks to finish, then read /tmp/personalization-*.json and merge.

Or, launching all in a single message with multiple Task calls:

# Launch 3 parallel sub-agents for one batch (variants A, B, C)
Task(description: "batch-1-variant-A", ...)
Task(description: "batch-1-variant-B", ...)
Task(description: "batch-1-variant-C", ...)
7. Error handling

Sub-agents can:

  • Return malformed JSON
  • Skip leads (if data is too thin)
  • Refuse to generate (if content feels risky)

The orchestrator should:

  1. Validate every returned JSON matches the output schema
  2. For missing lead_ids: retry once with a "strict mode" prompt that emphasizes no-skip
  3. For leads that genuinely can't be personalized (missing description): mark as personalization_status: "skipped" and use static copy instead

Never ship personalization fields that contain the string "cannot be generated" or similar — filter these out before upload.

8. Merge and upload

After all sub-agents complete:

  1. Read /tmp/personalization-*.json files
  2. Merge by lead_id
  3. Each lead now has:
    {
      ...original lead fields,
      "variant_a": { "situation_line": "...", "value_line": "...", "cta_soft": "..." },
      "variant_b": { ... },
      "variant_c": { ... }
    }
    
  4. When uploading to Smartlead/Instantly, map each field to a custom variable. Convention:
    • Smartlead: use {{situation_line_a}}, {{value_line_a}}, etc.
    • If running 3 A/B/C campaigns, upload variant_a fields to campaign A, variant_b to B, etc.

Approval loop stop rule

The loop exits automatically when:

  • The user gives zero corrections for 2 consecutive rounds of 10 leads, OR
  • The user explicitly says "lock it, scale up"

On stop:

  1. Save the final tuned prompt to ~/cold-email-ai-skills/profiles/<business-slug>/personalization-prompt.txt
  2. Save a client-profile.yaml metadata entry:
    personalization_prompt:
      path: profiles/<slug>/personalization-prompt.txt
      variant_count: 3
      tuned_at: YYYY-MM-DD
      rounds_to_convergence: 3
    
  3. Launch the parallel fan-out on the remaining leads.

If the user gives edits on round N+1 after 2 approved rounds, that's fine — the counter resets, and the loop continues.

Quality checks

Before uploading, manually spot-check 5 random leads per variant. Common issues:

  • Repetition across leads (sub-agent wrote the same line 10 times) → retry that batch with diversity instruction
  • Factually wrong claims (company does X when they actually do Y) → strengthen "never fabricate" rule in prompt
  • Unnatural phrasing (AI-speak like "I was intrigued by..." every time) → add forbidden-phrases list
  • Hedging / vagueness ("Your company might be doing X...") → add rule "assert, don't hedge"

References

  • references/prompt-template.md — copy-pasteable prompt template
  • references/example-output.json — what a well-personalized batch looks like
  • references/failure-modes.md — common sub-agent failures and how to detect them

What to do next

This is a pattern doc, not a standalone skill. It's invoked by /auto-research-public and /campaign-copywriting when they need per-lead personalization at scale.

If you're reading this directly, you're probably designing a new campaign-orchestration flow — return to whichever skill sent you here.

  • /auto-research-public — the primary consumer of this pattern
  • /icp-onboarding — produces the client-profile.yaml the prompt pulls from
  • /cold-email-starter-kit references 03-campaign-copywriting.md for copy principles the prompt enforces
1---
2name: personalization-subagent-pattern
3description: Reusable approval-loop pattern for fanning out lead personalization across parallel Claude Code Task sub-agents. Shows the user 1 sample personalization, collects feedback, runs 10 more, approves, runs 10 more — stops when 2 consecutive rounds have zero edits, then scales to the full list. ALWAYS uses Claude Code Task tool sub-agents — never an external Anthropic/OpenAI API key. Use when any skill needs per-lead custom variables (situation lines, value lines, CTAs).
4---
5 
6# Personalization Sub-Agent Pattern
7 
8Cold email personalization at scale requires per-lead generation. Claude Code's Task tool lets you fan out to many sub-agents in parallel, each personalizing a slice of the lead list. This skill defines the reusable approval-loop pattern.
9 
10## Always Task tool — never an API key
11 
12This skill runs entirely inside Claude Code via the Task tool. No Anthropic SDK calls, no OpenAI calls. This is intentional:
13 
14- **No extra API spend.** Uses your Claude Code plan.
15- **No key management.** Works out of the box.
16- **Parallel by design.** Claude Code spawns multiple Task sub-agents in one message, letting 100 leads finish in the time it takes to personalize 10.
17 
18At very large scale (1,000+ leads), the tuned prompt can optionally be shipped to the Anthropic API for throughput. But TUNING and normal campaign runs (under 500 leads) always go through the Task tool.
19 
20## The approval loop (before full fan-out)
21 
22Don't personalize 500 leads and then discover the prompt is wrong. Loop first, then scale.
23 
24### Round 0 — Sample on 1 lead
25 
261. Pick one lead from the batch with a rich `company_description`.
272. Show the user: "Here's the company description I'm working with. Based on this, what would you say to personalize?"
283. Display what YOU (Claude) would generate for `situation_line`, `value_line`, `cta_soft`.
294. Ask: "Does this feel right? Edit it, and I'll re-tune."
30 
31### Round 1-N — Batch of 10 with approval
32 
331. Spawn one Task sub-agent with the current prompt + 10 leads.
342. Display all 10 results in a table:
35 ```
36 Lead | situation_line | value_line | cta_soft
37 [email protected] | You're building the only APM for Ruby. | Our Ruby customers find ... | Worth 10 min?
38 [email protected] | ... | ... | ...
39 ```
403. Ask: "Any edits? Point at the row number and say what's wrong."
414. If the user has edits, update the prompt (or add rules like "never use the word X") and re-run a new batch.
425. If the user has **zero edits for 2 consecutive rounds**, the prompt is locked. Scale to the full list.
43 
44### Scale — Full fan-out
45 
46Once locked:
47- Split remaining leads into batches of 10-20.
48- Launch 3-10 parallel Task sub-agents (one per variant × batch).
49- Merge results by `lead_id`.
50 
51## When to use
52 
53- Any campaign where per-lead custom variables are needed (beyond just {first_name})
54- When you have 50+ leads and want personalization without manual writing
55- When `/auto-research-public` or similar orchestration skills need parallel personalization
56 
57## Don't use this for
58 
59- Small batches (<10 leads) — just personalize inline in the main conversation (no fan-out needed)
60- Static copy (same email to every lead) — personalization wastes tokens
61 
62## When to use
63 
64- Any campaign where per-lead custom variables are needed (beyond just {first_name})
65- When you have 50+ leads and want personalization without manual writing
66- When `/auto-research-public` or similar orchestration skills need parallel personalization
67 
68## Don't use this for
69 
70- Small batches (<20 leads) — just personalize inline in the main conversation
71- Static copy (same email to every lead) — personalization wastes tokens
72 
73## The pattern
74 
75### 1. Prepare the lead batch
76 
77Before fanning out, your lead batch should be a JSON array where each lead has:
78 
79```json
80{
81 "lead_id": "<stable identifier>",
82 "first_name": "<str>",
83 "last_name": "<str>",
84 "email": "<str>",
85 "company_name": "<str>",
86 "company_domain": "<str>",
87 "company_description": "<1-3 sentences about what the company does>",
88 "title": "<str>",
89 "linkedin_url": "<optional>",
90 "enrichment_data": { ... any extra signals ... }
91}
92```
93 
94The richer the `company_description`, the better the personalization. If you only have company names, the output will be generic.
95 
96### 2. Define the output schema
97 
98Decide up front what fields each sub-agent must return. Example:
99 
100```json
101{
102 "lead_id": "<same id>",
103 "situation_line": "<1 sentence — what you noticed about their company>",
104 "value_line": "<1 sentence — connecting their situation to your offer>",
105 "cta_soft": "<1 sentence — soft ask, e.g. 'worth a 15 min chat?'>"
106}
107```
108 
109Fewer fields = less that can go wrong. Default to 3 fields maximum per variant.
110 
111### 3. Split into variants (A/B/C)
112 
113If testing 3 copy variants, run 3 parallel sub-agents per company (or per batch). Each gets a different *angle* prompt:
114 
115- **Variant A**: Lead with a pain observation. "Noticed X on your site..."
116- **Variant B**: Lead with a compliment + transition. "Your approach to Y is unique..."
117- **Variant C**: Lead with a question. "How are you thinking about Z?"
118 
119This gives you 3x the data from one list — you can A/B/C test which angle resonates.
120 
121### 4. Batch size
122 
123- **Small batches are the right default: 10-20 leads per sub-agent.**
124- Bigger batches = fewer agents = cheaper but worse quality (agent loses context)
125- Smaller batches = more agents = higher quality but more context usage in parent
126 
127For 100 leads:
128- 10 sub-agents × 10 leads = good quality
129- 5 sub-agents × 20 leads = faster
130- 2 sub-agents × 50 leads = quality drops
131 
132For 1000 leads:
133- Consider running in rounds of 100 (to avoid hitting context limits in parent)
134- Each round launches 10 sub-agents of 10 leads each
135 
136### 5. The sub-agent prompt template
137 
138Every personalization sub-agent gets a prompt of this shape:
139 
140```
141You are personalizing cold email fields for N leads.
142 
143CONTEXT:
144- We sell: <one sentence from client-profile.yaml>
145- Our ICP: <one sentence>
146- Our offer: <the CTA we're asking them to respond to>
147- Tone: <casual | formal | peer-to-peer>
148 
149FIELDS TO GENERATE (per lead):
150- situation_line: <definition + 1 good example + 1 bad example>
151- value_line: <definition + 1 good example + 1 bad example>
152- cta_soft: <definition + 1 good example + 1 bad example>
153 
154RULES:
1551. Never fabricate facts. If the company description is thin, say something generic but not false.
1562. Never use em dashes (—). Use periods or commas.
1573. Never use the word "leverage", "synergy", "ecosystem".
1584. Maximum length: <N words per field>.
1595. If a lead is missing company_description, return "<fields cannot be generated — skip>"
160 
161LEADS:
162<JSON array>
163 
164RETURN:
165A JSON array with the same lead_ids and the personalization fields. Save to /tmp/personalization-<batch-id>.json and print "DONE" when complete.
166```
167 
168### 6. Fan-out code pattern
169 
170Pseudocode for the orchestrator (runs in the main Claude Code conversation):
171 
172```
173leads = load leads from JSON
174batches = chunk leads into groups of 10-20
175 
176for each batch:
177 for each variant in [A, B, C]:
178 Task(
179 description: "Personalize batch <i> variant <v>",
180 subagent_type: "general-purpose",
181 prompt: <template above with variant-specific angle>
182 )
183 
184# All tasks run in parallel (multiple tool calls in one message)
185 
186Wait for all tasks to finish, then read /tmp/personalization-*.json and merge.
187```
188 
189Or, launching all in a single message with multiple Task calls:
190 
191```
192# Launch 3 parallel sub-agents for one batch (variants A, B, C)
193Task(description: "batch-1-variant-A", ...)
194Task(description: "batch-1-variant-B", ...)
195Task(description: "batch-1-variant-C", ...)
196```
197 
198### 7. Error handling
199 
200Sub-agents can:
201- Return malformed JSON
202- Skip leads (if data is too thin)
203- Refuse to generate (if content feels risky)
204 
205The orchestrator should:
2061. Validate every returned JSON matches the output schema
2072. For missing lead_ids: retry once with a "strict mode" prompt that emphasizes no-skip
2083. For leads that genuinely can't be personalized (missing description): mark as `personalization_status: "skipped"` and use static copy instead
209 
210Never ship personalization fields that contain the string "cannot be generated" or similar — filter these out before upload.
211 
212### 8. Merge and upload
213 
214After all sub-agents complete:
215 
2161. Read `/tmp/personalization-*.json` files
2172. Merge by `lead_id`
2183. Each lead now has:
219 ```json
220 {
221 ...original lead fields,
222 "variant_a": { "situation_line": "...", "value_line": "...", "cta_soft": "..." },
223 "variant_b": { ... },
224 "variant_c": { ... }
225 }
226 ```
2274. When uploading to Smartlead/Instantly, map each field to a custom variable. Convention:
228 - Smartlead: use `{{situation_line_a}}`, `{{value_line_a}}`, etc.
229 - If running 3 A/B/C campaigns, upload variant_a fields to campaign A, variant_b to B, etc.
230 
231## Approval loop stop rule
232 
233The loop exits automatically when:
234- The user gives **zero corrections for 2 consecutive rounds** of 10 leads, OR
235- The user explicitly says "lock it, scale up"
236 
237On stop:
2381. Save the final tuned prompt to `~/cold-email-ai-skills/profiles/<business-slug>/personalization-prompt.txt`
2392. Save a `client-profile.yaml` metadata entry:
240 ```yaml
241 personalization_prompt:
242 path: profiles/<slug>/personalization-prompt.txt
243 variant_count: 3
244 tuned_at: YYYY-MM-DD
245 rounds_to_convergence: 3
246 ```
2473. Launch the parallel fan-out on the remaining leads.
248 
249If the user gives edits on round N+1 after 2 approved rounds, that's fine — the counter resets, and the loop continues.
250 
251## Quality checks
252 
253Before uploading, manually spot-check 5 random leads per variant. Common issues:
254- **Repetition across leads** (sub-agent wrote the same line 10 times) → retry that batch with diversity instruction
255- **Factually wrong claims** (company does X when they actually do Y) → strengthen "never fabricate" rule in prompt
256- **Unnatural phrasing** (AI-speak like "I was intrigued by..." every time) → add forbidden-phrases list
257- **Hedging / vagueness** ("Your company might be doing X...") → add rule "assert, don't hedge"
258 
259## References
260 
261- `references/prompt-template.md` — copy-pasteable prompt template
262- `references/example-output.json` — what a well-personalized batch looks like
263- `references/failure-modes.md` — common sub-agent failures and how to detect them
264 
265## What to do next
266 
267**This is a pattern doc, not a standalone skill.** It's invoked by `/auto-research-public` and `/campaign-copywriting` when they need per-lead personalization at scale.
268 
269If you're reading this directly, you're probably designing a new campaign-orchestration flow — return to whichever skill sent you here.
270 
271## Related skills
272 
273- `/auto-research-public` — the primary consumer of this pattern
274- `/icp-onboarding` — produces the `client-profile.yaml` the prompt pulls from
275- `/cold-email-starter-kit` references `03-campaign-copywriting.md` for copy principles the prompt enforces
276 

Discussion

Alternatives

A/B Test SetupWhen the user wants to plan, design, or implement an A/B test or experiment, or build a growth experimentation program. Also use when the user mentions "A/B test," "split test," "experiment," "test this change," "variant copy," "multivariate test," "hypothesis," "should I test this," "which version is better," "test two versions," "statistical significance," "how long should I run this test," "growth experiments," "experiment velocity," "experiment backlog," "ICE score," "experimentation program," or "experiment playbook." Use this whenever someone is comparing two approaches and wants to measure which performs better, or when they want to build a systematic experimentation practice. For tracking implementation, see analytics. For page-level conversion optimization, see cro.Marketing · MITAb test analysisAnalyze A/B test results with statistical significance, sample size validation, confidence intervals, and ship/extend/stop recommendations. Use when evaluating experiment results, checking if a test reached significance, interpreting split test data, or deciding whether to ship a variant. · MITAd Copy Generator + A/B TesterGenerate and A/B test Google Ads copy. Use when asked to write ad copy, headlines, descriptions, create ad variants, test ad messaging, improve CTR, or generate RSA (Responsive Search Ad) components. Trigger on "ad copy", "write ads", "headlines", "descriptions", "RSA", "responsive search ad", "ad text", "ad creative", "improve CTR", "ad A/B test", "ad variants", "write me an ad", "ad variation experiment", or when the user wants to improve click-through rate on existing ads.Marketing · MITA/B Test Planner SkillDesign statistically rigorous A/B tests for product features, UI changes, onboarding flows, and pricing experiments. Use when asked to set up an experiment, design an A/B test, calculate sample size, or interpret test results. Produces a complete test plan with hypothesis, variant definitions, sample size, duration estimate, guardrail metrics, and a results interpretation guide.Marketing · MIT