URL: /plays/promote-winners · Id: `play:promote-winners`

# Promote winners

> Pays only for posts that already won free reach, and raises spend a tier only when regions with no Ads show it adds Leads.

Pays only for posts that already won free reach, and raises spend a tier only when regions with no [Ads](https://startupbuilder.dev/business/nouns/ad) show it adds [Leads](https://startupbuilder.dev/business/nouns/lead).

```text title="Set it up · Ask"
Be our paid media lead: put money only behind posts that already won on their own, as they are, never edited. Start at $10 a day, and raise a tier only when regions we hold out show it adds Leads. Show me what’s missing; don’t spend.
```

```text title="Run one · Ask"
Hand our latest free winner to the paid media agent and show me what it chose and why. Don’t spend.
```

```ts title="Run one · SDK"
import { $ } from 'startup-builder'

const { events: [last] } = await $.Event.list({ where: 'name in ("Experiment.concluded", "Invoice.collected", "Monitor.checked", "Fact.recorded", "Integration.synced", "Offer.repriced", "Experiment.matured", "PlayMix.compiled")', limit: 1 })   // { play } compiled to this Play’s Trigger Events
const { subject } = await $.Trigger.fire({ like: last.$id }, { mode: 'try' })   // the same Event again, in try mode · Not live yet, gaps #7
const { decisions: [decision] } = await $.Decision.list({ where: `on = "${subject}"` })   // what it chose, and why · Not live yet, gaps #7
const { actions } = await $.Action.list({ where: `on = "${subject}"` })   // what it did, with its checks · Not live yet, gaps #7
return { decision, actions }
```

```json title="Run one · Response · sample"
{
  "decision": {
    "$id": "https://api.sb/leaseline.app/decisions/decision_503",
    "$type": "Decision",
    "choice": "winner-promotion",
    "reason": "post_31 won its free read on saves per 1,000 views (14.2 against 6.1, n 8,400); every claim maps to an approved Page, and Dana’s sponsor grant is on",
    "stakes": "medium",
    "options": [
      "winner-promotion",
      "promotion-queue",
      "tier-gate",
      "promotion-intake",
      "promoted-comment-desk"
    ],
    "by": "worker_7",
    "on": "post_31",
    "expected": "3.0 or more added Leads per $100 in served regions by day 28",
    "reopensWhen": "the arm passes 2 times its allowed cost after 50 bid events"
  },
  "actions": [
    {
      "$id": "https://api.sb/leaseline.app/actions/action_1",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_7",
      "verb": "Experiment.start",
      "checks": []
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_2",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_7",
      "verb": "Audience.create",
      "checks": []
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_3",
      "$type": "Action",
      "status": "pending",
      "reason": "awaitingApproval",
      "mode": "try",
      "by": "worker_7",
      "verb": "Ad.create",
      "checks": [
        {
          "check": "claims-resolve",
          "status": "pass"
        },
        {
          "check": "price",
          "status": "pass"
        },
        {
          "check": "disclosure",
          "status": "pass"
        },
        {
          "check": "tier-gate",
          "status": "pass"
        }
      ]
    }
  ]
}
```

`action_1` draws the 12 served and 12 held-out regions. `action_3` is the paused, fenced Ad behind Dana’s own post under her sponsor grant; it waits for your approval because every send of this Tactic does.

`+3.3 added Leads per $100 · ■■□□ Early sign · n 24 regions · path → arr`

| Try another: the winner | Choice | Reason |
| --- | --- | --- |
| Dana’s post on renewal slippage: saves 14.2 per 1,000 against 6.1 | `winner-promotion` | won on behavior, claims sourced, grant on |
| The move-in checklist reel: won on watch time, slot full this week | `promotion-queue` | all 3 paid slots taken; it ranks 2nd |
| Tier 1 read on post_31: +3.3 Leads per $100, lower bound over breakeven | `tier-gate` | a winning holdout clears the tier line |
| A post that won on likes only, with a “10,000 managers” claim | `promotion-intake` | likes are no behavior read, and the count is unsourced |

| Worth | Your time | Spend | First signal | Use it when | Instead |
| --- | --- | --- | --- | --- | --- |
| ≈ +$430 ARR a month at $340 | 30 min week 1, then 15 a week | $30 per added Lead, $340 a month at tier 1 | Day 3: the first Lead from a promoted post | a free winner, $300+ a month to spend | No free winner: `organic-social` · under $300: set a budget (<Op verb="PlayMix.compile" />) |

**Where it sits:** **Stranger** → **Lead** → Trialing → Activated → At risk → Churned → Won back · Promoted lead lift → New Leads → New Customers ↑ → ARR added each week → ARR

Worth’s formula: about +3.3 Leads per $100 × $340 a month × 0.11 Leads to paid (your last 90 days) × $347 Pro ARR ≈ +$428. Under First signal: One 28-day region holdout per tier: about 8 weeks to tell +3 Leads per $100 at $340 a month; under $300, 90 days can tell only +9 or more.

`sample · api.sb/leaseline.app · 41 Customers at 2026-10-19 · on Experiment.concluded · first Ad live 41 min after the verdict · 8 keys Not live yet`

**Also runs as:** [Creator ads](/plays/creator-ads).

## Start it this week
```bash
npx startup-builder add play promote-winners   # 1 · writes plays/promote-winners.mdx (Not live yet, gaps #5)

# 2 · dry run; below, its 422 on 2026-08-24
curl -X POST "https://api.sb/leaseline.app/campaigns?mode=try" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"play":"promote-winners"}'

# 3 · keep it, at You approve each one
curl -X POST https://api.sb/leaseline.app/campaigns \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"play":"promote-winners"}'

# on
curl -X PATCH https://api.sb/leaseline.app/campaigns/promote-winners \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"endsAt":null}'

# off: every Ad pauses; free posts stay up
curl -X PATCH https://api.sb/leaseline.app/campaigns/promote-winners \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"endsAt":"now"}'
```


```json title="The dry run’s 422 · 2026-08-24 · sample"
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "promote-winners can’t start yet: 1 check fails and 1 warns.",
  "checks": [
    {
      "check": "use-when",
      "status": "pass",
      "observed": "2 free winners in 30 days; a $340 monthly Demand Budget",
      "doc": "/plays/promote-winners#check-use-when",
      "fix": "Keep a winner and $300 a month, or run organic-social first.",
      "then": "Metric.query"
    },
    {
      "check": "baseline",
      "status": "pass",
      "observed": "1.9 Leads per $100 on boosted posts, n 6 in 90 days",
      "doc": "/plays/promote-winners#check-baseline",
      "fix": "Read it once; it fills Before in Your number.",
      "then": "Metric.query"
    },
    {
      "check": "spend-limit",
      "status": "fail",
      "observed": "no monthly Limit on ad spend",
      "doc": "/plays/promote-winners#check-spend-limit",
      "fix": "Set a $340 monthly spend Limit; the Play pauses at it.",
      "then": "Limit.raise"
    },
    {
      "check": "role-unstaffed",
      "status": "warn",
      "roles": [
        "marketing-lead",
        "content-lead"
      ],
      "owned": 0,
      "doc": "/plays/promote-winners#check-role-unstaffed",
      "fix": "Staff the Marketing Lead, or leave it: approvals and hard comments come to you.",
      "then": "Role.staff"
    }
  ]
}
```

| Check | Fix (one call) | Your time | Wait |
| --- | --- | --- | --- |
| <a id="check-use-when"></a>`use-when` · <a id="check-baseline"></a>`baseline` · pass: 2 winners, $340 a month; 1.9 Leads per $100, n 6 (gaps #8) | <Op verb="Metric.query" /> on `promoted-lead-lift`, which fills Before in Your number | 1 min | none |
| <a id="check-spend-limit"></a>`spend-limit` · fail: no spend Limit | <Op verb="Limit.raise" /> to $340 a month | 2 min | none |
| <a id="check-role-unstaffed"></a>`role-unstaffed` · warn (gaps #8): no Marketing Lead | <Op verb="Role.staff" />, or leave it: approvals and hard comments come to you | 0 min | none |

`Your number · Before 1.9 · Now 3.3 added Leads per $100 · Target 4.0 by Dec 31 · ■■□□ Early sign · n 24 regions`

The Ads cost what the Venues bill, capped at $340 a month by your Limit: about $30 for each added Lead at tier 1. Reading each arm and answering comments costs about $0.30 a day (Worker cost). At the $300 Use it when line, about $27 an added Lead.

## When it starts
| When | Trigger id | Starts on | Window | How it gets in |
| --- | --- | --- | --- | --- |
| A free post or line wins its read on behavior | `promotion-candidate` | `event` on `Experiment.concluded` where verdict = 'winner' on a behavior read, on a free placement | intake within 1d; a slot within 30d | <Op verb="Experiment.conclude" /> on the free read, from `organic-social` or `founder-voice` |
| A Customer a promoted Ad brought pays their first Invoice | `promoted-conversion` | `event` on `Invoice.collected` where the first collected Invoice of a Customer whose Acquisition names a promoted Ad | 30d | <Op verb="Integration.connect" /> (Stripe) |
| A member grants or revokes sponsoring their own post | `sponsor-grant-change` | `state` on `Integration.synced` where a member's sponsor grant on their own Post becomes granted or revoked | 1d | <Op verb="Integration.connect" /> with LinkedIn |
| + 5 more for ad fatigue, new facts, margins, holdout reads and budget moves (`?lanes=all`) | | | | |

```json title="One signed delivery · StartupBuilder-Signature"
{
  "$id": "https://api.sb/leaseline.app/events/event_4120",
  "$type": "Event",
  "name": "Experiment.concluded",
  "at": "2026-10-18T16:30:00Z",
  "seq": 4120,
  "data": {
    "experiment": "experiment_9",
    "post": "post_31",
    "venue": "linkedin",
    "verdict": "winner",
    "read": "saves per 1,000 views",
    "winner": 14.2,
    "control": 6.1,
    "n": 8400
  }
}
```

```bash title="Reproduce it in test"
npx startup-builder trigger Experiment.concluded --like event_4120   # replays the verdict
npx startup-builder clock advance --by 28d   # closes the region holdout and runs the tier read
npx startup-builder listen Experiment.concluded --forward-to http://localhost:3000/hook
```

## Who runs it
**Paid media · your agent** (`role:paid-media-manager`, `worker_7`) holds the Key Result. **Content Lead · your agent** (`worker_6`) answers comments on promoted posts. Every Ad and every tier raise comes to **you** (Dana Okafor, person, `worker_1`) for approval, and hard comments come to you, because the Marketing Lead Role has no person. Your number links to Did it work.

| Option | Rung today | Evidence (passed of n) | Moves up at | Drops back when | You can |
| --- | --- | --- | --- | --- | --- |
| Run the winner as an Ad (`winner-promotion`) · Raise a tier (`tier-gate`) | You approve each one | 3 of 3 · 1 of 1 | never: the spend is yours | — | <Op verb="Approval.approve" /> · <Op verb="Approval.refuse" /> |
| Queue it (`promotion-queue`) · Refuse at intake (`promotion-intake`) | It does it | 4 of 4 · 2 of 2 | — | — | <Op verb="Action.redirect" /> |
| Answer comments (`promoted-comment-desk`) | It does it, you can stop it | 31 of 31 | met on day 12 (the first 20 answers) | any answer fails a Check → You approve each one | <Op verb="Action.cancel" /> inside the hold |
| Change the tier lines | You approve each one | 0 merged | never | — | <Op verb="Change.merge" /> · <Op verb="Change.revert" /> |
| Never: an edited winner, a count you didn’t earn, a critical comment hidden | Refused, every time | 2 refused at intake | never | — | nothing; intake holds it |

**What it reads · What it may call.** It reads the free read’s verdict, the Ad’s spend and counted conversions from Stripe, and the held-out regions. It writes Ads only through the proxy, and every change outside the approved state is undone on the daily read (`drift`).

```md title="The brief (folded): compiled Agent-register Markdown · play:promote-winners@1 · each line names its record key"
You hold the paid media Key Result: 4.0 added Leads per $100 by 2026-12-31.      ← worker, metric
On each Trigger, pick the Tactic that runs and say why.                          ← triggers, tactics
Promote only a winner on a behavior read, as it is; any edit is a new arm.       ← copyRules 2; intake
Start at $10 a day; raise a tier only on a winning region holdout.               ← your file, lines 4–5
Never claim a count you did not earn; never hide a critical comment.             ← copyRules 3, 8
A Price in an Ad equals the Offer’s live Price.                                  ← copyRules 9; price
```

**The slow loop.** The weekly Decisions kept both tier lines, and no Change was merged in the window, so there is no replay or read-back to print. The tier raise on day 29 was a Decision on the Readout, never a change to the rule.

**Bring your own agent** (folded) starts at You approve each one whatever it scores, because every Ad spends your money; <Op verb="Role.staff" /> seats it.

```bash
npx startup-builder eval --scenario promote-winners-24 --agent "claude -p" --seat paid-media-manager   # seated: +3.3 Leads per $100, 0 Checks failed served, every arm paused on time · yours: +2.6, 1 arm paused late
```

## What it can do next
| Option | Picked when | Decided by | Runs | Hands to | If it’s just you |
| --- | --- | --- | --- | --- | --- |
| winner-promotion `winner-promotion` | a winner passes intake and a slot is open | Agent’s call, with its reason | <Op verb="Experiment.start" /> (regions) → <Op verb="Audience.create" /> → <Op verb="Ad.create" />, paused until you approve | you approve it, due 1 business day · Take this one | you |
| promotion-queue `promotion-queue` | every paid slot is full | Your rule (3 slots, line 4) | ranks it; one slot in five drawn at random | nobody | the same |
| tier-gate `tier-gate` | a region holdout’s lower bound clears breakeven | Your rule (line 5) | `Readout` read → <Op verb="Ad.update" /> to the next tier | you approve the raise · Take this one | you |
| promotion-intake `promotion-intake` | a hand-off field is missing, the read is not behavior, or a claim is unsourced | Agent’s call, with its reason | refuses it with the field it lacks | nobody; the free Play keeps it | the same |
| promoted-comment-desk `promoted-comment-desk` | a comment on a promoted post | Agent’s call, with its reason | <Op verb="Conversation.answer" /> from approved Pages; a hard one <Op verb="Conversation.escalate" /> | hard ones to you, due 1 business day · Take this one | you |

| When they write back | Fixture reply | What happens | Who answers | By when | Call |
| --- | --- | --- | --- | --- | --- |
| Interested | a comment: “How do I try this for my 40 units?” | the trial link and one next step; the signup goes to `instant-response` | Content Lead · your agent | 1 h | <Op verb="Conversation.answer" /> |
| Send me pricing | a comment: “What does it cost?” | the Pricing Page’s list Price, as it stands | Content Lead · your agent | 1 h | <Op verb="Conversation.answer" /> |
| Not now | the lead form: “Just send me the checklist.” | the checklist, then `lead-nurture` holds them | nobody | at once | <Op verb="Lead.nurture" /> |
| Wrong person | a comment: “Tagging our ops manager.” | a thank-you; it never writes to the tagged person | Content Lead · your agent | 1 h | <Op verb="Conversation.answer" /> |
| Out of office | an auto-reply to the lead form’s email | the follow-up waits for the return date | nobody | the return date | <Op verb="Message.schedule" /> |
| Question it can’t answer | a comment: “Is this the same tool my landlord uses?” | To a person; the comment stays up | you | 1 business day | <Op verb="Conversation.escalate" /> |
| Stop | a comment: “Stop showing me this.” | they leave every Audience of the Play | nobody | at once | <Op verb="Contact.suppress" /> |

## When it hands to you
Every Ad, every tier raise and every hard comment comes to **you**: the founder seat approves spend, and the Marketing Lead Role has no person. What lands in your queue, as captured (`draft` and `checks` are Not live yet, gaps #7).

```json title="What lands in each queue · sample"
[
  {
    "$id": "https://api.sb/leaseline.app/approvals/approval_83",
    "$type": "Approval",
    "verdict": null,
    "deadline": "2026-10-20T17:00:00Z",
    "draft": "Sponsor post_31 on LinkedIn at $10 a day in 12 served regions; 12 held out",
    "route": "winner-promotion",
    "checks": [
      {
        "check": "claims-resolve",
        "status": "pass"
      },
      {
        "check": "tier-gate",
        "status": "pass"
      }
    ]
  },
  {
    "$id": "https://api.sb/leaseline.app/tasks/task_562",
    "$type": "Task",
    "assignee": "worker_1",
    "due": "2026-09-30T17:00:00Z",
    "on": "post_24",
    "brief": {
      "who": "a commenter on the move-in checklist reel",
      "said": "Is this the same tool my landlord uses to raise rent?",
      "chose": "promoted-comment-desk",
      "why": "a hard comment goes to a person; it stays up",
      "never": [
        "hide it",
        "delete it"
      ]
    }
  }
]
```

You answer with <Op verb="Approval.approve" /> or <Op verb="Approval.refuse" />, <Op verb="Task.claim" /> (Take this one), <Op verb="Task.return" /> with a reason, or <Op verb="Action.redirect" />, each a one-call window. An Approval unanswered by its deadline expires, and the Ad stays paused. A Task nobody claims by its due time goes up a Role through <Op verb="Task.escalate" />, and `person-promise` fires.

**On time:** you, 6 of 6 within 1 business day, median 3 h, n 6, ■□□□ Too early to tell beside Other Startups’ 91 % on time. **Your queue:** in week 1, 2 approvals and 1 weekly review; then about 1 approval and 1 hard comment a week.

**The fixed line:** <Op verb="Deal.assign" /> sets `owner` to the receiving person, the agent is never a Deal’s owner, and Meetings land on the owner’s calendar under the owner’s name.

## When it won’t send
| Check (refuses one send) | Refuses | Fix | Then |
| --- | --- | --- | --- |
| <a id="check-intake"></a>`intake` (the record’s Intake gate) | a candidate with a missing hand-off field or no winner verdict on a behavior read | Read it on saves, clicks or signups, then hand it off again. | <Op verb="Experiment.conclude" /> |
| <a id="check-claims-resolve"></a>`claims-resolve` (hub) | an Ad sentence with no approved Page behind it | Cut the sentence, or publish the Page that says it. | <Op verb="Content.draft" /> |
| <a id="check-disclosure"></a>`disclosure` (the record’s Disclosure gate) | an Ad whose labels differ from the asset’s disclosure manifest | Carry the asset’s labels onto the Ad unchanged. | <Op verb="Ad.update" /> |
| <a id="check-read-lock"></a>`read-lock` (the record’s Read lock) | an edit to a live arm during its read | Start a new arm with the change; the read stays clean. | <Op verb="Ad.create" /> |
| <a id="check-tier-gate"></a>`tier-gate` (the record’s Tier gate) | spend past a tier line with no winning holdout Readout | Wait for the 28-day region read, then approve the raise. | <Op verb="Approval.approve" /> |
| <a id="check-price"></a>`price` (gaps #1) | a Price in an Ad that differs from the Offer’s live Price | Quote the Pricing Page’s live Price. | <Op verb="Approval.approve" /> |

| Stop (pauses the Play) | Pauses when | Then |
| --- | --- | --- |
| <a id="stop-claims"></a>`claims` | a fact a live Ad states changes or fails its recheck | every Ad stating it pauses within 1 h |
| <a id="stop-arm"></a>`arm` | an arm past 2× its allowed cost after 50 bid events, or 3× with no counted conversion | that arm pauses |
| <a id="stop-slot-loser"></a>`slot-loser` | a confirmation loses to its control | the slot pauses; the candidate waits 90 days |
| <a id="stop-lift-floor"></a>`lift-floor` | a refresh holdout’s lower bound sits under breakeven | the placement drops one tier, or pauses |
| <a id="stop-payback"></a>`payback` | 14-day cost per first payment above first-payment margin, on 5 or more | the Play pauses |
| <a id="stop-offer"></a>`offer` | 3 confirmations running lose to the story control | new slots halt; an Offer Task goes to you |
| <a id="stop-quality"></a>`quality` | counted conversions’ 30-day payer rate under half your baseline, on 20 or more | the Play pauses; an Offer Task goes to you |
| <a id="stop-refund"></a>`refund` | refunds on promoted conversions at twice your baseline | every Ad pauses |
| <a id="stop-conversion-feed"></a>`conversion-feed` | no counted conversion for 72 h while spend flows | changes freeze; at 7 days the Play pauses |
| <a id="stop-drift"></a>`drift` | a live Ad or setting differs from the approved state | it pauses or resets on the daily read |
| <a id="stop-grant"></a>`grant` (LinkedIn) | a member revokes the grant or deletes the post | that Ad ends, with no retry |
| <a id="stop-comment"></a>`comment` (Facebook and Instagram) | comments calling an Ad fake or misleading pass 5 % of 100 or more | that Ad pauses |
| <a id="stop-spend-limit"></a>`spend-limit` | month-to-date spend reaches the Limit | the Play pauses until the month turns (<Op verb="Limit.raise" />) |
| <a id="stop-kill-switch"></a>`kill-switch` | you press it | every Ad pauses through the proxy; write scope is revoked |

The captured refusal, then the Stop that fired as a `Campaign.paused` reason (Not live yet, gaps #2); the arm that ran instead is the move-in reel in Example.

```json title="The captured refusal · sample"
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "The Ad for the move-in reel did not run: its caption was edited after the free read.",
  "checks": [
    {
      "check": "read-lock",
      "status": "fail",
      "doc": "/plays/promote-winners#check-read-lock",
      "fix": "Start a new arm with the change; the read stays clean.",
      "then": "Ad.create"
    },
    {
      "check": "claims-resolve",
      "status": "pass"
    }
  ]
}
```


```json title="The Stops that fired, as Campaign.paused reasons · sample"
[
  {
    "reason": {
      "limit": "arm",
      "observed": "$61 with 0 counted conversions",
      "bound": "3 × $19 allowed cost",
      "window": "arm_4"
    },
    "doc": "/plays/promote-winners#stop-arm",
    "task": "task_548"
  }
]
```

## Did it work
The 24 metro regions were split at random, 12 served and 12 held out, for each 28-day read (rules m–o); each figure’s call is <Op verb="Metric.query" /> by node and by arm, and every node above the leaf is Not live yet (gaps #4).

`+3.3 added Leads per $100 · ■■□□ Early sign · n 24 regions · path → arr`

| Node | Agent half (served regions) | Your half (held out) | Added | Confidence | Basis |
| --- | --- | --- | --- | --- | --- |
| `promoted-lead-lift` | 38 new Leads on $640 (5.9 per $100) | 17 new Leads, the same regions’ size | +3.3 per $100 [0.4, 6.2] | ■■□□ Early sign | held back at random by region · n 24 · 2 reads of 28 days |
| `new-leads` | 38 | 17 | +21 [3, 39] | ■■□□ Early sign | the same assignment |
| `new-arr` ↑ | $1,388, 4 Customers on Pro · Booked: credited, not all caused by this Play | $347, 1 Customer | +$1,041 [−$471, +$2,553] · Worth about +$430 a month · Not carrying up yet | ■□□□ Too early to tell (4 vs 1 Customers) | the same · 5 trials still deciding |
| `net-new-arr` · `arr` | +$1,388 | +$347 | +$1,041, carried: it moves no other bridge term | ■□□□ Too early to tell | carried from `new-arr` |

The leaf counts new Leads per $100 of promoted spend in served regions against held-out regions over each 28-day holdout, to 10 regions in each half; both reads are in, and tier 2’s read closes on Nov 16.

**How it moves it:** each dollar ran behind a post people had already saved and shared on their own, as it was, at a median $19 per click-through to the trial Page.

**Guardrails:** served against held out: paid per Lead, 0.11 against 0.06 (`new-paying-customers` per Lead), and refunds on promoted conversions, 0 of 4 against 0 of 1.

```json title="The Campaign card · sample"
{
  "$id": "https://api.sb/leaseline.app/campaigns/promote-winners",
  "$type": "Campaign",
  "play": "play:promote-winners@1",
  "path": [
    "promoted-lead-lift",
    "new-leads",
    "new-arr",
    "net-new-arr",
    "arr"
  ],
  "experiment": {
    "metric": "promoted-lead-lift",
    "unit": "ratio",
    "heldBack": 12,
    "decision": null
  },
  "readout": {
    "value": 3.3,
    "n": 24,
    "maturing": 0,
    "interval": [
      0.4,
      6.2
    ],
    "readability": "C",
    "basis": "held-back",
    "pooled": false
  },
  "ledger": {
    "spend": 640,
    "workerCost": 17,
    "hours": 2.1,
    "pipelineArr": 0,
    "wonArr": 1388,
    "projectedArr": 428,
    "paybackDays": 168
  }
}
```

Outside benchmark: this Play’s catalog record carries no sourced range yet, so Other Startups’ results stand in: +2.4 added Leads per $100, ■■■□ Likely, n 312 regions.

**Before it · After it.** Before it: `organic-social`, `founder-voice` and `video-ladder` hand it their free winners; `creator-ads` runs inside it. After it: `instant-response` answers the Leads, and `retargeting` reads the visitors the Ads sent.

## Example
Leaseline ran it from 2026-08-24 to 2026-10-19 (56 days): 6 candidates, 3 promoted, 2 refused at intake and 1 queued, at tier 1 ($10 a day) for 28 days and tier 2 ($14 a day) after. Read every captured thread at `?threads=all`; three follow the tables.

| What happened, by Tactic | Decisions | Spend | Leads (counts) |
| --- | --- | --- | --- |
| Run the winner as an Ad | 3 | $640 | 38 in served regions |
| Queue it · Refuse at intake · Raise a tier | 1 · 2 · 1 | — · — · +$4 a day | — · — · tier 2 read open |
| Answer comments | 31 | — | 3 trials from comment answers |
| **Served regions** · held out | **12** · 12 regions | **$640** · $0 | **38** · 17 |
| At $300 a month (the Use it when line) | about 3 Ads in 56 days | held back at random by region | Other Startups’ results: +2.4 per $100, ■■■□ Likely, n 312 |

| Day | Event | What happened | Your time |
| --- | --- | --- | --- |
| 0 | `event_4002` Campaign.created | the three steps; the dry run fails `spend-limit` | 10 min |
| 0 | `event_4004` Limit.raised | a $340 monthly spend Limit | 2 min |
| 1 | `event_4011` Ad.created | post_24, the move-in checklist reel, after your approval | 4 min |
| 3 | `event_4030` Lead.created | the first Lead from a promoted post | — |
| 17 | `event_4071` Campaign.paused | `arm`: arm_4 spent $61 with no counted conversion; paused | 5 min |
| 29 | `event_4098` Readout.created | the tier 1 read: +3.3 per $100, lower bound over breakeven; you approved tier 2 | 6 min |
| 1–56 | `event_4010` Approval.created, first of 5 | you approved 3 Ads and 1 raise; `read-lock` refused 1 edited caption, run instead as a new arm; weekly reviews | 1 h 34 min · **total 2.1 h measured, 2 h asked** |

> **Dana’s LinkedIn post, sponsored under her grant, Sep 22** · “We lost 3 renewals last spring because the notices went out late. So we built the 60-day reminder into Leaseline.”¹
>
> **A property manager in Denver, served region, Sep 24** · clicked through, started a trial, and paid for Pro on Oct 9. `Invoice.collected`, +$347 ARR, credited to the promoted post.²

```diff title="The move-in reel, 2026-09-10 · the edited caption read-lock refused, then the arm that ran"
- arm_2 (live): caption edited to “Trusted by 10,000 managers. Try it free.”
+ arm_5 (new arm): the winning caption as it won, “Our move-in checklist, the one we use ourselves.”
```

> **A commenter on the move-in reel, 2026-09-29 13:12** · “Is this the same tool my landlord uses to raise rent?” · **Leaseline, 13:14** · *Thanks for asking. Dana, our founder, answers questions like this and replies here by tomorrow 17:00.*³ The comment stays up; the Brief went to you (`task_562`), and you replied at 15:40.

¹ The winner itself, unedited: any edit would make it a new arm. ² Credit by the coded link on the Ad; counted from Stripe, never from the Venue’s own count. ³ The routed line, compiled from the Role’s Settings.

## Your settings
```mdx title="plays/promote-winners.mdx"
---
holdout: regions   # 24 metros, half held out at random for each 28-day read
---
Promote only posts that won on saves, clicks or signups, as they are. 3 paid slots.
Start at $10 a day; raise a tier only on a winning region read.
```

```text
$ npx startup-builder show plays/promote-winners
holdout regions · 3 slots · tier 1 $10 a day · tier on a winning read     your file  plays/promote-winners.mdx:2,4,5 (Ask 2026-08-24)
worker role:paid-media-manager, owns never · chooses none (D38): the Decision picks a Tactic · approval every-send · checks intake, claims-resolve, disclosure, read-lock, tier-gate, price · stops 14 (12 on every lane) · placements facebook, instagram, tiktok, youtube, linkedin, reddit, google-search, bing    catalog  play:promote-winners@1 (Not live yet, gaps #5)
```

### Every key from the catalog
| Key | Id | What it is |
| --- | --- | --- |
| Tactic | <a id="tactic-promotion-intake"></a>`promotion-intake` | Promotion intake checks each hand-off against the contract, the claims source and the disclosure manifest, and queues only winners read on behavior. Starts at Sends nothing. |
| Tactic | <a id="tactic-promotion-queue"></a>`promotion-queue` | Promotion queue ranks accepted candidates each week, fills paid slots by score with one slot in five drawn at random, and builds each slot’s plan. Starts at You approve each one. |
| Tactic | <a id="tactic-winner-promotion"></a>`winner-promotion` | Winner promotion runs a free-placement winner as a paused, fenced Ad against its control, and judges it on counted conversions from Stripe. Starts at You approve each one. |
| Tactic | <a id="tactic-tier-gate"></a>`tier-gate` | Tier gate holds a winner’s spend at its tier until a holdout shows incremental conversions per $100 clear breakeven, and re-reads every placement each quarter. Starts at You approve each one. |
| Tactic | <a id="tactic-promoted-comment-desk"></a>`promoted-comment-desk` | Promoted comment desk answers questions on promoted Posts from the claims source, hides spam and exposed personal data, and routes hard comments to a human. Starts at You approve each one. |
| Tactic | <a id="tactic-ad-write-proxy"></a>`ad-write-proxy` | Ad write proxy is the only write path to an ads Venue, holding the token outside the model and forcing every create to paused. Starts at You approve each one. |
| Tactic | <a id="tactic-incrementality-holdout"></a>`incrementality-holdout` | Incrementality holdout measures the first Transactions a paid arm adds, by a geo holdout or a day switchback, before any scale step. Starts at You approve each one. |
| Tactic | <a id="tactic-payback-gate"></a>`payback-gate` | Payback gate pauses a paid Play the moment its cost per first Transaction outruns first-transaction margin, with no approval and no wait. Starts at Sends nothing. |
| Tactic | <a id="tactic-ad-drift-monitor"></a>`ad-drift-monitor` | Ad drift monitor diffs every live Ad’s rendered creative, text and enhancement settings against the approved source daily, and pauses or resets what drifted. Starts at Sends nothing. |
| Trigger | <a id="trigger-promotion-candidate"></a>`promotion-candidate` | Promotion candidate: `event` on `Experiment.concluded`, window 30d. |
| Trigger | <a id="trigger-promoted-conversion"></a>`promoted-conversion` | Promoted conversion: `event` on `Invoice.collected`, window 30d. |
| Trigger | <a id="trigger-ad-fatigue"></a>`ad-fatigue` | Ad fatigue: `threshold` on `Monitor.checked`, window 3d. |
| Trigger | <a id="trigger-fact-new"></a>`fact-new` | New or changed fact: `event` on `Fact.recorded`, window 14d. |
| Trigger | <a id="trigger-sponsor-grant-change"></a>`sponsor-grant-change` | Sponsor grant change: `state` on `Integration.synced`, window 1d. |
| Trigger | <a id="trigger-margin-change"></a>`margin-change` | Margin change: `event` on `Offer.repriced`, window 1d. |
| Trigger | <a id="trigger-holdout-closed"></a>`holdout-closed` | Holdout closed: `event` on `Experiment.matured`, window 7d. |
| Trigger | <a id="trigger-demand-budget-change"></a>`demand-budget-change` | Demand budget change: `threshold` on `PlayMix.compiled`, window 1d. |