# Campaign Debrief

> Structured 5-question post-campaign learning capture (Q1 hypothesis, Q2 result, Q3 what-worked, Q4 surprise, Q5 transferable) that assigns one of four objective campaign verdicts (SCALE / ITERATE / PAUSE / KILL per ADR-018) against concrete numeric thresholds and appends an entry to `docs/campaigns/{short_entity}/learnings.md`. Serves BDRs, RevOps, and marketing operators closing the loop between campaign execution and campaign intelligence. Triggers on debrief, campaign debrief, retro, log campaign, capture learnings. Receives primary input from `campaign-analysis` via `analysis-*.md`; retroactive path pulls metrics standalone from Email Bison when no analysis artifact exists. Hands off transferable learnings to `message-market-fit` (ITERATE Notes column), `product-marketing-context` (cross-entity propagation proposals), and `/workflows:handbook-drift-check` (handbook-contradiction signals). Append-only, forever. Under 5 minutes per debrief. Adapted from Revgrowth1/ai-gtm-workflows workflow 12 (MIT).

- Skill: `brite-nites/campaign-debrief` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add brite-nites/campaign-debrief`
- Raw SKILL.md: https://api.skillmd.com/api/skills/brite-nites/campaign-debrief/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Marketing & Growth
- Author: Brite-Nites (https://skillmd.com/u/brite-nites)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/brite-nites/campaign-debrief

---


# Campaign Debrief

You are the debrief facilitator for Brite's marketing flywheel — the keystone skill that closes the loop between campaign execution and campaign intelligence. This skill serves BDRs, RevOps, and marketing operators whose problem is not that Brite lacks post-campaign analysis, but that today's insights from `campaign-analysis` evaporate before they shape the next campaign. Engineering runs a compound-knowledge flywheel through decision traces, a precedents INDEX, and the `/workflows:compound-learnings` command; marketing has had no parallel — this skill fills that gap with domain-native conventions. The outcome is an append-only `docs/campaigns/{short_entity}/learnings.md` per Brite entity, with each entry carrying one of four objective campaign verdicts (per ADR-018), four tag families, and a transferable-insight flag that routes cross-entity patterns to `product-marketing-context` proposals or handbook-drift signals. Under 5 minutes per debrief. Data suggests answers; operator confirms. Append-only, forever.

---

## Before Starting

Four gates resolve in order before any append to `docs/campaigns/{short_entity}/learnings.md`. Cross-references elsewhere in this skill (e.g. "§2 Gate 2" in §6 Procedure preconditions) point to the numbered gates below.

**Input validation.** Two tokens reach `Write` destinations and `Glob` patterns: `{entity}` (from operator confirmation at Gate 3) and `{campaign-name}` (from Gate 4 or the matched `analysis-*.md` filename at Gate 2). Both must pass the rules below before any `Read`, `Write`, `Glob`, or MCP tool interpolation — a poisoned token must not reach any tool call. `Read` is gated because Workflow 4 Step 5 (BC-8752) interpolates both tokens into the σ3 manifest read path.

- **`{entity}`** — must match `^(brite-nites|brite-supply|brite-labs|nites|supply|labs)$`. Both long-form (`brite-nites`/`brite-supply`/`brite-labs`) and short-form (`nites`/`supply`/`labs`) slugs are accepted for one release cycle per [BC-8719](https://linear.app/brite-nites/issue/BC-8719) (O15 migration). The skill internally normalizes to short-form `{short_entity}` by stripping the `brite-` prefix (`brite-nites` → `nites`; `nites` → `nites`) before any `Write` interpolation. Reject any other form. Gates every `Write` path under `docs/campaigns/{short_entity}/` and the workspace-routing dispatch at Gate 3. Backward-compat acceptance retires one release cycle after BC-8719.
- **`{campaign-name}`** — must match `^[a-z0-9-]{1,80}$`. Reject spaces, path separators (`/`, `\`), `..`, single quotes, semicolons, NUL, or any value longer than 80 characters (a 5,000-char hyphen-only value would pass character-class but breach SOQL length limits and produce oversized learnings.md entries). Gates the `analysis-*.md` `Glob` pattern at Gate 2 and the `Write` destination for learnings.md entries.

### Gate 1 — Marketing context (soft gate)

Check for product marketing context first. `Glob` for `docs/marketing-context.md`; on hit, `Read` it before asking questions and use that context for Brite entity selection, voice, and ICP. On miss, warn the user: "Marketing context doc not found — proceeding with reduced context. Run `/marketing:product-marketing-context` to generate it." Then continue using only user-provided information. Do NOT attempt `Read` on a file the `Glob` already reported missing.

### Gate 2 — Campaign analysis data availability (soft gate)

This gate decides which procedure runs. It does NOT halt on failure — both paths are first-class.

1. **Glob for analysis artifacts** — once `{entity}` is confirmed at Gate 3, run `Glob` for `docs/campaigns/{short_entity}/analysis-*.md`. On ≥1 match, route to §6 Procedure 1 (post-analysis debrief — happy path); auto-suggestions for Q1/Q2/Q3 draw from the matched artifact. On zero matches, route to §6 Procedure 2 (retroactive debrief — no artifact); metrics pull standalone from Email Bison at §5 Workflow 2.
2. **Do not halt.** Retroactive debrief is a first-class flow per the Scope doc — operators routinely run this skill on campaigns that pre-date the `campaign-analysis` ship, or on campaigns whose analysis artifact was lost. Missing artifact is not an error; it selects Procedure 2.

### Gate 3 — Entity identification

Use `AskUserQuestion` to confirm the Brite entity for this debrief (Nites / Supply / Labs). The answer gates two downstream behaviors:

- **Output path.** Every `Write` targets `docs/campaigns/{short_entity}/` where `{short_entity}` is the short-form slug derived by stripping the `brite-` prefix from the validated `{entity}` answer (`brite-nites` → `nites`, `brite-supply` → `supply`, `brite-labs` → `labs`; short-form inputs pass through unchanged). The directory is created on first write. Per BC-8719 (O15 migration), the canonical filesystem layout is short-form; legacy long-form (`docs/campaigns/brite-{short_entity}/`) is read-compatible for one release cycle via two backward-compat shims — (1) Workflow 4 Step 1's legacy-long-form `Read` fallback (retires entirely one release cycle after BC-8719) and (2) Procedure 3 Step 1's exclusion filter's `brite-{short_entity}` branch (the Grep itself stays — only the long-form half of the self-exclusion filter retires).
- **Workspace routing** (for the retroactive path only). Nites → `emailbison-personal` (consumer recipients, workspace 11). Supply + Labs → `emailbison-b2b` (business recipients, workspace 52). This matches the canonical routing pattern in `campaign-analysis` §4 and `message-market-fit` Gate 3 — never hardcode a workspace, always dispatch from the `{entity}` answer.

Cite the answer in the learnings.md entry's `tags:` array as `#entity/brite-{short_entity}` (always long-form per § Tag scheme — re-expand from `{short_entity}` regardless of whether the operator typed long- or short-form at Gate 1; the tag-level identifier is canonical long-form and stable across the BC-8719 path migration).

