Skip to Content
CampaignsCampaigns — AI Agents

Campaigns — AI Agents

Related: Data Model | Agents directory | Agent config seeding

All agents follow the DB-only system prompt pattern — system prompts are stored in agent_config and loaded at runtime. No hardcoded fallback strings. See agents repo memory for the pattern.


Quick Reference

New Workers (to build)

WorkerPurposeCampaign TypeQueue
campaign-brief-writerGenerate structured campaign briefAllagent__campaign-brief-writer
linkedin-ads-writerWrite LinkedIn ad copy variantspaid_adsagent__linkedin-ads-writer
review-campaign-writerGenerate review request drip sequencereview_generationagent__review-campaign-writer
audience-analystRecommend audience segmentsAllagent__audience-analyst
search-term-classifierClassify Google Ads search termspaid_ads (Google)agent__search-term-classifier
meta-ads-optimizerAnalyse Meta data, create recommendationspaid_ads (Meta)agent__meta-ads-optimizer
linkedin-ads-optimizerAnalyse LinkedIn data + demographicspaid_ads (LinkedIn)agent__linkedin-ads-optimizer
seo-outreach-optimizerAnalyse outreach + backlink healthseo_outreachagent__seo-outreach-optimizer
review-campaign-optimizerAnalyse drip stats + review velocityreview_generationagent__review-campaign-optimizer

Existing Workers (reused)

WorkerRoleCampaign Type
google-ads-writerWrite Google RSA copy (15 headlines, 4 descriptions)paid_ads
meta-ads-writerWrite Meta ad variants (3–5 angles)paid_ads
ads-analystAnalyse performance + recommend optimisationspaid_ads
google-ads-insightsGoogle campaign health insights → CampaignMetricspaid_ads
meta-ads-insightsMeta campaign performance insights → CampaignMetricspaid_ads
keyword-researcherPost-processes keyword output into Keyword + KeywordGroup rowspaid_ads (reused)
email-writerWrite email content (newsletters, promotional, nurture)email_marketing
social-post-writerWrite social post copysocial_media
social-post-designerGenerate image prompts / design directionsocial_media
backlink-outreach-writerPersonalise outreach emails per backlinkseo_outreach

New Workers — Detailed Specs

Content Creation Workers

These workers generate content (briefs, ad copy, drip sequences) that gets stored as campaign output.

campaign-brief-writer

Generates a structured campaign brief from the client’s goal and tenant context.

PropertyValue
Queueagent__campaign-brief-writer
Worker filepackages/agents/src/workers/campaign-brief-writer.worker.ts

Input payload:

