Each agent in this playbook is a skill, and the skills share one brain. You spend your time editing what the agents produce, not producing it yourself.
The cost of an experiment keeps collapsing. Whoever runs the most good experiments learns fastest. Most ideas die waiting for a designer, a developer, an analyst and two meetings. This system removes that wait.
Requires: Cogny ($9/mo) — Sign up. Every agent reads from Cogny's managed MCPs. Free skills in this repo still work without it.
The system at a glance
| # | Agent (skill) | Reads | Writes | Cadence |
|---|---|---|---|---|
| 0 | /marketing-brain |
You (interview), CRM won/lost deals (HubSpot / Pipedrive / Upsales), store orders (Shopify / WooCommerce), Stripe | brain/company/*, brain/brand/* (human-approved) |
Once, then on every rejection |
| 1 | /customer-language |
CRM calls/meetings/notes, Crisp chats, Discord, YouTube comments, local transcripts | brain/evidence/customer-language/* |
Mon 07:00 |
| 2 | /campaign-ledger |
Google, Microsoft, Meta, LinkedIn, TikTok, X, Reddit, Snapchat, Apple Search, ChatGPT ads, Bidtheatre; GA4; CRM; fibbler | brain/campaigns/ledger |
Daily |
| 3 | /buying-triggers |
fibbler warm accounts, CRM accounts (HubSpot / Pipedrive / Upsales), Upsales site visits, Brave News | brain/evidence/triggers/*, draft outreach |
Tue + Thu |
| 4 | /search-demand |
Search Console, Bing Webmaster, Semrush / Ahrefs, AI visibility, Brave | brain/evidence/search/*, page briefs |
Wed |
| 5 | /creative-engine |
Brain + customer language + brand assets | Images (Cloud) or briefs (Solo), paused ads on Meta / LinkedIn / TikTok / Reddit / X / Snapchat | On demand |
| 6 | /ad-kill-rules |
Ad-level insights on every platform | Pauses losers (the only write) | Daily 08:00 |
| 7 | /closed-lost-revival |
CRM lost deals + their calls/notes (HubSpot / Pipedrive / Upsales) | brain/evidence/closed-lost/*, draft notes |
Weekly |
| 8 | /social-engine |
LinkedIn / X / YouTube organic stats, X search, Brave, Crisp, Discord | Draft posts (scheduled only after a yes) | Sun batch |
| 9 | /performance-memo |
Ledger + fibbler + CRM + Stripe/Fortnox + store revenue + PostHog/Mixpanel + GrowthBook | brain/memos/performance/* |
Mon 07:30 |
Build them one at a time. Get /customer-language reliable before adding the next.
A confused workflow running every morning gives you a reliable supply of confusion.
Shared conventions (every agent in this system follows these)
The brain lives in the Cogny context tree
brain/
START-HERE ← every agent reads this first
company/customer-fit ← who buys, who stays, who you can serve profitably
company/offers ← what you sell, price, allowed claims, claims needing approval
brand/voice ← 3 loved examples + why, 2 rejected + why
brand/founder-opinions ← strong takes nobody else can generate
decisions/failed-campaigns ← lessons that cost real money
decisions/agent-corrections ← every fix you made to an agent's judgment
evidence/customer-language/ ← latest + dated reports
evidence/triggers/ ← buying-trigger shortlists
evidence/search/ ← question inventory, AI-visibility snapshots
evidence/closed-lost/ ← ledger of real loss reasons + reopen conditions
evidence/social/ ← listening digests, organic hook results
campaigns/ledger ← one row per campaign ID
campaigns/<ID> ← the campaign folder: quotes, offer, assets, results
experiments/log ← tested / why / result / next
contracts/<agent> ← each agent's job description + guardrails
memos/performance/ ← Monday memos
The one rule that keeps the brain healthy: agents write to evidence/,
campaigns/, experiments/ and memos/. Only a human moves something into
company/ or brand/. An agent may propose an edit to those nodes as a ticket, but
must never write it unless the user says yes in the current session. Otherwise one
agent's hunch quietly becomes company policy.
Campaign IDs travel everywhere
Format: <PREFIX><NNN> (e.g. BK014). The ID goes in:
- UTM:
utm_campaign=bk014_<slug> - Every ad name:
BK014_<angle>_<format>_v<n>(e.g.BK014_dinner-angle_ugc_v2) - Every email/newsletter name and every scheduled organic post's tracking link
- The deal in the CRM: a
campaign_iddeal property in HubSpot (ask a HubSpot admin to create it), otherwise the contact's original-source UTM (hs_analytics_source_data_2)
/campaign-ledger owns ID allocation and the naming audit.
The review queue: tickets (Cloud) or findings (Solo)
Anything that needs a human decision becomes a review item. On Cloud that's create_ticket
(execution_target: "manual"), and the queue is read with list_tickets. On Solo it's
create_finding (put the urgency in priority and the category in action_type), and the queue
is read with list_findings. Use whichever tool the workspace has; the field mapping is in
references/mcp-routing.md section 2. Solo caps findings at 100, so merge stale ones before filing more. Each ticket must be decidable in under a minute:
- Title: the decision ("Approve: dinner-angle ads for BK014 (3 ads, paused)")
- Body: what changes, what it touches, and the evidence (quote + source)
- expected_outcome: measurable, with the evidence bar
- estimated_cost_usd + cost_cadence + cost_type: whenever approving it activates spend
Never use tickets for routine numbers. A quiet week should produce a short queue.
Title prefix: every review item an agent in this system files starts with [ME:<agent>]
(e.g. [ME:creative-engine] Approve: …), on either plan. The workspace queue also holds platform,
support and automation items, so week mode filters list_tickets / list_findings on this prefix.
Guardrails live in the tools, not the prompt
"Always ask before sending" is only a suggestion to a model. Real guardrails:
- Read-only by default. Every agent starts with read tools only.
- Create paused, never active. Ads are created with
status: PAUSED. Budgets stay with the human. - Draft, never send. Outreach and newsletters are drafts. Organic posts are scheduled only after an explicit yes.
- Scheduled agents get grants, not trust. When scheduling via
create_automation, grant only the specific write tools needed, pinned withconstraints(e.g. pinstatus: "PAUSED"onupdate_ad_statusso the kill-switch can pause but never activate). - Exact rules are code, judgment is model. Spend caps, suppression lists, dedupe and
CTR thresholds go in
create_script(deterministic JS). Fit and tone are judgment calls for the model. - "Done" means every source was checked, and any failure was reported. An agent that says "HubSpot
returned 3 empty call notes today" is worth ten that quietly skip them. Every output node ends
with a
Failures:line (orFailures: none), whichstatusmode reads.
Real-data rules (learned from live runs; every agent follows these)
- Big responses spill to a file. CRM searches with many properties, GSC at 5,000 rows and
Stripe lists can return 100k–400k characters. Page HubSpot searches with
after(≤100 per page) and request only the properties you need. When a result is saved to a file, process it withjqor a short script instead of reading it whole, and prefer smaller limits. - Never sum across currencies. Ad accounts may be SEK, Stripe may mix usd/sek/eur. Report in
each account's currency (read it from the platform:
customer.currency_code, account currency), and state any FX rate used. Some CRM amounts are in minor units (49900 = 499.00), so sanity-check against the price list. - CRM noise. Expect duplicate deals and companies, test records, and deals without
associated companies. Dedupe by HubSpot company id (or normalized domain), report raw vs unique
counts, and exclude deals whose reason or name says duplicate, test or merged. Deal stages are
pipeline-specific ids, so map them with
get_properties(deals, dealstage)before counting. - "Active" means delivering. Meta: use
effective_status(anACTIVEad under a paused campaign isn't live). Google: require ad, ad group and campaignENABLED. X: check the parent campaign and line item. - First run has no brain. When a
brain/node is missing, say so once, use the stated fallback (usually the CRM's call and meeting notes, or asking the user), and label the output "no brain: provisional".
The CRM layer
The CRM is where customer truth lives: who bought, who was lost and why, what they said on calls, and which campaign they came from. HubSpot is the default. Connect it in Cogny and every agent uses it read-only. Bigger CRMs map onto the same jobs:
| Job | HubSpot (search_crm_objects) |
Pipedrive (Pipedrive) |
Upsales (Upsales) |
Salesforce (custom MCP) |
|---|---|---|---|---|
| Won deals | deals: hs_is_closed_won = true, closedate, amount, deal_currency_code |
list_deals(status: "won"): value, currency, won_time |
list_orders with probability = 100, date, value, currency |
Opportunity: IsWon = true, CloseDate, Amount |
| Lost deals + reason | deals: hs_is_closed = true AND hs_is_closed_won = false, closed_lost_reason |
list_deals(status: "lost"), then get_deal for lost_reason, lost_time |
list_orders with probability = 0; reason in a custom field (find it with get_metadata) |
Opportunity: IsClosed AND NOT IsWon, Loss_Reason__c |
| Open pipeline | deals: hs_is_closed = false; stage labels via search_properties |
list_deals(status: "open"), list_stages |
list_opportunities (probability < 100) |
Opportunity: IsClosed = false |
| Call / meeting words | calls (hs_call_body), meetings (hs_meeting_body, hs_internal_meeting_notes), notes (hs_note_body) |
list_activities(deal_id / org_id): subject, note |
list_activities, list_appointments (notes, description) |
Task, Event (Description) |
| Support pain | tickets (subject, content) |
— (use Crisp) |
— (use Crisp) |
Case |
| Accounts | companies (domain, industry, numberofemployees, annualrevenue, lifecyclestage) |
list_organizations |
list_accounts |
Account |
| Opt-outs | contacts hs_email_optout, hs_lead_status |
person marketing_status (from list_persons) when present |
contact active, unsubscribed flags (via get_metadata) |
HasOptedOutOfEmail, DoNotCall |
| Last touch | notes_last_contacted, hs_last_sales_activity_timestamp |
latest list_activities(org_id) |
latest list_activities / list_appointments filtered by client.id |
LastActivityDate |
| Campaign source | contacts hs_analytics_source_data_2 (paid utm_campaign), deal property campaign_id |
deal custom field campaign_id (define it; see list_deal_fields) |
list_leads source, list_campaigns |
Opportunity.CampaignId |
| Site re-engagement | hs_analytics_last_visit_timestamp, recent_conversion_date |
— | list_visits (Upsales web tracking) |
Pardot fields |
Pipedrive lists are cursor-paginated (limit ≤ 500, cursor from next_cursor). Upsales uses
limit (≤ 1000) + offset and filters=[{"field": "client.id", "comparator": "eq", "value": …}];
its responses can be very large, so keep limit small. Salesforce CRM and Attio connect through
add_custom_mcp_server. Map the same jobs and write the mapping to brain/contracts/crm-layer
so every agent uses it. Fibbler's
deal attribution (get_influenced_deals, get_deal_attribution_summary) takes
crm: "hubspot" | "salesforce" | "copper" | "attio", with no Pipedrive; only get_warm_accounts
accepts pipedrive. On Pipedrive or Upsales, join fibbler's warm accounts to CRM deals by domain yourself.
Read-only, always. No agent in this system writes to the CRM. Proposed fields (e.g.
campaign_id, reopen_condition) go to a HubSpot admin as a ticket.
Usage
/marketing-engineer setup— first-time setup (Step 1–3 below)/marketing-engineer week— run the Monday review: memo, queue, keep/kill/change/test/marketing-engineer campaign <idea>— run one campaign end to end across all agents/marketing-engineer status— what's built, what's stale, what's broken/marketing-engineer schedule— put the reliable agents on a schedule with grants
Prerequisites check
-
Connected. Call
cogny_statusandlist_integrations. If nomcp__cogny__*tools exist (nolist_integrations, nocogny_status), print this and switch to local mode (below):Marketing Engineer needs your marketing data through Cogny's MCP gateway. 1. Sign up at https://cogny.com and connect your CRM, ad platforms, Search Console and Stripe (whichever you use; each agent lists what it needs). 2. Add Cogny to .mcp.json in your project: {"mcpServers": {"cogny": {"type": "http", "url": "https://app.cogny.com/mcp"}}} 3. Restart Claude Code; it opens a browser for the OAuth login on first use. Without Cogny you can still: run /marketing-brain and /customer-language locally (files under ./brain/); the other agents need Cogny.Local mode. Without Cogny you can run
/marketing-brainand/customer-languagelocally (files under./brain/<node path>.md, e.g../brain/company/customer-fit.md); the other agents need Cogny.setupruns those two (see Mode: setup) and stops;week,campaign,statusandschedulestop after the block above. Skip steps 2–4 and the table. -
Plan (tier). Detect it from the tools that exist, since
cogny_statusdoesn't name it: Cloud ifcreate_ticket/list_ticketsexist, Solo ifcreate_finding/list_findingsexist. Automations and scripts are on iflist_automationsresponds. Print the plan on the last row of the table and use it to pick the review-queue tools everywhere below. -
Health.
list_integrationshealth_statusmisses revoked tokens (a 401 LinkedIn Ads token still readshealthy). For each integration an agent needs, make one cheap read. A 401, Forbidden or "lacks scope" means 🔴 "reconnect in Cogny"; don't retry the same call.Integration Cheap read HubSpot get_user_detailsPipedrive / Upsales list_users(Upsales:limit: 1)fibbler get_account_overviewGoogle / Meta / LinkedIn / Bing / TikTok / X / Reddit Ads tool_list_accessible_accounts/tool_list_ad_accounts/tool_list_ad_accounts/tool_list_accounts/tool_list_advertisers/tool_list_ad_accounts/tool_list_ad_accountsSnapchat / Apple Search / ChatGPT ads tool_list_organizations/tool_list_acls/tool_get_ad_accountGA4 / Search Console / Bing Webmaster tool_list_account_summaries/tool_list_properties/tool_get_sitesLinkedIn Organic / X Organic / YouTube tool_list_organizations/tool_get_me/get_channelDiscord / Crisp list_guilds/list_conversations(page 1)Stripe / Fortnox / Shopify stripe_api_readGetSubscriptions {limit: 1}(notget_stripe_account_info, which restricted keys can't call) /get_company_information/tool_get_shop_infoBrave Search, AI Visibility, Image Gen and Ahrefs-lite are Cogny-native and don't appear in
list_integrations. Treat them as available if their tools load. -
Missing tool. If a tool an agent needs isn't loaded, call
refresh_mcp_discoveryonce. New tools only appear after the client reconnects, so use that agent's fallback for this run, mark the source ⚠ "needs reconnect", and tell the user to reconnect the Cogny MCP.
Map channels to agents and print the table:
Agent Needs Status
customer-language CRM (HubSpot / Pipedrive / Upsales), Crisp, Discord, YouTube, or local transcripts ✅ Discord · ⚪ CRM
campaign-ledger ≥1 ad platform + GA4 or Plausible (+ CRM) ✅ (Google, Meta, Bing, TikTok, X) · 🔴 LinkedIn: reconnect
buying-triggers app-fibbler-co and/or CRM ✅ fibbler · ⚪ CRM
search-demand Search Console (+ Bing, Semrush/Ahrefs) ✅
creative-engine Meta / LinkedIn / TikTok / Reddit / X / Snapchat Ads ✅
ad-kill-rules any ad platform with a pause tool ✅
closed-lost-revival CRM (HubSpot / Pipedrive / Upsales) ⚪ connect a CRM in Cogny
social-engine LinkedIn Organic / X Organic / YouTube ✅
performance-memo ledger + Stripe / Fortnox / store platform ✅
Plan Solo: no BigQuery, scripts, automations, image gen · Cloud: all (detected: Cloud)
If a single source is missing or broken, mark that agent ⚪ ("connect X in Cogny") or 🔴 ("reconnect X in Cogny") and continue. Never fail the whole run.
Mode: setup
Local mode (no Cogny): check for ./brain/START-HERE.md (Glob ./brain/**/*.md); if it's missing, run
the /marketing-brain interview, writing to ./brain/. Then run /customer-language on the user's local
transcripts and have them compare it with their own memory of those calls; save corrections to
./brain/decisions/agent-corrections.md. Then stop: the remaining steps need Cogny.
- Desk. Confirm
.mcp.jsonhas the Cogny server and the skills are installed. - Brain. Call
get_context_tree_overview. Ifbrain/START-HEREis missing, run the/marketing-braininterview now. Nothing else is worth building on a thin brain. - Data. Run
/campaign-ledger initto pick a campaign-ID prefix, audit current naming across platforms, and createbrain/campaigns/ledger. - Contracts. For each agent the user wants, write
brain/contracts/<agent>using the template below. - First agent. Run
/customer-languageand have the user compare it with their own memory of those calls. Save every correction tobrain/decisions/agent-corrections. - Stop there. Tell the user to add the next agent only once this report is trusted (usually 3–4 rounds).
Contract template (brain/contracts/<agent>)
# <Agent> contract
Job: <one sentence>
Reads: <sources + limits, e.g. "HubSpot calls + meetings, last 7 days, max 200">
May write: <exact nodes / draft locations>
May NOT: <e.g. send email, activate ads, edit company/*>
Evidence bar: <what must be true before it recommends something>
Done means: every source checked; failures listed by name; output saved to <node>
Escalates via: create_ticket (manual) when <condition>
Eval set: brain/contracts/<agent>/evals — 10 example inputs with expected outputs
Mode: week (the Monday loop)
- Read the latest
brain/memos/performance/*(run/performance-memoif none exists for this week). - Read the open queue. Cloud:
list_ticketshas no title search and returns at most 100 results without paging, so call it withexecution_target: "manual"and the open statuses (new,assigned,doing,review) andlimit: 100(the default is 20), then keep titles starting with[ME:client-side. If 100 come back, say the queue may be truncated. Solo:list_findings, filtered the same way. Group them into Approve, Decide and FYI. (analysismeans shipped and being measured; list those under FYI, not as done.) - For each memo line, ask the user for the call: keep / kill / change / test. Record it in
brain/experiments/logas: date, campaign ID, decision, reason, next test. - For every rejection the user makes, ask why and write the answer where the next run sees it
(customer-fit, offers, voice, failed-campaigns or agent-corrections). Propose
company/andbrand/edits and apply them only after a yes. - Print the week's plan, following Greg's rhythm: Mon decide, Tue–Wed build and launch, Thu sit in on sales and support calls, Fri review agent output and update the brain.
If the user spends most of the session fixing agent output, say so: the problem is upstream, in the brain or the contract.
Mode: campaign
The end-to-end run. The customer's words have to survive every step.
- Evidence. Read
brain/evidence/customer-language/latest. Find the quote cluster behind the idea and say who said it: customers, prospects, community members, or people replying to ads (ad repliers aren't customers). If fewer than ~3 independent customers or prospects said it, say so and ask whether to proceed. - Offer check (a gate). Read
brain/company/offers. If the idea needs a new offer or claim, draft it and open "[ME:marketing-engineer] Approve offer: for ". The human walks it over to whoever delivers the service and confirms the company can actually deliver it. Don't fan out until the offer is approved (in this session, or the ticket is approved). Until then, only/search-demand pageand/social-engine hooksmay draft, labelled "pending offer". - ID.
/campaign-ledger new "<name>"→ allocates e.g.BK014and createsbrain/campaigns/BK014holding the verbatim quotes, the approved offer, the audience and the evidence bar (spend, days, win metric). - Fan out. Each agent writes its own section of
brain/campaigns/<ID>(the canonical template is in/campaign-ledger), or one line, "skipped: ", when it can't run:/creative-engine <ID>→ 3+ distinct angles, created paused/search-demand page <ID>→ one page brief or decision tool (calculator, checker)/closed-lost-revival match <ID>→ old prospects whose reopen condition this campaign meets/buying-triggers <ID>→ accounts with a fresh trigger that fits this angle/social-engine hooks <ID>→ organic hooks to test before paying for them
- Review in one sitting. The agents file their own tickets. Add one index ticket,
"[ME:marketing-engineer] Review bundle: ", that links them in approval order (offer → creative
→ page → hooks → outreach), lists skipped agents, and states what's still missing (budget,
screenshots, evidence bar). Cut anything that promises what
offersdoesn't allow. - After launch.
/ad-kill-rulesenforces the evidence bar and/performance-memoreports back with the ID joined from spend → calls → deals → revenue. Remind the user when the sample is still small.
Mode: status
For each agent, read its latest output node and report its age, its last run's Failures:
line, and whether a contract and eval set exist. Output nodes:
| Agent | Latest output |
|---|---|
| customer-language | brain/evidence/customer-language/latest |
| campaign-ledger | brain/campaigns/ledger |
| buying-triggers | newest brain/evidence/triggers/* |
| search-demand | brain/evidence/search/latest |
| creative-engine | newest ## Assets section in brain/campaigns/* |
| ad-kill-rules | ## Kill log sections in brain/campaigns/*, plus its script runs (list_scripts) |
| closed-lost-revival | brain/evidence/closed-lost/ledger |
| social-engine | newest brain/evidence/social/* |
| performance-memo | newest brain/memos/performance/* |
Also list_automations / list_scripts to show what actually runs unattended. **Flag any |
|
existing automation whose write grants have no constraints** (e.g. a post or send tool that |
|
| can target anything) and any script paused after failures. |
Mode: schedule
Only for agents the user says are reliable.
create_automation, update_automation and run_automation are writes, so they are not in this
skill's allowed-tools: Claude Code asks before each call. Show the user the exact automation
(instructions, grants, constraints) and get a yes first.
On Solo there are no automations or scripts: schedule the skill itself with Claude Code's
/schedule (cloud routine) or /loop (in-session), using the same read-only discipline, and
let the skill file findings rather than act. Everything below is Cloud, and needs the
workspace's automations/scripts flag. For each one:
- Confirm with the user the exact write tools and targets to grant.
create_automationwithenabled: false, the agent's steps asinstructions, and the minimumaction_grants(withconstraints). Read tools are inferred from granted servers; passread_toolsexplicitly for agents that only read from other servers.- Test with
run_automation(dry_run: true), inspect withget_automation_run, and only then enable.
Suggested grants:
| Agent | Grants |
|---|---|
| customer-language, search-demand, performance-memo, closed-lost-revival | none (read-only; output is context nodes + tickets) |
| ad-kill-rules | Meta Ads/tool_update_ad_status pinned {"status":"PAUSED"}; equivalent pause-only grants on TikTok, LinkedIn, Google, Bing, Reddit, Snapchat (tool_update_ad_status) and ChatGPT ads (OpenAI Ads/tool_update_ad_status pinned {"status":"paused"}) |
| social-engine | none. Scheduling posts stays manual. |
| buying-triggers | none (drafts land in tickets) |
Prefer deterministic create_script for /ad-kill-rules and the ledger sync, because those rules are exact.
Critical rules
- Brain before agents. Thin context produces generic slop. Fix the brain first.
- One agent at a time. Each one has to earn trust before the next gets built.
- Humans own
company/andbrand/. Agents propose; humans promote. - Every campaign has an ID, and it appears in every name.
- Every review item takes under a minute and shows the change, what it touches and the evidence.
- Report failures by name. Silent skips are the worst bug.
Tool names and providers
Every tool, prefix and provider alternative is listed in
references/mcp-routing.md: naming (mcp__cogny__mcp__<Prefix>__<tool>),
what Solo vs Cloud can call, how to choose between connected providers, and "same job, different
provider" tables. If a tool you expect is missing, call refresh_mcp_discovery.