Skip to content

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.

Channel
Ads and sponsorships
Run oneMode try
You, to your agent

Hand our latest free winner to the paid media agent and show me what it chose and why. Don’t spend.

curl "https://api.sb/leaseline.app/events?where=name%20in%20(%22Experiment.concluded%22%2C%20%22Invoice.collected%22%2C%20%22Monitor.checked%22%2C%20%22Fact.recorded%22%2C%20%22Integration.synced%22%2C%20%22Offer.repriced%22%2C%20%22Experiment.matured%22%2C%20%22PlayMix.compiled%22)&limit=1" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"

# Not live yet (gaps #7)
curl -X POST "https://api.sb/leaseline.app/triggers/fire?mode=try" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"like":"event_4120"}'

# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/decisions?where=on%20%3D%20%22post_31%22" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"

# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/actions?where=on%20%3D%20%22post_31%22" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
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 }
npx startup-builder do Event.list --json '{"where":"name in (\"Experiment.concluded\", \"Invoice.collected\", \"Monitor.checked\", \"Fact.recorded\", \"Integration.synced\", \"Offer.repriced\", \"Experiment.matured\", \"PlayMix.compiled\")","limit":1}'
npx startup-builder do Trigger.fire --json '{"like":"event_4120"}' --try   # Not live yet (gaps #7)
npx startup-builder do Decision.list --json '{"where":"on = \"post_31\""}'   # Not live yet (gaps #7)
npx startup-builder do Action.list --json '{"where":"on = \"post_31\""}'   # Not live yet (gaps #7)
{
  "name": "do",
  "arguments": {
    "code": "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\nconst { subject } = await $.Trigger.fire({ like: last.$id }, { mode: 'try' })   // the same Event again, in try mode · Not live yet, gaps #7\nconst { decisions: [decision] } = await $.Decision.list({ where: `on = \"${subject}\"` })   // what it chose, and why · Not live yet, gaps #7\nconst { actions } = await $.Action.list({ where: `on = \"${subject}\"` })   // what it did, with its checks · Not live yet, gaps #7\nreturn { decision, actions }",
    "mode": "try"
  }
}
Response200application/json
{
  "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"
    ],
  …
sample · 4 Not live yetPreview

On PATCH /{namespace}/campaigns/{id}Off PATCH /{namespace}/campaigns/{id}Take this one POST /{namespace}/tasks/{task}/claim

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 winnerChoiceReason
Dana’s post on renewal slippage: saves 14.2 per 1,000 against 6.1winner-promotionwon on behavior, claims sourced, grant on
The move-in checklist reel: won on watch time, slot full this weekpromotion-queueall 3 paid slots taken; it ranks 2nd
Tier 1 read on post_31: +3.3 Leads per $100, lower bound over breakeventier-gatea winning holdout clears the tier line
A post that won on likes only, with a “10,000 managers” claimpromotion-intakelikes are no behavior read, and the count is unsourced

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.

Start it this week

Terminal
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"}'
422Refusedpromote-winners can’t start yet: 1 check fails and 1 warns.
CheckFixThen
use-when pass · Not live yet, gaps #8Keep a winner and $300 a month, or run organic-social first.POST /{namespace}/metrics/query
baseline pass · Not live yet, gaps #8Read it once; it fills Before in Your number.POST /{namespace}/metrics/query
spend-limit failSet a $340 monthly spend Limit; the Play pauses at it.POST /{namespace}/limits/{limit}/raise
role-unstaffed warn · Not live yet, gaps #8Staff the Marketing Lead, or leave it: approvals and hard comments come to you.POST /{namespace}/roles/{role}/staff
JSON
{
  "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"
    }
  ]
}

It says no, says why, and names the fix.

CheckFix (one call)Your timeWait
use-when · baseline · pass: 2 winners, $340 a month; 1.9 Leads per $100, n 6 (gaps #8)POST /{namespace}/metrics/query on promoted-lead-lift, which fills Before in Your number1 minnone
spend-limit · fail: no spend LimitPOST /{namespace}/limits/{limit}/raise to $340 a month2 minnone
role-unstaffed · warn (gaps #8): no Marketing LeadPOST /{namespace}/roles/{role}/staff, or leave it: approvals and hard comments come to you0 minnone

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

WhenTrigger idStarts onWindowHow it gets in
A free post or line wins its read on behaviorpromotion-candidateevent on Experiment.concluded where verdict = 'winner' on a behavior read, on a free placementintake within 1d; a slot within 30dPOST /{namespace}/experiments/{experiment}/conclude on the free read, from organic-social or founder-voice
A Customer a promoted Ad brought pays their first Invoicepromoted-conversionevent on Invoice.collected where the first collected Invoice of a Customer whose Acquisition names a promoted Ad30dPOST /{namespace}/integrations/connect (Stripe)
A member grants or revokes sponsoring their own postsponsor-grant-changestate on Integration.synced where a member's sponsor grant on their own Post becomes granted or revoked1dPOST /{namespace}/integrations/connect with LinkedIn
+ 5 more for ad fatigue, new facts, margins, holdout reads and budget moves (?lanes=all)
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
  }
}
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.