{ tenantId: string; goal: CampaignGoal; // awareness | traffic | leads | sales | retention | reviews channels: string[]; // ["google_ads", "meta_ads"] targetAudienceDescription: string; budget?: number; budgetCurrency?: string; startDate?: string; endDate?: string; }

Output (stored in Campaign.brief):

  • Campaign objectives and success metrics (KPI targets)
  • Messaging pillars (3–5 core messages)
  • Audience breakdown (primary + secondary segments)
  • Recommended ad types / content formats per channel
  • Budget allocation recommendation (if budget provided)
  • Suggested A/B testing angles

linkedin-ads-writer

Creates LinkedIn ad copy variants. Fills the gap in the existing ads writer suite (Google and Meta writers already exist).

PropertyValue
Queueagent__linkedin-ads-writer
Worker filepackages/agents/src/workers/linkedin-ads-writer.worker.ts

Input payload:

{ tenantId: string; campaignId: string; brief: string; // from Campaign.brief productOrService: string; targetPersona: string; adFormats: string[]; // ["sponsored_content", "message_ad", "text_ad", "dynamic_ad"] variantCount?: number; // default 3 }

Output (stored as Activity output, linked via Activity.campaignId):

  • Per format: 3–5 ad variants each with headline, introductory text, CTA, destination URL placeholder
  • Persona-matched tone per variant

review-campaign-writer

Generates a review-request drip sequence for GBP review generation campaigns.

PropertyValue
Queueagent__review-campaign-writer
Worker filepackages/agents/src/workers/review-campaign-writer.worker.ts

Input payload:

{ tenantId: string; campaignId: string; businessName: string; reviewPlatform: string; // "Google" | "Trustpilot" | "G2" etc. reviewUrl: string; tone: string; // "friendly" | "professional" | "casual" stepCount?: number; // default 3, max 5 channel: "email" | "sms"; }

Output (stored in ReviewCampaign.sequence):

  • Array of steps: { stepOrder, delayDays, subject?, body, smsText? }
  • Each step: different angle (appreciation, ease-of-process, impact message)
  • SMS variants kept under 160 characters

Analysis & Planning Workers

These workers analyse data and classify inputs to inform campaign decisions.

audience-analyst

Recommends audience segments for a campaign based on CRM data and connected channel audience insights.

PropertyValue
Queueagent__audience-analyst
Worker filepackages/agents/src/workers/audience-analyst.worker.ts

Input payload:

{ tenantId: string; campaignId: string; goal: CampaignGoal; channels: string[]; leadSummary: string; // aggregated CRM lead segment summary channelAudienceData?: string; // platform audience data if available brief?: string; // campaign brief for context }

Output (stored as CampaignAudience records):

  • 2–4 recommended segments, each with:
    • name, rationale
    • Suggested filters (age, location, interests, lead tags)
    • Estimated performance notes per channel
    • Recommended budget split % if paid campaign

search-term-classifier

Classifies synced search terms from Google Ads into actionable categories. This is the AI step in the search term → negative keyword workflow. The output is reviewed by the DM before anything is pushed to the platform.

PropertyValue
Queueagent__search-term-classifier
Worker filepackages/agents/src/workers/search-term-classifier.worker.ts

Input payload:

{ tenantId: string; campaignId: string; searchTerms: { id: string; // SearchTermReport.id searchTerm: string; impressions: number; clicks: number; cost: number; conversions: number; ctr: number; matchType: string; }[]; campaignGoal: string; // for context: awareness | traffic | leads | sales etc. productOrService: string; // from tenant context file }

Output (stored as SearchTermClassification records, one per search term):

  • Per term: aiClassification (add_as_keyword | add_as_negative | watch | irrelevant)
  • Per term: aiRationale — one-sentence explanation (e.g. “High spend, zero conversions, unrelated to product”)
  • Terms are created with status: pending — no platform action until DM reviews

Workflow after classification:

search-term-classifier runs DM reviews in Keywords tab → Search Terms sub-view ├── Accept AI suggestion (bulk or per-term) ├── Override classification └── Skip term For each term marked `add_as_negative`: └─ NegativeKeyword record created (status: pending_push) └─ Push to Google Ads API via addNegativeKeyword() └─ externalCriterionId stored back └─ SearchTermClassification.status → pushed

Optimization Workers

These workers run as part of the weekly scheduler (or on-demand). They read stored metrics, apply threshold logic, and write CampaignOptimizationRecommendation records for HITL review. They never push directly to platform APIs.

meta-ads-optimizer

Reads synced Meta campaign data (ad sets, ads, recent metrics) and produces CampaignOptimizationRecommendation records. Runs automatically via the weekly scheduler job, or on demand.

PropertyValue
Queueagent__meta-ads-optimizer
Worker filepackages/agents/src/workers/meta-ads-optimizer.worker.ts

Input payload:

{ tenantId: string; campaignId: string; externalCampaignId: string; // from CampaignExternalMapping adAccountId: string; // from ConnectedChannel thresholdHits: { type: string; // creative_refresh | budget_shift | pause_placement | ... targetId: string; // adSetId or adId that triggered the threshold data: Record<string, unknown>; // the metric values that crossed the threshold }[]; }

What it does:

  1. Reads all MetaAdSet and MetaAd records for the campaign (with recent CampaignMetrics week-over-week trend)
  2. For each thresholdHit in the payload: calls the Meta API for latest context if needed, then writes one CampaignOptimizationRecommendation record per hit
  3. Sets priority based on spend impact (high > $500/week equivalent, medium $100–$500, low < $100)
  4. For creative_refresh type: also enqueues meta-ads-writer with the fatigued ad context as input — the brief, current headline/copy, and the platform feedback
  5. Does NOT push anything to Meta — just creates recommendation records

Output:

  • CampaignOptimizationRecommendation records (status: pending) visible in the Optimizations panel
  • For creative_refresh: a linked Activity (via Activity.campaignId) containing the draft ad variants from meta-ads-writer

linkedin-ads-optimizer

Reads synced LinkedIn campaign data (ads, demographic breakdown, lead gen form stats) and produces CampaignOptimizationRecommendation records.

PropertyValue
Queueagent__linkedin-ads-optimizer
Worker filepackages/agents/src/workers/linkedin-ads-optimizer.worker.ts

Input payload:

{ tenantId: string; campaignId: string; externalCampaignId: string; adAccountId: string; thresholdHits: { type: string; // creative_refresh | audience_tighten | bid_adjust | lead_form_refresh | ... targetId: string; data: Record<string, unknown>; }[]; }

What it does:

  1. Reads LinkedInAd records + LinkedInDemographicBreakdown for the campaign
  2. For audience_tighten hits: reads the demographic breakdown to identify the top-converting segment and writes a recommendation with a concrete targeting change — e.g. “Filter to Director and VP seniority only, estimated audience size: 12,000”
  3. For creative_refresh: enqueues linkedin-ads-writer with the current ad copy and performance context
  4. For lead_form_refresh: enqueues linkedin-ads-writer with format: lead_gen_form and the form completion rate data as context
  5. For bid_adjust: calculates target CPC based on campaign goal CPL and writes a bid recommendation with reasoning

Output:

  • CampaignOptimizationRecommendation records (status: pending)
  • Optional linked Activity for creative work (via linkedin-ads-writer)

seo-outreach-optimizer

Analyzes SEO outreach campaign sequence performance and acquired backlink health. Creates CampaignOptimizationRecommendation records when thresholds are crossed. Does not itself send emails or modify backlinks — it only writes recommendations for DM review.

PropertyValue
Queueagent__seo-outreach-optimizer
Worker filepackages/agents/src/workers/seo-outreach-optimizer.worker.ts

Input payload:

{ tenantId: string; campaignId: string; thresholdHits: { type: string; // refresh_outreach_template | add_follow_up_step | flag_dead_backlink | reprioritize_prospects | suggest_disavow targetId: string; // sequenceStepId or BacklinkHealth.id data: Record<string, unknown>; // the metric values that triggered the threshold }[]; }

What it does:

  1. For refresh_outreach_template: reads the current step’s subject/body, open/click rate history, and the campaign brief — then writes a recommendation with a rewrite rationale. Optionally enqueues backlink-outreach-writer to generate a revised email draft
  2. For add_follow_up_step: analyzes the last step sent, the prospect context, and the time elapsed — writes a recommendation with a suggested new step angle (e.g. “social proof angle”, “resource offer”, “direct ask”)
  3. For flag_dead_backlink: reads the BacklinkHealth record (domain, last HTTP status, DA, anchor text) — writes a recommendation with re-engagement context, e.g. “This DA 52 backlink from domain.com went dead 3 days ago — recommend re-outreach with updated content offer”
  4. For reprioritize_prospects: reads the full target list DA scores and writes a re-ordered priority recommendation
  5. For suggest_disavow: reads the suspicious backlink pattern data and writes a recommendation with the domain list and rationale

Output:

  • CampaignOptimizationRecommendation records (status: pending)
  • Optional linked Activity for revised email drafts (via backlink-outreach-writer)

review-campaign-optimizer

Analyzes review generation campaign drip sequence performance and incoming review velocity/sentiment. Creates CampaignOptimizationRecommendation records to improve conversion rates and surface quality issues.

PropertyValue
Queueagent__review-campaign-optimizer
Worker filepackages/agents/src/workers/review-campaign-optimizer.worker.ts

Input payload:

{ tenantId: string; campaignId: string; thresholdHits: { type: string; // refresh_review_sequence_step | re_engage_non_responders | adjust_send_schedule | shift_review_platform | flag_negative_review_pattern targetId: string; // sequenceStepId or ReviewMetrics.id data: Record<string, unknown>; }[]; }

What it does:

  1. For refresh_review_sequence_step: reads the step body, subject, send stats, and business context — writes a recommendation with a rewrite rationale. Enqueues review-campaign-writer for a fresh draft of that specific step (not the whole sequence)
  2. For re_engage_non_responders: reads contact list completion stats and writes a recommendation for a one-off re-engagement email with a different angle (e.g. “your opinion matters” vs original “leave us a review”)
  3. For adjust_send_schedule: analyzes clickCount patterns by day/hour across all steps and writes a recommendation for the optimal send window
  4. For shift_review_platform: compares ReviewMetrics.totalReviewCount across platforms and writes a recommendation to redirect the next batch to the lagging platform
  5. For flag_negative_review_pattern: reads the ReviewMetrics.keyThemes and sentimentScore data — writes a high-priority recommendation with the recurring negative theme extracted, e.g. “3 of 5 recent reviews mention ‘slow response time’ — this may need an operational fix before continuing review generation”

Output:

  • CampaignOptimizationRecommendation records (status: pending)
  • flag_negative_review_pattern recommendations are always set to priority: high regardless of review count

Agent Config Seeding

Add AgentConfig rows for the nine new workers in packages/db/src/seed.ts:

{ role: 'campaign-brief-writer', model: 'claude-3-5-sonnet', systemPrompt: '...' }, { role: 'linkedin-ads-writer', model: 'claude-3-5-sonnet', systemPrompt: '...' }, { role: 'review-campaign-writer', model: 'claude-3-5-sonnet', systemPrompt: '...' }, { role: 'audience-analyst', model: 'claude-3-5-sonnet', systemPrompt: '...' }, { role: 'search-term-classifier', model: 'claude-3-5-sonnet', systemPrompt: '...' }, { role: 'meta-ads-optimizer', model: 'claude-3-5-sonnet', systemPrompt: '...' }, { role: 'linkedin-ads-optimizer', model: 'claude-3-5-sonnet', systemPrompt: '...' }, { role: 'seo-outreach-optimizer', model: 'claude-3-5-sonnet', systemPrompt: '...' }, { role: 'review-campaign-optimizer', model: 'claude-3-5-sonnet', systemPrompt: '...' },

Run pnpm --filter @leadmetrics/db db:seed to populate.


Skill Files

Skills are Markdown (or JS) files injected into the agent’s working directory at runtime. They give the agent domain-specific rules, formats, and benchmarks without bloating the system prompt.

The skill system lives in packages/agents/src/skills.ts (directory builder) and packages/db/src/seed.ts (PLATFORM_SKILLS array). Skills are stored in the skill table and mapped to agents via agent_skill.

New Skills Required

These skill files need to be written and added to the PLATFORM_SKILLS seed array:

campaign_brief_guide.md

PropertyValue
Categorystrategy
Rolescampaign-brief-writer

Content to cover:

  • Brief structure per campaign type (paid_ads, email_marketing, seo_outreach, review_generation, social_media)
  • KPI target ranges per goal (awareness → impressions/reach; leads → CPL; sales → ROAS/CPA; reviews → review count/velocity)
  • How to write messaging pillars (3–5, benefit-led, not feature-led)
  • Budget allocation guidance per channel (e.g. 70% search / 30% display for Google; split across ad sets for Meta)
  • Output format — what a complete brief looks like

linkedin_ads_guide.md

PropertyValue
Categorycontent
Roleslinkedin-ads-writer, linkedin-ads-optimizer

Content to cover:

  • Format types and character limits: Sponsored Content (headline 70 chars, intro 150 chars), Message Ads (subject 60 chars, body 500 chars), Text Ads (headline 25 chars, copy 75 chars), Dynamic Ads, Lead Gen Forms
  • B2B copywriting principles — professional tone, persona-specific value props, avoiding B2C-style urgency
  • Lead Gen Form best practices: form field count (≤5 for best completion rate), pre-filled fields, thank-you message
  • Creative fatigue benchmarks: frequency > 2.0 on B2B audiences signals burnout — rotate creative every 2–3 weeks
  • Bid strategy guidance: Manual CPC vs Max Delivery vs Target Cost — when to use each

ppc_search_intent_guide.md

PropertyValue
Categoryseo
Rolessearch-term-classifier

Content to cover:

  • Classification decision tree: add_as_keyword | add_as_negative | watch | irrelevant
  • Signals for each class:
    • add_as_keyword: high impressions + clicks + conversions, relevant to product, not already in keyword list
    • add_as_negative: irrelevant industry, competitor name (unless conquest campaign), navigational terms for other brands, junk queries (e.g. “free”, “DIY”, “jobs”)
    • watch: low data volume (< 5 impressions), ambiguous intent, relevant but zero conversions so far
    • irrelevant: clearly unrelated to the business — skip without logging
  • Match type recommendation when adding as keyword (broad → phrase → exact based on volume + intent specificity)
  • Industry-specific negative keyword patterns (e.g. medical: exclude symptoms/diagnosis terms; legal: exclude DIY legal advice queries)

review_request_guide.md

PropertyValue
Categorygeneral
Rolesreview-campaign-writer, review-campaign-optimizer

Content to cover:

  • Review request drip structure: 3–5 steps, each with a different angle
    • Step 1 (Day 0–1): Appreciation + ease — “It only takes 60 seconds”
    • Step 2 (Day 5–7): Social proof + impact — “Your review helps others decide”
    • Step 3 (Day 10–14): Direct ask + urgency — “We’d really love to hear from you”
  • Per-platform link format: Google (https://g.page/r/{place_id}/review), Trustpilot, G2
  • SMS vs email: SMS kept under 160 characters; include review link; no subject line
  • What NOT to say: incentivising reviews (violates platform policies), asking to “only leave 5 stars”, asking to remove a negative review
  • Re-engagement angles for non-responders: different value framing, different CTA button text
  • Timing best practices: send within 24 hours of service completion for highest conversion

PropertyValue
Categorycontent
Rolesmeta-ads-optimizer, linkedin-ads-optimizer

Content to cover:

  • Creative fatigue decision framework: when to refresh vs when to pause outright
  • Budget rebalance criteria: ROAS comparison across ad sets, how to frame the shift recommendation (specific $ amounts, not percentages)
  • Placement analysis: which placements consistently underperform and why (Audience Network CTR is typically 60–70% lower than Feed)
  • How to write a recommendation rationale: lead with the data, state the impact, give the specific action, note the risk if not actioned
  • Priority scoring: high = actively wasting spend; medium = opportunity cost; low = preventive
  • Audience expansion triggers: when frequency capping is the problem vs when creative burnout is the problem (different fixes)
  • LinkedIn demographic opportunity framing: how to present a tighten-audience recommendation with the specific segment data

Note: An outreach email writing skill likely already exists partially. This entry documents what it should contain once extended.

PropertyValue
Categoryseo
Rolesbacklink-outreach-writer, seo-outreach-optimizer

Content to cover:

  • Cold outreach email structure: personalised opener (reference something specific on their site), value proposition (what they get from linking), ask (specific and low-friction), social proof
  • Character limits: subject 50–60 chars, body 100–200 words (shorter wins in cold outreach)
  • Follow-up timing: 5 days after first email; 7 days after second; max 3 touches before marking as dead
  • Follow-up angles: Step 2 — add value (share a resource), Step 3 — final ask with different framing
  • Dead backlink re-engagement: reference the existing relationship, suggest updated content to replace the dead link, keep it very short
  • Signals that a prospect is worth re-engaging vs worth retiring (DA drop, domain sold, niche diverged)

Existing Skills — New Roles to Add

These skills already exist in PLATFORM_SKILLS but need the following new agent roles added to their roles array:

Skill fileAdd these roles
ad_copy_frameworks.mdlinkedin-ads-writer
reporting_standards.mdmeta-ads-optimizer, linkedin-ads-optimizer, seo-outreach-optimizer, review-campaign-optimizer
deliverable_types.mdcampaign-brief-writer

Standard Dataset Access

Update STANDARD_DATASETS in packages/feature-knowledge/src/knowledge.types.ts to grant the new workers access to the relevant RAG datasets:

DatasetAdd these roles
client_docsAll 9 new workers (they all need brand voice + product context)
website_contentcampaign-brief-writer, audience-analyst, seo-outreach-optimizer
published_contentcampaign-brief-writer, meta-ads-optimizer, linkedin-ads-optimizer

© 2026 Leadmetrics — Internal use only