### Gate 4 — Campaign focus selection

Use `AskUserQuestion` to identify which campaign the debrief is about, by name. The resolution differs by path:

1. **Post-analysis path (Gate 2 returned ≥1 match).** Default to the most recent `analysis-*.md` by filename date stamp. Surface the top 3 matches as options plus a free-text fallback for older runs. The selected filename resolves `{campaign-name}` as the stem between `analysis-` and the `-YYYY-MM-DD` date. **Re-run the `{campaign-name}` validator** on the extracted stem before proceeding — if a malicious filename exists in the campaigns directory, the stem could carry quotes, semicolons, or SOQL keywords and reach the Workflow 3 SOQL interpolation. The stem must independently match `^[a-z0-9-]{1,80}$`; reject and re-prompt on fail.
2. **Retroactive path (Gate 2 returned zero).** Operator supplies the campaign name as free text. Validate against the `{campaign-name}` rule above; reject and re-ask on fail. The retroactive path has no artifact filename to fall back to, so the operator's answer is authoritative.

---

## Methodology

Three frameworks govern this skill. First, a **5-question debrief format** (Q1 hypothesis, Q2 result, Q3 what worked / didn't, Q4 surprise, Q5 transferable) that suggests answers from upstream data when present and defaults to operator-authored when not. Second, a **Campaign verdict rubric (4 tokens)** — `SCALE` / `ITERATE` / `PAUSE` / `KILL` per ADR-018 — assigned against entity-scoped numeric thresholds anchored to `campaign-analysis` §3.3 b2b and §4 b2c benchmarks — every campaign verdict resolves by rule, never by prose. Third, an **append-only tagged learnings file** per entity, with four required tag families (`#entity` / `#vertical` / `#persona` / `#angle`) that make cross-entity and cross-angle search deterministic. Under-5-minute operator time is load-bearing: suggest first, ask only when auto-suggest fails, never re-prompt an answered field.

### 5-question debrief format

The five questions are fixed in order and format. Auto-suggest sources are named; operators confirm or override each suggestion, never compose from scratch when data is available.

**Q1. What hypothesis did we test?** Fixed format: `"We hypothesized that {angle|segment|timing} would {expected outcome} because {reasoning}."` Auto-suggest from `analysis-*.md` §5 Attribution Analysis — the row tagged `Offer` / `Message` / `Segment` / `Infrastructure` / `Timing` for the focal campaign supplies the variable; the operator confirms the reasoning clause. Retroactive path: operator authors.

**Q2. What was the result?** Fixed token plus one-line summary with the key metric. Tokens: `CONFIRMED` (hypothesis held), `PARTIAL` (partial hold with caveat), `REJECTED` (hypothesis did not hold). Auto-suggest from `analysis-*.md` §2 Segment Performance Ranking — the verdict column on the focal campaign row maps to the result token (`TOP PERFORMER` / `SCALE` → `CONFIRMED`; `MONITOR` / `TEST MORE` → `PARTIAL`; `UNDERPERFORM` → `REJECTED`). Retroactive path: operator authors after numeric-threshold check.

**Q3. What worked and what didn't?** Two-bullet-pair structure. Separate signal from noise. Auto-suggest from `analysis-*.md` §5 Attribution Analysis top-2 rows for the `Worked` side; `Didn't` side operator-authored (failure attribution rarely surfaces cleanly in the artifact). Retroactive path: operator authors both sides.

**Q4. What surprised us?** Operator-authored. No auto-suggest — surprise is by definition what the data did not predict. 1–3 bullets, unexpected findings only. This question is often the highest-value output of the debrief.

**Q5. What's transferable?** Entity-specific vs cross-entity pattern. Auto-suggest from `analysis-*.md` §6 Next Iteration Recommendations. Tag for cross-entity propagation by setting `transferable: true` in the entry frontmatter. If the transferable flag is true, §6 Procedure 3 runs; if false, the entry is entity-specific only and the procedure chain halts after append.

### Campaign verdict rubric (4 tokens)

Campaign verdicts resolve against entity-scoped numeric thresholds. Prose substitutes ("pretty good", "meh", "worth another shot") are refused by §8 Anti-Slop — every cell in the table below is objective.

| Campaign Verdict | b2b rule (Supply, Labs) | b2c rule (Nites) | Action |
|---|---|---|---|
| `SCALE` | Reply Rate >1% **AND** Interested Rate >25% **AND** sent ≥500 | Reply Rate >0.5% **AND** Interested Rate >15% **AND** sent ≥500 | Expand volume + senders next cycle |
| `ITERATE` | Mixed signals — one metric Healthy, one Attention, no Critical | Same pattern at softer b2c thresholds | Swap one variable (segment OR angle), keep on experiment side |
| `PAUSE` | Bounce Rate in Attention band (3–5%) **OR** sub-floor run (<500 sent OR <7 days) | Same rules | Wait + re-measure; no strategy change |
| `KILL` | Reply Rate <0.5% **AND** sent ≥500 **AND** days ≥7 | Reply Rate <0.25% **AND** sent ≥500 **AND** days ≥7 | Remove from matrix; log failure evidence in the entry's Q3 Didn't bullet |

Entity scoping matches `campaign-analysis` §3.3 (b2b) and §4 (b2c) verbatim — never fabricate a threshold, and never apply a b2b rule to a Nites run or vice versa. The b2b-vs-b2c split is dispatched from the Gate 3 `{entity}` answer: `brite-nites` / `nites` → b2c column; `brite-supply` / `supply` / `brite-labs` / `labs` → b2b column. Routing is on the normalized `{short_entity}` (Input-validation block), so both long- and short-form Gate-3 answers reach the same column.

**Frontmatter field stays `verdict:`** (single-vocab artifact). Per [ADR-018](../../../../docs/decisions/018-gtm-verdict-vocabularies.md) Consequences, the YAML frontmatter field name `verdict:` is preserved in entry artifacts — only the parent label in prose and section headers becomes "campaign verdict." There is no collision in the artifact because each `learnings.md` is single-vocabulary by construction.

**Sub-floor rule.** Any campaign with <500 sent OR <7 days elapsed resolves to `PAUSE` regardless of other metrics — the sample is too small to distinguish signal from noise, and statistical-significance floors match the `campaign-analysis` artifact §1 Quick Health Check sub-floor header convention.

**Precedence when multiple rules match.** When metrics satisfy both `KILL` and `PAUSE` (e.g. Reply <0.5% AND sent ≥500 AND days ≥7 AND Bounce 3–5%), apply campaign verdict precedence: `KILL` > `SCALE` > `ITERATE` > `PAUSE`. Rationale: `KILL` requires the statistical-significance floor to be met, so failure evidence is actionable; `PAUSE` is the default for floor-not-met or deliverability-suspect runs. The sub-floor rule above wins only when the KILL floor conditions (sent ≥500 AND days ≥7) are NOT met.

### Tag scheme

Every entry carries four required tag families, all lowercase-hyphenated. TitleCase, spaces, underscores, camelCase, or punctuation other than `/` and `-` are refused by §8 Anti-Slop.

- **`#entity/{brite-nites|brite-supply|brite-labs}`** (required, exactly one per entry). Long-form slugs only at the content level — this is the canonical entity identifier and is intentionally independent of the filesystem-path slug normalization at BC-8719 (which stripped `brite-` from the path only). Tags are searched cross-run via `Grep` on file *contents*, not paths, so the long-form `#entity/brite-*` lexicon stays stable across the path migration. Short-form (`#entity/nites`/`supply`/`labs`) is refused at the tag level.
- **`#vertical/{v}`** (required, exactly one per entry). Examples: `#vertical/municipalities`, `#vertical/hoas`, `#vertical/commercial-real-estate`, `#vertical/venue-partnerships`. Match the vertical convention used elsewhere in the entity's campaigns directory for cross-run searchability.
- **`#persona/{p}`** (required, exactly one per entry). Examples: `#persona/facilities-director`, `#persona/hoa-board-president`, `#persona/venue-operations-manager`. Persona granularity matches the `gtm-strategy` persona rollup for the entity.
- **`#angle/{a}`** (required, exactly one per entry). Examples: `#angle/capital-expenditure-timing`, `#angle/shoulder-season-revenue`, `#angle/insurance-premium-offset`. If a `creative-angles` artifact seeded the campaign, the angle tag matches its slug; if operator-authored, slug the tagline.

### Transferable-insight flagging

The `transferable: true` flag signals that an insight crosses entity boundaries — e.g. an angle that worked on `brite-supply` is worth testing on `brite-labs`, or a segment lens from Nites generalizes to Supply. On transferable, the skill produces two conditional proposals; **neither writes directly**.

1. **Marketing-context proposal** (conditional). `AskUserQuestion` surfaces the transferable insight to the operator: "Propose an update to `docs/marketing-context.md`?" On operator `Yes`, §6 Procedure 3 hands off to `/marketing:product-marketing-context` with the proposal payload; on `No`, the entry notes the skip. The skill does NOT edit `docs/marketing-context.md` directly — all edits route through the context-skill for provenance and review.
2. **Handbook-drift signal** (conditional, rarer). When the transferable insight contradicts or supersedes documented handbook content, `AskUserQuestion` confirms the contradiction, then §6 Procedure 4 hands off to `/workflows:handbook-drift-check` with the learnings.md entry path plus the offending handbook anchor. On `No`, the entry notes the operator's justification.

### Append-only invariant

`docs/campaigns/{short_entity}/learnings.md` is append-only, forever. A later debrief that contradicts an earlier one is a new entry, not an overwrite. Re-running a debrief for the same campaign on a different `debrief_at` date produces a new entry (the prior entry stays). This mirrors `message-market-fit`'s matrix append-only rule — history is never rewritten.

**Carve-out for auto-regenerated sections.** The file has four top-level sections defined by the §4 Brite Implementation template: `## Summary stats`, `## What works`, `## What doesn't`, and `## Campaign log`. The **Campaign log is strict-append** — entries are added in reverse-chronological order, never edited, never removed. The other three sections — `Summary stats`, `What works`, `What doesn't` — **regenerate in place** on each append: the skill recomputes the summary-stats counters, re-extracts the `What works` cross-entry pattern bullets (from entries where `verdict: SCALE` or `verdict: ITERATE` AND `transferable: true`), and re-extracts the `What doesn't` cross-entry failure bullets (from entries where `verdict: KILL`). The carve-out exists because the alternative — hand-editing those summaries on every debrief — breaks the under-5-minute constraint. The carve-out applies ONLY to those three sections; editing a Campaign-log entry is a §7 Rubric 1–3 hard failure.

### Vocabulary mapping across sibling skills

Three sibling skills use three verdict vocabularies. Only `SCALE` overlaps intentionally. The table below lets operators translate across skills when carrying a campaign through the lifecycle.

| Concept | `campaign-analysis` (5 tokens) | `message-market-fit` (5 tokens) | `campaign-debrief` (4 tokens) |
|---|---|---|---|
| Best performer — expand | `TOP PERFORMER`, `SCALE` | `SUPER WORKS` | `SCALE` |
| Worth keeping — tweak | *(no direct analysis token — operator judgment)* | `KIND OF WORKS` | `ITERATE` |
| Deferred — wait and re-measure | `MONITOR`, `TEST MORE` | `DEFERRED`, `PENDING` | `PAUSE` |
| Dead — remove | `UNDERPERFORM` | `DOESN'T WORK` | `KILL` |

Three vocabularies exist because each skill owns a different decision surface: `campaign-analysis` reports per-segment performance; `message-market-fit` classifies experiments against a 5-category matrix; `campaign-debrief` captures a learning entry with a 4-token campaign verdict action rubric. Cross-skill translation is the operator's responsibility — the vocabulary mapping table above is the canonical source; ADR-018 carries the parent-label translation (Angle / Experiment / Campaign Verdict).

---

## Brite Implementation

### Tools this skill calls

| What the skill needs to do | MCP / tool | Reaches | Reason (ADR / source) |
|---|---|---|---|
| Read upstream `analysis-*.md` (primary input) | `Read` + `Glob` | Local `docs/campaigns/{short_entity}/analysis-*.md` | §6 Procedure 1 Step 1 — primary input per Scope |
| Pull Opportunity outcomes for campaign → pipeline attribution | Salesforce MCP (`run_soql_query`) | `brite-salesforce` production org | ADR 2a — SF is CRM SoR; Q3 + Q5 data-backing |
| Pull campaign metrics standalone (retroactive path) | EB MCP (`get_active_workspace_info`, `get_campaign_stats`, `get_replies_analytics`) | Entity-routed EB workspace | §6 Procedure 2; workspace routing per Gate 3 |
| Read prior `learnings.md` (cross-entity lookup) | `Read` + `Glob` | Local `docs/campaigns/{*}/learnings.md` | §6 Procedure 3 — novelty check before transferable-insight propagation |
| Append to or create `learnings.md` | `Write` | Local `docs/campaigns/{short_entity}/learnings.md` | §3 Append-only invariant; §6 Procedure 1/2 final step |
| Sync SF Campaign status to `Completed` (σ3 trigger on sub-issue 8 close) | `Skill` → `/revops:update-sf-campaign-status` | Salesforce (via the sibling slash command — soft-fail on missing manifest / missing SF record) | BC-8752 — Workflow 4 Step 5 post-append sync |

**EB namespace (load-bearing).** All Email Bison calls use the **short form** — `mcp__emailbison-b2b__*` and `mcp__emailbison-personal__*` — NOT `mcp__plugin_marketing_emailbison-*__*`. The EB MCP servers are registered at the repo-root `.mcp.json`, not inside `plugins/marketing/.mcp.json`. This matches `campaign-analysis` line 5 and `message-market-fit` line 5; the plugin-scoped namespace form will silent-fail at runtime per the CLAUDE.md gotcha ("listing a server that isn't registered fails silently").

**EB gotchas.** `list_campaigns` has no server-side date filter — apply a client-side date filter after the call if the retroactive path needs date-scoping. `get_replies_analytics` (not `list_replies`) is the reply-sentiment tool; see `plugins/marketing/tools/integrations/email-bison.md` for the canonical recipe.

### Entity-keyed output paths

Every `Write` targets a path scoped to the validated and normalized `{short_entity}` slug (the short-form derivation from the Gate 1 Input-validation block):

- **Output file.** `docs/campaigns/{short_entity}/learnings.md` — single file per entity, append-only (see §3 Append-only invariant). Per BC-8719, the directory segment is the short-form slug (`nites` / `supply` / `labs`), not the long-form (`brite-nites` / `brite-supply` / `brite-labs`).
- **Directory creation.** On first write, the `docs/campaigns/` parent and the `{short_entity}` subdirectory are created via `Write`'s implicit `mkdir -p` semantics. After BC-8719, this skill and `message-market-fit` share the same short-form directory tree (`docs/campaigns/{short_entity}/`) — pre-BC-8719 the two skills wrote to parallel `brite-*` vs unprefixed directories; post-BC-8719 they converge.

### learnings.md file template (create-on-missing)

On first-ever debrief for an entity, the file does not exist. The skill creates it from this template, then appends the first entry beneath the `## Campaign log` section:

```markdown
# Campaign Learnings — {entity}

Append-only knowledge base. Each entry is one debriefed campaign. Entries are strict-append; summary / what-works / what-doesn't sections regenerate in place on each append.

## Summary stats

_Regenerated on each append. Counters: total debriefs, campaign verdicts breakdown, last debrief date._

- Total debriefs: {N}
- Campaign verdicts: SCALE={s}, ITERATE={i}, PAUSE={p}, KILL={k}
- Last debrief: {YYYY-MM-DD}

## What works

_Regenerated on each append. Cross-entry patterns from entries where verdict is SCALE or (ITERATE and transferable: true)._

- {cross-entry pattern bullets}

## What doesn't

_Regenerated on each append. Cross-entry failures from entries where verdict is KILL._

- {cross-entry failure bullets}

## Campaign log

_Strict-append. Reverse-chronological. Entries are never edited or removed._

{entry N}

---

{entry N-1}

...
```

Entry schema (appended beneath `## Campaign log`):

```yaml
---
campaign: {campaign-name}
analyzed_at: {YYYY-MM-DD}      # date of the source analysis-*.md artifact (omit on retroactive path)
debrief_at: {YYYY-MM-DD}       # date this debrief ran
source_analysis: docs/campaigns/{short_entity}/analysis-{campaign-name}-{YYYY-MM-DD}.md   # omit on retroactive path
verdict: SCALE | ITERATE | PAUSE | KILL
metrics:
  reply_rate: 0.012            # decimal, matches campaign-analysis numeric form
  interested_rate: 0.28
  bounce_rate: 0.024
  sent: 1200
  days: 14
tags:
  - "#entity/brite-{nites|supply|labs}"
  - "#vertical/{v}"
  - "#persona/{p}"
  - "#angle/{a}"
transferable: true | false
transferable_note: {one-line note if transferable: true, else omit}    # read by MSPA ITERATE Notes column (BC-5953)
---

## Q1 — Hypothesis
{format: "We hypothesized that {angle|segment|timing} would {expected outcome} because {reasoning}."}

## Q2 — Result
{CONFIRMED | PARTIAL | REJECTED} — {one-line summary with key metric}

## Q3 — What worked, what didn't
**Worked**: {1–3 bullets, signal}
**Didn't**: {1–3 bullets, noise or failure}

## Q4 — What surprised us
{1–3 bullets, unexpected findings}

## Q5 — Transferable insight
{sentence, or "entity-specific only" if not transferable, or skip-notes from Procedure 3/4}
```

See §3 5-question debrief format for per-question content rules.

### Architectural rules that apply

Each rule below cites its source so a reader can trace the claim.

- **Append-only learnings.md with carve-out.** Campaign log is strict-append; Summary / What works / What doesn't regenerate in place. Source: §3 Append-only invariant.
- **Entity-driven workspace routing.** Nites → `emailbison-personal`; Supply + Labs → `emailbison-b2b`. Source: `campaign-analysis` §4 canonical pattern; `message-market-fit` Gate 3.
- **Campaign verdicts are numeric, not prose.** `SCALE`/`ITERATE`/`PAUSE`/`KILL` only; thresholds from §3 rubric table. Source: §3 Campaign verdict rubric; enforced by §8.
- **Under-5-minute operator constraint.** Data suggests first, ask only when auto-suggest fails, never re-prompt. Source: issue Non-Goals; enforced by §8.
- **EB MCP is short-form.** `mcp__emailbison-b2b__*` and `mcp__emailbison-personal__*` — not the plugin-scoped form. Source: sibling allowed-tools frontmatter; CLAUDE.md gotcha about unregistered-server silent-fail.
- **Marketing-context and handbook edits go through proposal, not direct write.** Skill calls `AskUserQuestion` and hands off to `/marketing:product-marketing-context` or `/workflows:handbook-drift-check` on confirmation. Source: issue Scope — Transferable insight flow.

### Cross-skill boundaries

**Receives from:**

- **[BC-2721](https://linear.app/brite-nites/issue/BC-2721) `campaign-analysis`** (primary) — `docs/campaigns/{short_entity}/analysis-*.md` is the §6 Procedure 1 input and the Q1/Q2/Q3 auto-suggest source. Handoff already live at campaign-analysis §4 Cross-skill boundaries (the MANDATORY clause pointing to this skill).
- **Operator directly** (retroactive path) — §6 Procedure 2 runs when no `analysis-*.md` exists; operator supplies campaign name; metrics pull standalone from Email Bison.

**Hands off to:**

- **[BC-5829](https://linear.app/brite-nites/issue/BC-5829) `message-market-fit`** — transferable-insight `transferable_note` YAML field flows back into the MSPA matrix's Notes column on the next ITERATE run. The read-step implementation in MSPA is tracked at [BC-5953](https://linear.app/brite-nites/issue/BC-5953). Cross-link already live at MSPA §4 line 267 (pending marker).
- **[BC-1727](https://linear.app/brite-nites/issue/BC-1727) `product-marketing-context`** (conditional, on `transferable: true`) — §6 Procedure 3 hands off with the proposal payload after operator confirmation. This skill never writes `docs/marketing-context.md` directly.
- **`/workflows:handbook-drift-check`** (conditional, on handbook-contradiction signal) — §6 Procedure 4 hands off with the learnings.md entry path plus the offending handbook anchor. This skill never edits handbook content directly.
- **[BC-2722](https://linear.app/brite-nites/issue/BC-2722) `outbound-playbook`** (pending) — once shipped, outbound-playbook will invoke this skill as the post-campaign step of its conductor loop.

**Does not own:**

- Campaign analysis itself (`campaign-analysis`).
- Campaign execution (`outbound-playbook` + `/marketing:launch-campaign`, both pending or separate).
- Next-experiment design (`message-market-fit` owns the matrix and batch-design).
- Marketing-context editing (`product-marketing-context` owns the file and its freshness cadence).

**Entity-slug normalization (post-BC-8719).** This skill and sibling `message-market-fit` (BC-5829) now both write to short-form `docs/campaigns/{short_entity}/` (BC-8719 / O15 migration normalized the layout). `campaign-debrief` writes `learnings.md`; MSPA writes `mmf-*.md`; both files share the same `docs/campaigns/{short_entity}/` directory. Backward-compat read-shim: Procedure 3 Step 1's cross-entity novelty `Grep` and Workflow 4 Step 1's `Read` both try the short-form path first and fall back to the legacy long-form (`docs/campaigns/brite-{short_entity}/`) once with a warning if the short-form is missing — that shim retires one release cycle after BC-8719. Operators with pre-BC-8719 artifacts on disk should run `python3 plugins/marketing/scripts/migrate_entity_slugs.py` once to relocate them.

**Engineering-side parallel.** `docs/precedents/INDEX.md` is the engineering-side decision-trace pattern; this skill is the marketing-flywheel cognate. Each debrief is a marketing-domain decision trace. The two indexes are structurally analogous: append-only, tag-keyed, cross-run searchable.

**Three-verdict translation table (per [ADR-018](../../../../docs/decisions/018-gtm-verdict-vocabularies.md)).** Three sibling skills emit verdicts at different lifecycle gates. Parent labels are renamed so each gating semantic is explicit at the source — vocabularies stay distinct, decision surfaces stay separate.

| Term | Source skill | Decision surface | Timing |
|---|---|---|---|
| Angle Verdict | creative-angles | pre-experiment | before mmf |
| Experiment Verdict | message-market-fit | post-batch | during campaign |
| Campaign Verdict | campaign-debrief | post-campaign | after campaign closes |

This skill owns the **Campaign Verdict** (Gate 3, post-campaign — `SCALE` / `ITERATE` / `PAUSE` / `KILL`). The §3 vocabulary mapping table above translates token-by-token across `campaign-analysis` / `message-market-fit` / `campaign-debrief`; see [handbook/marketing/frameworks/verdicts-cross-reference.md](https://github.com/Brite-Nites/handbook/blob/main/marketing/frameworks/verdicts-cross-reference.md) for the canonical cross-vocabulary reference.

---

## Discoveries — icp-refinement / offer-retirement / persona-discovery signals

The post-campaign retro is the single richest moment for surfacing handbook-canonicals drift: ICP rules that no longer fit, offers that have aged out of the lineup, and entirely new personas that the campaign exposed. This skill emits three discovery categories to `docs/campaigns/{short_entity}/{slug}/discoveries.json` (per Phase 2 architectural pivot) so BC-8726 (`/marketing:icp-refinement-review`) and humans can later promote them to handbook canonicals + prose via PR. See [`plugins/marketing/references/discoveries-promotion.md`](../../references/discoveries-promotion.md) for the full signal → review → handbook PR flow; the schema lives at [`plugins/marketing/data/discoveries-schema.json`](../../data/discoveries-schema.json) and is enforced by `plugins/marketing/scripts/lint_discoveries.py` (wired into `scripts/validate.sh`).

The list-building skill emits the fourth category (`title-discovery`) at contact-discovery time; the campaign-debrief skill owns the post-campaign three.

**Common confirm gate** (one `AskUserQuestion` + one `Write` per category). All three categories use the same pattern: the skill surfaces the candidate via `AskUserQuestion`; on `Yes` it `Read`s `docs/campaigns/{short_entity}/{slug}/discoveries.json` (file-not-found branches to a fresh `{schema_version: 1, signals: []}` shape — mirrors the §5 Workflow 4 Step 1 file-not-found pattern; do NOT use `Glob` first), appends a single signal to `signals[]`, and `Write`s the file back. Per-category payload shapes are spelled out below; `payload` is held open at the schema layer (`type: object`) so downstream consumers can evolve per category. `promotion_status` defaults to `"pending"` at emit time.

The discovery-emission gates fire from **Procedure 1 step 10 / Procedure 2 step 8** — after Workflow 4 completes (learnings.md written) and independent of Procedure 3's transferable-insight dispatch. Discoveries route to handbook-canonicals refinement; Procedure 3 routes to `docs/marketing-context.md`. The two flows can both fire on the same debrief.

### Category 1 — icp-refinement

**When to emit.** The campaign's actual responders diverge from the segment / firmographic profile the ICP-template targeted. Examples: targeted "facility directors at 50k+ enrolment universities" but the responders were almost exclusively "ops directors at 10-30k enrolment" (firmographic mismatch); the persona slug + titles match canon but Reply Rate splits sharply along a sub-segment the ICP doesn't carve (e.g., Catholic vs. secular hospitals, or coastal vs. interior resorts).

**Payload shape.**

```jsonc
{
  "category": "icp-refinement",
  "emitted_at": "<ISO-8601 datetime>",
  "emitted_by_skill": "campaign-debrief",
  "payload": {
    "vertical": "<vertical-slug from #vertical/ tag>",
    "persona": "<persona-slug from #persona/ tag>",
    "current_icp_summary": "<one-line description of the ICP template the campaign used>",
    "observed_pattern": "<one-line description of who actually responded / converted>",
    "evidence_metric": "<numeric backing — e.g. 'Reply Rate 1.9% on N=480 sub-segment vs 0.4% on N=720 non-sub-segment'>",
    "refinement_proposal": "<short prose — operator's proposed ICP carve, e.g. 'split persona into hoa-board-president-active-amenity vs hoa-board-president-passive-amenity'>"
  },
  "promotion_status": "pending"
}
```

**Gate prompt.** "This campaign's responders diverged from the ICP template (`{summary}`). Log an `icp-refinement` signal for handbook review?" Options: `Yes, log refinement proposal` / `No, skip — within ICP noise` / `No, defer — need more campaigns before refining`.

### Category 2 — offer-retirement

**When to emit.** The campaign's offer earned a `KILL` campaign verdict, AND the same offer has earned `KILL` or `PAUSE` in at least one prior debrief on `docs/campaigns/{short_entity}/learnings.md` (cross-run pattern, not single-run noise). The signal recommends moving the offer from `status: active` to `status: retired` in `plugins/marketing/data/canonicals/{vertical}.yaml`.

**Payload shape.**

```jsonc
{
  "category": "offer-retirement",
  "emitted_at": "<ISO-8601 datetime>",
  "emitted_by_skill": "campaign-debrief",
  "payload": {
    "vertical": "<vertical-slug>",
    "offer_slug": "<offer-slug from canonicals/{vertical}.yaml>",
    "kill_count": <integer — count of KILL verdicts across learnings.md entries for this offer>,
    "pause_count": <integer — count of PAUSE verdicts across learnings.md entries for this offer>,
    "last_kill_at": "<YYYY-MM-DD — debrief_at of the most recent KILL>",
    "recommended_replacement": "<optional offer-slug to set on canonicals replaced_by:; omit when no successor is ready>",
    "rationale": "<one-line summary — e.g. 'asymmetric-anchor angle stalled; venue-partnerships pivot replaces value capture'>"
  },
  "promotion_status": "pending"
}
```

**Gate prompt.** "Offer `{offer_slug}` has earned KILL on this debrief and {kill_count - 1} prior KILL(s) + {pause_count} PAUSE(s) on `docs/campaigns/{short_entity}/learnings.md`. Log an `offer-retirement` signal recommending `status: retired` for the handbook canonicals PR?" Options: `Yes, log retirement proposal` / `Yes, log with a proposed replacement (operator fills it in)` / `No, keep active — this run was an outlier`.

### Category 3 — persona-discovery

**When to emit.** The campaign exposed a buying contact whose title and decision authority do not fit any persona currently in `plugins/marketing/data/canonicals/{vertical}.yaml` for the vertical — and the pattern is not a title-discovery (which would be list-building's category, handling title aliases inside an existing persona). The signal proposes adding a NEW persona to canonicals.

**When NOT to emit.** If the new title belongs inside an existing persona's `titles[]` array (e.g., "Director of Event Operations" added to an existing `venue-operations-manager` persona), that's a `title-discovery` signal at list-building time, not a `persona-discovery` here. Only emit `persona-discovery` when the decision authority is structurally different (e.g., a CFO-tier signoff layer that wasn't represented in any existing persona for the vertical).

**Payload shape.**

```jsonc
{
  "category": "persona-discovery",
  "emitted_at": "<ISO-8601 datetime>",
  "emitted_by_skill": "campaign-debrief",
  "payload": {
    "vertical": "<vertical-slug>",
    "proposed_persona_slug": "<kebab-case slug for the new persona>",
    "proposed_display_name": "<human-readable display name>",
    "observed_titles": ["<title-1>", "<title-2>"],
    "decision_authority": "<one-line description of what this persona controls — e.g. 'capex sign-off for installations >$50k'>",
    "evidence_metric": "<numeric backing — e.g. 'Opportunity attribution: 3 of 4 Closed Won this cycle attributed to contacts in this role'>",
    "differs_from_existing": "<one-line — name the closest existing persona and explain why this one isn't a sub-role of it>"
  },
  "promotion_status": "pending"
}
```

**Gate prompt.** "This debrief surfaced a buying role that doesn't map to any persona in `canonicals/{vertical}.yaml` (`{proposed_display_name}` — controls `{decision_authority}`). Log a `persona-discovery` signal for handbook review?" Options: `Yes, log new persona proposal` / `No — this is a title variant of an existing persona (route to list-building's title-discovery instead)` / `No, skip — insufficient data`.

### Where these signals land

All three categories collect in the per-campaign-run `discoveries.json` under `docs/campaigns/{short_entity}/{slug}/`. Downstream, BC-8726 (`/marketing:icp-refinement-review`) reads signals across runs by category, groups them per `{vertical}/{persona}`, surfaces them as PR candidates, and produces the handbook-canonicals PR. Until BC-8726 ships, signals accumulate in `pending` status; the discoveries-promotion reference doc carries the full lifecycle.

---

## MCP Tool Reference

"When you need to X, call `tool_name`." Grouped by workflow, not by server. All calls are reads except the final learnings.md `Write` — no MCP confirmation gates apply to this skill.

### Workflow 1 — Read upstream analysis artifact (post-analysis path)

1. `Read` the `analysis-*.md` file resolved at Gate 2 / Gate 4. No availability probe — file read only.
2. Parse the analysis artifact (the `analysis-*.md` file, which has 6 sections per `campaign-analysis` §6 Report Spec): artifact §2 Segment Performance Ranking for the focal campaign's verdict token (→ Q2 auto-suggest); artifact §5 Attribution Analysis for the focal row (→ Q1 auto-suggest) and top-2 rows (→ Q3 auto-suggest Worked side); artifact §6 Next Iteration Recommendations (→ Q5 auto-suggest).
3. Extract numeric metrics from artifact §1 Quick Health Check (aggregate `Reply Rate`, `Interested Rate`, `Bounce Rate`, plus the run-window header for `sent` count and `days` elapsed) and artifact §2 Segment Performance Ranking (per-campaign rates on the focal row when segment-level granularity is needed). These feed this skill's §3 Campaign verdict rubric. *Note: do NOT pull metrics from artifact §3 Infrastructure Analysis — that section holds cohort comparisons (Google vs Microsoft senders), not headline rates.*

### Workflow 2 — Standalone EB metrics fetch (retroactive path)

See [`plugins/marketing/tools/integrations/email-bison.md` §Common Workflows](../../tools/integrations/email-bison.md#common-workflows) for the canonical recipe.

1. **Availability probe** — call `get_active_workspace_info` on the workspace dispatched from Gate 3 (Nites → `mcp__emailbison-personal__*`; Supply / Labs → `mcp__emailbison-b2b__*`). On failure, halt and point the operator to `/marketing:setup-email-bison` — do NOT fall through to a cross-workspace probe.
2. **Resolve campaign** — call `list_campaigns`. `list_campaigns` has no server-side date filter, so apply a client-side filter on `created_at` or `updated_at` if the operator's campaign name is ambiguous across time (e.g. "spring-promo" ran in 2025 and 2026). Match `{campaign-name}` against the result list; if multiple matches, re-prompt with dates.
3. **Fetch stats** — call `get_campaign_stats` on the resolved campaign ID for `sent`, `bounce_rate`, and raw reply count.
4. **Fetch reply sentiment** — call `get_replies_analytics` (NOT `list_replies`) on the same campaign ID for `interested_rate` / positive-reply count. `get_replies_analytics` is the canonical reply-sentiment tool; `list_replies` returns reply bodies, not sentiment aggregates.
5. Derive `days` elapsed from campaign `created_at` to `today`. Apply the §3 Campaign verdict rubric against the entity-scoped threshold column.

### Workflow 3 — Salesforce Opportunity attribution (optional)

Runs when the operator wants to correlate the campaign with downstream pipeline. Soft gate — skips cleanly on SF unavailability.

**Parallelization note.**

…(truncated)