OptionRung todayEvidence (passed of n)Moves up atDrops back whenYou can
Run the winner as an Ad (winner-promotion) · Raise a tier (tier-gate)You approve each one3 of 3 · 1 of 1never: the spend is yours—POST /{namespace}/approvals/{approval}/approve · POST /{namespace}/approvals/{approval}/refuse
Queue it (promotion-queue) · Refuse at intake (promotion-intake)It does it4 of 4 · 2 of 2——POST /{namespace}/actions/{action}/redirect
Answer comments (promoted-comment-desk)It does it, you can stop it31 of 31met on day 12 (the first 20 answers)any answer fails a Check → You approve each onePOST /{namespace}/actions/{action}/cancel inside the hold
Change the tier linesYou approve each one0 mergednever—POST /{namespace}/changes/{change}/merge · POST /{namespace}/changes/{change}/revert
Never: an edited winner, a count you didn’t earn, a critical comment hiddenRefused, every time2 refused at intakenever—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).

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; POST /{namespace}/roles/{role}/staff seats it.

Terminal
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

OptionPicked whenDecided byRunsHands toIf it’s just you
winner-promotion winner-promotiona winner passes intake and a slot is openAgent’s call, with its reasonPOST /{namespace}/experiments/{experiment}/start (regions) → POST /{namespace}/audiences → POST /{namespace}/ads, paused until you approveyou approve it, due 1 business day · Take this oneyou
promotion-queue promotion-queueevery paid slot is fullYour rule (3 slots, line 4)ranks it; one slot in five drawn at randomnobodythe same
tier-gate tier-gatea region holdout’s lower bound clears breakevenYour rule (line 5)Readout read → PATCH /{namespace}/ads/{id} to the next tieryou approve the raise · Take this oneyou
promotion-intake promotion-intakea hand-off field is missing, the read is not behavior, or a claim is unsourcedAgent’s call, with its reasonrefuses it with the field it lacksnobody; the free Play keeps itthe same
promoted-comment-desk promoted-comment-deska comment on a promoted postAgent’s call, with its reasonPOST /{namespace}/conversations/{conversation}/answer from approved Pages; a hard one POST /{namespace}/conversations/{conversation}/escalatehard ones to you, due 1 business day · Take this oneyou
When they write backFixture replyWhat happensWho answersBy whenCall
Interesteda comment: “How do I try this for my 40 units?”the trial link and one next step; the signup goes to instant-responseContent Lead · your agent1 hPOST /{namespace}/conversations/{conversation}/answer
Send me pricinga comment: “What does it cost?”the Pricing Page’s list Price, as it standsContent Lead · your agent1 hPOST /{namespace}/conversations/{conversation}/answer
Not nowthe lead form: “Just send me the checklist.”the checklist, then lead-nurture holds themnobodyat oncePOST /{namespace}/leads/{lead}/nurture
Wrong persona comment: “Tagging our ops manager.”a thank-you; it never writes to the tagged personContent Lead · your agent1 hPOST /{namespace}/conversations/{conversation}/answer
Out of officean auto-reply to the lead form’s emailthe follow-up waits for the return datenobodythe return datePOST /{namespace}/messages/{message}/schedule
Question it can’t answera comment: “Is this the same tool my landlord uses?”To a person; the comment stays upyou1 business dayPOST /{namespace}/conversations/{conversation}/escalate
Stopa comment: “Stop showing me this.”they leave every Audience of the Playnobodyat oncePOST /{namespace}/contacts/{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).

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 POST /{namespace}/approvals/{approval}/approve or POST /{namespace}/approvals/{approval}/refuse, POST /{namespace}/tasks/{task}/claim (Take this one), POST /{namespace}/tasks/{task}/return with a reason, or POST /{namespace}/actions/{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 POST /{namespace}/tasks/{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: POST /{namespace}/deals/{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)RefusesFixThen
intake (the record’s Intake gate)a candidate with a missing hand-off field or no winner verdict on a behavior readRead it on saves, clicks or signups, then hand it off again.POST /{namespace}/experiments/{experiment}/conclude
claims-resolve (hub)an Ad sentence with no approved Page behind itCut the sentence, or publish the Page that says it.POST /{namespace}/content/draft
disclosure (the record’s Disclosure gate)an Ad whose labels differ from the asset’s disclosure manifestCarry the asset’s labels onto the Ad unchanged.PATCH /{namespace}/ads/{id}
read-lock (the record’s Read lock)an edit to a live arm during its readStart a new arm with the change; the read stays clean.POST /{namespace}/ads
tier-gate (the record’s Tier gate)spend past a tier line with no winning holdout ReadoutWait for the 28-day region read, then approve the raise.POST /{namespace}/approvals/{approval}/approve
price (gaps #1)a Price in an Ad that differs from the Offer’s live PriceQuote the Pricing Page’s live Price.POST /{namespace}/approvals/{approval}/approve
Stop (pauses the Play)Pauses whenThen
claimsa fact a live Ad states changes or fails its recheckevery Ad stating it pauses within 1 h
arman arm past 2× its allowed cost after 50 bid events, or 3× with no counted conversionthat arm pauses
slot-losera confirmation loses to its controlthe slot pauses; the candidate waits 90 days
lift-floora refresh holdout’s lower bound sits under breakeventhe placement drops one tier, or pauses
payback14-day cost per first payment above first-payment margin, on 5 or morethe Play pauses
offer3 confirmations running lose to the story controlnew slots halt; an Offer Task goes to you
qualitycounted conversions’ 30-day payer rate under half your baseline, on 20 or morethe Play pauses; an Offer Task goes to you
refundrefunds on promoted conversions at twice your baselineevery Ad pauses
conversion-feedno counted conversion for 72 h while spend flowschanges freeze; at 7 days the Play pauses
drifta live Ad or setting differs from the approved stateit pauses or resets on the daily read
grant (LinkedIn)a member revokes the grant or deletes the postthat Ad ends, with no retry
comment (Facebook and Instagram)comments calling an Ad fake or misleading pass 5 % of 100 or morethat Ad pauses
spend-limitmonth-to-date spend reaches the Limitthe Play pauses until the month turns (POST /{namespace}/limits/{limit}/raise)
kill-switchyou press itevery 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.

422RefusedThe Ad for the move-in reel did not run: its caption was edited after the free read.
CheckFixThen
read-lock failStart a new arm with the change; the read stays clean.POST /{namespace}/ads
claims-resolve pass—POST /{namespace}/content/draft
JSON
{
  "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"
    }
  ]
}

It says no, says why, and names the fix.

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 POST /{namespace}/metrics/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

Chose
winner-promotion
Why
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
From
winner-promotion · promotion-queue · tier-gate · promotion-intake · promoted-comment-desk
Expects
3.0 or more added Leads per $100 in served regions by day 28
Reopens when
the arm passes 2 times its allowed cost after 50 bid events
By
worker_7
JSON
{
  "$id": "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"
}
NodeAgent half (served regions)Your half (held out)AddedConfidenceBasis
promoted-lead-lift38 new Leads on $640 (5.9 per $100)17 new Leads, the same regions’ size+3.3 per $100 [0.4, 6.2]■■□□ Early signheld back at random by region · n 24 · 2 reads of 28 days
new-leads3817+21 [3, 39]■■□□ Early signthe 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 tellcarried 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.

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 TacticDecisionsSpendLeads (counts)
Run the winner as an Ad3$64038 in served regions
Queue it · Refuse at intake · Raise a tier1 · 2 · 1— · — · +$4 a day— · — · tier 2 read open
Answer comments31—3 trials from comment answers
Served regions · held out12 · 12 regions$640 · $038 · 17
At $300 a month (the Use it when line)about 3 Ads in 56 daysheld back at random by regionOther Startups’ results: +2.4 per $100, ■■■□ Likely, n 312
DayEventWhat happenedYour time
0event_4002 Campaign.createdthe three steps; the dry run fails spend-limit10 min
0event_4004 Limit.raiseda $340 monthly spend Limit2 min
1event_4011 Ad.createdpost_24, the move-in checklist reel, after your approval4 min
3event_4030 Lead.createdthe first Lead from a promoted post—
17event_4071 Campaign.pausedarm: arm_4 spent $61 with no counted conversion; paused5 min
29event_4098 Readout.createdthe tier 1 read: +3.3 per $100, lower bound over breakeven; you approved tier 26 min
1–56event_4010 Approval.created, first of 5you approved 3 Ads and 1 raise; read-lock refused 1 edited caption, run instead as a new arm; weekly reviews1 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.²

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

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

KeyIdWhat it is
Tacticpromotion-intakePromotion 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.
Tacticpromotion-queuePromotion 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.
Tacticwinner-promotionWinner 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.
Tactictier-gateTier 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.
Tacticpromoted-comment-deskPromoted 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.
Tacticad-write-proxyAd 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.
Tacticincrementality-holdoutIncrementality 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.
Tacticpayback-gatePayback 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.
Tacticad-drift-monitorAd 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.
Triggerpromotion-candidatePromotion candidate: event on Experiment.concluded, window 30d.
Triggerpromoted-conversionPromoted conversion: event on Invoice.collected, window 30d.
Triggerad-fatigueAd fatigue: threshold on Monitor.checked, window 3d.
Triggerfact-newNew or changed fact: event on Fact.recorded, window 14d.
Triggersponsor-grant-changeSponsor grant change: state on Integration.synced, window 1d.
Triggermargin-changeMargin change: event on Offer.repriced, window 1d.
Triggerholdout-closedHoldout closed: event on Experiment.matured, window 7d.
Triggerdemand-budget-changeDemand budget change: threshold on PlayMix.compiled, window 1d.

Compiled, not written: edit the Play record promote-winners and its Leaseline run, then regenerate. Every step runs from a sentence or a line of code. In your words: the CRM words you know, and where each lives here.