Skip to content

Positioning tournament

Tests three ways of saying what you do, as ads that differ in one phrase, and counts only signups that fit and act.

Channel
Signup flows
Run oneMode try
You, to your agent

Hand our latest tournament round to the Marketing Lead agent and show me what it chose and why. Don’t change anything.

curl "https://api.sb/leaseline.app/events?where=name%20in%20(%22Story.revised%22%2C%20%22PlayMix.compiled%22%2C%20%22Dashboard.scheduled%22%2C%20%22Monitor.checked%22%2C%20%22Conversation.closed%22%2C%20%22Lead.qualified%22%2C%20%22Metric.watched%22%2C%20%22Territory.requested%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_1418"}'

# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/decisions?where=on%20%3D%20%22experiment_11%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%22experiment_11%22" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
import { $ } from 'startup-builder'

const { events: [last] } = await $.Event.list({ where: 'name in ("Story.revised", "PlayMix.compiled", "Dashboard.scheduled", "Monitor.checked", "Conversation.closed", "Lead.qualified", "Metric.watched", "Territory.requested")', 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 (\"Story.revised\", \"PlayMix.compiled\", \"Dashboard.scheduled\", \"Monitor.checked\", \"Conversation.closed\", \"Lead.qualified\", \"Metric.watched\", \"Territory.requested\")","limit":1}'
npx startup-builder do Trigger.fire --json '{"like":"event_1418"}' --try   # Not live yet (gaps #7)
npx startup-builder do Decision.list --json '{"where":"on = \"experiment_11\""}'   # Not live yet (gaps #7)
npx startup-builder do Action.list --json '{"where":"on = \"experiment_11\""}'   # Not live yet (gaps #7)
{
  "name": "do",
  "arguments": {
    "code": "const { events: [last] } = await $.Event.list({ where: 'name in (\"Story.revised\", \"PlayMix.compiled\", \"Dashboard.scheduled\", \"Monitor.checked\", \"Conversation.closed\", \"Lead.qualified\", \"Metric.watched\", \"Territory.requested\")', 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_596",
    "$type": "Decision",
    "choice": "ask-for-the-pick",
    "reason": "the finals’ Readout is graded B: phrase 14 (“every owner’s renewal notice, 60 days out”) won on LinkedIn and on the Site split, 0.62 against the control’s 0.31 qualified signups per 1,000 impressions; phrases 3 and 22 trail it",
    "stakes": "high",
    "options": [
      "start-a-round",
      "probe-instead",
      "shift-budget",
      "rerun-the-finalists",
      "ask-for-the-pick",
      "reopen"
  …
sample · 4 Not live yetPreview

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

action_2 is the pick card: the top three phrases with their evidence, and no click-through anywhere on it. action_3 hands it to you; the Story changes only on your pick (pick).

+0.31 per 1,000 · ■■□□ Early sign · n 84,000 · path → arr

Try another: the roundChoiceReason
Round 1 finals: phrase 14 at 0.62 per 1,000 against the control’s 0.31, graded Bask-for-the-pickan admissible read: the pick is yours
Round 1 screen, day 28: the top 6 phrases and the controlrerun-the-finaliststhe screen passed its finalists: 21 days on a second Venue and the Site
Screen day 9: phrases 3, 14 and 22 lead on ICP-matched signupsshift-budgetinside the fence: no phrase under a quarter or over 4 times its share
A rival’s homepage now says “renewals, per owner”reopena rival is saying the winning phrase: a new round with it as the control

Where it sits: Stranger → Lead → Trialing → Activated → At risk → Churned → Won back · Qualified signup rate → New Leads → New Customers ↑ → ARR added each week → ARR

Worth’s formula: about +0.31 qualified signups per 1,000 impressions (your finals, ■■□□ Early sign) × 60,000 impressions a month × 0.20 qualified signups to paid × $347 Pro ARR ≈ +$1,290, once the winning phrase runs on your ads and Site. Under First signal: A round takes 7 weeks: 4 days of equal budgets, the screen to day 28, then 21 days of finals; a signup counts 14 days after it lands.

sample · api.sb/leaseline.app · 41 Customers at 2026-10-19 · on Lead.qualified · pick card drafted 9 min after the last signup matured · 9 keys Not live yet

Start it this week

Terminal
npx startup-builder add play positioning-tournament   # 1 · writes plays/positioning-tournament.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":"positioning-tournament"}'

# 3 · keep it: you approve the slate and make the pick
curl -X POST https://api.sb/leaseline.app/campaigns \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"play":"positioning-tournament"}'

# the round’s Limit
curl -X POST https://api.sb/leaseline.app/limits \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"positioning-round","amount":1500,"period":"round"}'

# off: every Ad and the Site Flag pause through the proxy
curl -X PATCH https://api.sb/leaseline.app/campaigns/positioning-tournament \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"endsAt":"now"}'
422Refusedpositioning-tournament can’t start yet: 1 check fails and 1 warns.
CheckFixThen
use-when fail · Not live yet, gaps #8Add 8 more quotes from 2 more ICP members; your interview notes hold them.POST /{namespace}/interviews
baseline pass · Not live yet, gaps #8Nothing to do: your current headline becomes the control.POST /{namespace}/metrics/query
fact-check pass · Not live yet, gaps #1Nothing to do: every phrase must cite one of the 9.POST /{namespace}/content/draft
price-split-by-buyer passNothing to do: Variants differ only in the phrase.POST /{namespace}/experiments/{experiment}/start
role-unstaffed warn · Not live yet, gaps #8Staff the Marketing Lead, or leave it: the slate and the pick come to you.POST /{namespace}/roles/{role}/staff
JSON
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "positioning-tournament can’t start yet: 1 check fails and 1 warns.",
  "checks": [
    {
      "check": "use-when",
      "status": "fail",
      "observed": "the Story has its ICP and Problem; 22 quotes from 8 ICP members",
      "doc": "/plays/positioning-tournament#check-use-when",
      "fix": "Add 8 more quotes from 2 more ICP members; your interview notes hold them.",
      "then": "Interview.create"
    },
    {
      "check": "baseline",
      "status": "pass",
      "observed": "the current headline: 0.30 qualified signups per 1,000 impressions, n 61,000",
      "doc": "/plays/positioning-tournament#check-baseline",
      "fix": "Nothing to do: your current headline becomes the control.",
      "then": "Metric.query"
    },
    {
      "check": "fact-check",
      "status": "pass",
      "observed": "the claims source holds 9 claims",
      "doc": "/plays/positioning-tournament#check-fact-check",
      "fix": "Nothing to do: every phrase must cite one of the 9.",
      "then": "Content.draft"
    },
    {
      "check": "price-split-by-buyer",
      "status": "pass",
      "observed": "one Offer and one Price on every Variant",
      "doc": "/plays/positioning-tournament#check-price-split-by-buyer",
      "fix": "Nothing to do: Variants differ only in the phrase.",
      "then": "Experiment.start"
    },
    {
      "check": "role-unstaffed",
      "status": "warn",
      "roles": [
        "marketing-lead"
      ],
      "owned": 0,
      "doc": "/plays/positioning-tournament#check-role-unstaffed",
      "fix": "Staff the Marketing Lead, or leave it: the slate and the pick come to you.",
      "then": "Role.staff"
    }
  ]
}

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

CheckFix (one call)Your timeWait
use-when · fail: 22 quotes from 8 ICP members (gaps #8)POST /{namespace}/interviews for 2 more members, or add the quotes from your notes20 minnone
baseline · pass: 0.30 per 1,000, n 61,000POST /{namespace}/metrics/query on qualified-signup-rate, which fills Before in Your number1 minnone
fact-check · price-split-by-buyer · pass: 9 claims; one Price (Not live yet)nothing; each runs again on every Variant0 minnone
role-unstaffed · warn (gaps #8): the slate and the pick come to youPOST /{namespace}/roles/{role}/staff, or leave it0 minnone

Your number · Before 0.30 · Now 0.62 per 1,000 for phrase 14 · Target 0.5 by Dec 31 · ■■□□ Early sign · n 84,000 impressions in the finals · Other Startups +0.20, ■■■□ Likely

A round spends $1,500 of media under its Limit over 7 weeks, about $930 a month; the Evaluator, the claims check and the phrase slate cost about $4 a round (Worker cost). The Site headline split costs nothing. That comes to about $930 a month while a round runs, and $0 between rounds.

When it starts

WhenTrigger idStarts onWindowHow it gets in
A tournament signup reaches day 14, and its committed action counts or notsignup-maturedevent on Lead.qualified where experiment != null and age = 14d14dPOST /{namespace}/leads/{lead}/qualify at day 14, from your product’s setup Event
Your Story has no stance backed by behaviorstory-ungradedstate on Story.revised where stance.backedBy = null30da state on the Story; no call
Two or more rivals start saying the winning phrasecell-filledthreshold on Monitor.checked where rivalsSaying(winningCell) >= 214dthe monthly rival scan’s POST /{namespace}/monitors/{monitor}/check
+ 6 more: a funded round, the monthly scan date, a rival’s claim change, a batch of Customer words, a decaying stance, and your other Startup asking for the same phrase (?lanes=all)
One signed delivery · StartupBuilder-Signature
{
  "$id": "https://api.sb/leaseline.app/events/event_1418",
  "$type": "Event",
  "name": "Lead.qualified",
  "at": "2026-10-19T09:00:03Z",
  "seq": 1418,
  "data": {
    "lead": {
      "$id": "https://api.sb/leaseline.app/leads/lead_47",
      "icp": true,
      "action": "first lease set up",
      "day": 14
    },
    "experiment": {
      "$id": "https://api.sb/leaseline.app/experiments/experiment_11",
      "variant": "phrase-14",
      "round": "finals"
    },
    "matured": {
      "finals": "all"
    }
  }
}
Reproduce it in test
npx startup-builder trigger Lead.qualified --like event_1418   # replays the last matured signup in test
npx startup-builder clock advance --by 5bd   # checks for your pick (reopensWhen)
npx startup-builder listen Decision.created --forward-to http://localhost:3000/hook

Who runs it

Marketing Lead · your agent (role:marketing-lead, worker_6) holds the Key Result: it scans rivals, mines Customer words, drafts the slate, runs the screen and the finals, and writes the pick card. You (Dana Okafor, person, worker_1) approve the slate, approve any rival’s name, and make the pick; the Story changes only on your pick. Your number links to Did it work.

OptionRung todayEvidence (passed of n)Moves up atDrops back whenYou can
Shift budget to the leaders · Rerun the finalistsIt does it, you can stop it19 daily shifts · 1 of 1met on day 5 (allocation inside the fence)the surrogate Stop → equal shares againPOST /{namespace}/actions/{action}/cancel before the proxy writes
Start a round · Run a stance probe insteadYou approve each one1 of 1 · 0never: a round spends your money—POST /{namespace}/approvals/{approval}/approve · POST /{namespace}/approvals/{approval}/refuse
Ask you to pick · Reopen the tournamentYou decide, every time1 waiting · 0never: the pick is yours—POST /{namespace}/tasks/{task}/claim, then POST /{namespace}/decisions
Change the reward ruleYou approve each one1 merged of 1never—POST /{namespace}/changes/{change}/merge · POST /{namespace}/changes/{change}/revert
Never: a rival’s claim quoted, a superlative no fact holds, two Prices for one OfferRefused, every time2 drafted, 2 refusednever—none: the Check holds it

What it reads · What it may call. It reads ICP-matched signups, their committed action at day 14 and spend, never click-through. Rival pages are read monthly and never quoted, and Customer words become phrases only when three or more people say them. Its tools are POST /{namespace}/content/draft, POST /{namespace}/experiments, POST /{namespace}/approvals, POST /{namespace}/flags, PATCH /{namespace}/ads/{id}, PATCH /{namespace}/flags/{id}, POST /{namespace}/experiments/{experiment}/start, POST /{namespace}/readouts, POST /{namespace}/tasks/{task}/assign, and any other call becomes an Approval under the Role’s Mandate.

The brief (folded): compiled Agent-register Markdown · play:positioning-tournament@1 · each line names its record key
You hold the Marketing Lead Key Result: 0.5 qualified signups per 1,000 impressions by 2026-12-31. ← worker, metric
On each matured signup, funding, scan or decay, choose one option and say why.   ← triggers, chooses
Variants differ in the phrase alone; image, Offer, Price and Page stay the same. ← copyRules 2
No phrase states a fact the claims source lacks; no superlative a fact doesn’t hold. ← copyRules 1, 6
Count a signup only if it fits the ICP and sets up a lease within 14 days.       ← your file, line 6
Never change the Story: hand Dana the pick card.                                 ← Pick gate, chooses.ask-for-the-pick

The slow loop, as a Change. decision_409 (2026-09-07, weekly) read the screen’s day-7 ranking by signups against its day-21 ranking by qualified signups: a rank correlation of 0.41, with 2 of the day-7 top 6 falling out. change_32 made allocation wait for ICP match at signup, not signups alone (your file, line 7), and Dana merged it with POST /{namespace}/changes/{change}/merge. Replayed on the screen, POST /{namespace}/changes/{change}/review put the correlation at 0.68; the finals read back at 0.71, n 7 phrases, ■□□□ Too early to tell (Example, day 49).

Bring your own agent (folded) starts at It does it, you can stop it when it matches or beats the seated agent on the eval suite, and at You approve each one when it does not; the pick stays yours either way.

Terminal
npx startup-builder eval --scenario positioning-tournament-40 --agent "claude -p" --seat marketing-lead   # seated: 0 claims failed sent, rank correlation 0.68 · yours: 1 superlative passed → You approve each one

What it can do next

OptionPicked whenDecided byRunsHands toIf it’s just you
Start a round start-a-roundno stance backed by behavior, and $1,500 fundedYour rule (line 5)POST /{namespace}/content/draft 40 phrases over 3 stances → POST /{namespace}/experiments → POST /{namespace}/approvals for the slateyou approve the slate · Take this oneyou
Run a stance probe instead probe-insteadunder $1,500 fundedYour rule (line 5)POST /{namespace}/flags for a $0 Site headline split → POST /{namespace}/experimentsnobodythe same
Shift budget to the leaders shift-budgetfrom day 5, ICP-matched signups favor some phrasesAgent’s call, with its reasonPATCH /{namespace}/ads/{id} budgets, inside the allocation fencenobodythe same
Rerun the finalists rerun-the-finaliststhe screen passes its top 6 and the controlYour rule (line 8)PATCH /{namespace}/flags/{id} for the Site split → POST /{namespace}/experiments/{experiment}/start on a second Venue, 21 daysnobodythe same
Ask you to pick ask-for-the-pickthe finals read graded A or BYour rule (the Readability gate)POST /{namespace}/readouts → POST /{namespace}/tasks/{task}/assign the pick cardyou pick, within 1 business day · Take this oneyou
Reopen the tournament reopenrivals say the winning phrase, it decays, or the Hypothesis changesAgent’s call, with its reasonPOST /{namespace}/experiments with the incumbent as control → POST /{namespace}/tasks/{task}/assignyou approve the new round · Take this oneyou
When they write backFixture replyWhat happensWho answersBy whenCall
Interesteda reply to the welcome: “Happy to talk. Thursday?”a 20-minute interview on your calendar; their words join the next phrase mineyou (Dana Okafor, person)1 business dayPOST /{namespace}/meetings/book
Send me pricing“What does it cost after the trial?”the Pricing Page’s list Price, the same on every VariantMarketing Lead · your agent5 minPOST /{namespace}/conversations/{conversation}/answer
Not now“Just looking for now.”no interview invite; the signup still counts if they act by day 14nobody—GET /{namespace}/decisions
Wrong person“My boss decides on tools.”it thanks them and asks nothing more; it never writes to someone from their textMarketing Lead · your agent1 business dayPOST /{namespace}/conversations/{conversation}/answer
Out of officean auto-reply to the welcomethe interview invite waits for the return date, oncenobodythe return datePOST /{namespace}/messages/{message}/schedule
Question it can’t answer“Is 60 days the right notice period in my state?”To a person: it is never the agent’s to answeryou (Dana Okafor, person)1 business dayPOST /{namespace}/tasks/{task}/assign
Stop“Unsubscribe.”no welcome follow-up or interview invite sends againnobodyat oncePOST /{namespace}/contacts/{contact}/unsubscribe

When it hands to you

The slate, every rival’s name, every new round and the pick come to you: the Marketing Lead Role has no person, and the Story is yours. One Brief, as captured, then the Approval (brief, draft and checks are Not live yet, gaps #7).

What lands in each queue · sample
[
  {
    "$id": "https://api.sb/leaseline.app/tasks/task_481",
    "$type": "Task",
    "assignee": "worker_1",
    "due": "2026-10-20T17:00:00Z",
    "on": "experiment_11",
    "brief": {
      "what": "the round 1 pick card",
      "top": [
        "phrase 14: every owner’s renewal notice, 60 days out (0.62 per 1,000)",
        "phrase 3: renewals done in an afternoon (0.48)",
        "phrase 22: no lease renews late (0.44)"
      ],
      "control": "the current headline (0.31)",
      "grade": "B",
      "chose": "ask-for-the-pick",
      "never": [
        "click-through as a reason"
      ]
    }
  },
  {
    "$id": "https://api.sb/leaseline.app/approvals/approval_52",
    "$type": "Approval",
    "verdict": "approved",
    "deadline": "2026-08-27T17:00:00Z",
    "draft": "40 phrases over 3 stances: per-owner reliability (14), time saved (13), fewer late renewals (13)…",
    "route": "start-a-round",
    "checks": [
      {
        "check": "evaluator",
        "status": "pass"
      },
      {
        "check": "fact-check",
        "status": "pass"
      },
      {
        "check": "rival-name",
        "status": "pass"
      }
    ]
  }
]

You answer with POST /{namespace}/tasks/{task}/claim (Take this one), then POST /{namespace}/decisions for the pick, or POST /{namespace}/approvals/{approval}/approve and POST /{namespace}/approvals/{approval}/refuse for a slate, each a one-call window. With no pick in 5 business days, the Story keeps its stance and the round’s read waits; nothing is picked for you on a Startup with a person.

On time: you, 1 of 1 slates approved by its deadline, 38 of 40 phrases kept, n 1; the pick of Oct 19 waits for you, ■□□□ Too early to tell beside Other Startups’ 86 % on time. Your queue: counted by kind: the slate in week 1, a rival-name approval if one comes up, and the pick card at the end of each round.

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
fact-check (Not live yet)a phrase stating a fact the claims source lacks, or a superlative or number no sourced fact holdsCut the claim, or name the record that backs it.POST /{namespace}/content/draft
price-split-by-buyer (gaps #8)a Variant that shows a different Price or Offer for the same thingAssign the Price test by market or by time.POST /{namespace}/experiments/{experiment}/start
evaluator (the record’s Evaluator gate)a phrase that fails the Evaluator or the claims checkFix the lines the Evaluator lists, then submit the phrase again.POST /{namespace}/content/draft
rival-name (the record’s Rival-name gate)a phrase naming a rival with no supporting fact and your approvalCite a dated fact for the rival, then approve the phrase.POST /{namespace}/approvals/{approval}/approve
coherence (the record’s Coherence gate)a Variant whose landing Page’s first screen does not deliver what the phrase promisesFix the Page’s first screen, or drop the phrase.PATCH /{namespace}/pages/{id}
status (the record’s Status gate)any Ad while the tournament Page hides whether the product is liveState the product’s status on the Page’s first screen.PATCH /{namespace}/pages/{id}
allocation-fence (the record’s Allocation fence)a shift that puts a phrase under a quarter or over 4 times its share, or a kill before day 14Shift spend inside the fence, and kill nothing before day 14.PATCH /{namespace}/ads/{id}
readability (the record’s Readability gate)a Story write from a Readout graded inadmissibleLet the finals mature; only an A or B read reaches the pick card.GET /{namespace}/readouts/{id}
pick (the record’s Pick gate)a Story change without your pickPick from the card; the Story changes on your Decision.POST /{namespace}/decisions
Stop (pauses the Play)Pauses whenThen
claimsa fact a live phrase relies on changes or fails its recheckevery Variant using it pauses within 1 h
signup-qualityexcluded signups over 30 % of a day’s, at 20 or morethat Venue pauses; a Task for you
surrogatethe early ranking and the matured ranking correlate under 0.3allocation returns to equal shares
signup-feedno signup recorded for 72 h while spend flowschanges freeze; at 7 days the Play pauses
commentcomments calling an Ad misleading over 5 % of 50 or morethat Ad pauses
drifta live Ad or setting differs from the approved stateit pauses or resets on the daily read
spend-limitspend reaches the round’s Limitevery Ad pauses until the next round opens
kill-switchyou press itevery Ad and the Site Flag pause through the proxy

The captured refusal, then the Stop that fired as a Campaign.paused reason (Not live yet, gaps #2); the rewrite that ran is phrase 9 in Example.

422RefusedPhrase 9 did not enter the slate: it states a superlative no sourced fact holds.
CheckFixThen
fact-check fail · Not live yet, gaps #1Cut the claim, or name the record that backs it.POST /{namespace}/content/draft
price-split-by-buyer pass—POST /{namespace}/experiments/{experiment}/start
JSON
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "Phrase 9 did not enter the slate: it states a superlative no sourced fact holds.",
  "checks": [
    {
      "check": "fact-check",
      "status": "fail",
      "doc": "/plays/positioning-tournament#check-fact-check",
      "fix": "Cut the claim, or name the record that backs it.",
      "then": "Content.draft"
    },
    {
      "check": "price-split-by-buyer",
      "status": "pass"
    }
  ]
}

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

The Stops that fired, as Campaign.paused reasons · sample
[
  {
    "reason": {
      "limit": "signup-quality",
      "observed": "34 % excluded (10 of 29 signups)",
      "bound": "30 %",
      "window": "1 day on Reddit, Sep 3"
    },
    "doc": "/plays/positioning-tournament#stop-signup-quality",
    "task": "task_402"
  }
]

Did it work

Impressions were assigned at random across the finalists and the control (rule o); the winning phrase is read against the control from that assignment, on LinkedIn and on the Site split. Each figure’s call is POST /{namespace}/metrics/query by node and by arm; the parent field and every node above the leaf are Not live yet (gaps #4).

+0.31 per 1,000 · ■■□□ Early sign · n 84,000 · path → arr

Chose
ask-for-the-pick
Why
the finals’ Readout is graded B: phrase 14 (“every owner’s renewal notice, 60 days out”) won on LinkedIn and on the Site split, 0.62 against the control’s 0.31 qualified signups per 1,000 impressions; phrases 3 and 22 trail it
From
start-a-round · probe-instead · shift-budget · rerun-the-finalists · ask-for-the-pick · reopen
Expects
your pick written to the Story within 1 business day
Reopens when
no Decision.created by worker_1 on experiment_11 within 5 business days: the Story keeps its stance
By
worker_6
JSON
{
  "$id": "decisions/decision_596",
  "$type": "Decision",
  "choice": "ask-for-the-pick",
  "reason": "the finals’ Readout is graded B: phrase 14 (“every owner’s renewal notice, 60 days out”) won on LinkedIn and on the Site split, 0.62 against the control’s 0.31 qualified signups per 1,000 impressions; phrases 3 and 22 trail it",
  "stakes": "high",
  "options": [
    "start-a-round",
    "probe-instead",
    "shift-budget",
    "rerun-the-finalists",
    "ask-for-the-pick",
    "reopen"
  ],
  "by": "worker_6",
  "on": "experiment_11",
  "expected": "your pick written to the Story within 1 business day",
  "reopensWhen": "no Decision.created by worker_1 on experiment_11 within 5 business days: the Story keeps its stance"
}
NodePhrase 14The control (your current headline)AddedConfidenceBasis
qualified-signup-rate0.62 per 1,000 impressions (26 of 42,000)0.31 (13 of 42,000)+0.31 [+0.07, +0.55]■■□□ Early signassigned at random · n 84,000 impressions · 0 still maturing · won again on the Site split · Other Startups +0.20, ■■■□ Likely, n 29 Startups
new-leads1.10 per 1,000 (46 of 42,000), any signup0.71 (30 of 42,000)+0.39 [−0.05, +0.83]■□□□ Too early to tellthe same assignment
new-arr ↑$694, 2 Customers · Booked: credited, not all caused by this Play$347, 1 Customer+$347 [−$500, +$1,200] · Not carrying up yet · Worth about +$1,300 a month■□□□ Too early to tell (2 vs 1 Customers)the same · 9 trials still deciding
net-new-arr · arr+$694+$347+$347, carried: it moves no other bridge term■□□□ Too early to tellcarried from new-arr

The leaf counts signups who match the ICP and set up a first lease within 14 days, per 1,000 impressions, for each phrase; the screen ran 40 phrases on LinkedIn for 28 days, and the finals 6 phrases and the control for 21 days on Facebook and the Site headline split.

How it moves it: the phrase changed and nothing else: same image, Offer, Price and Page body, so the gap is the words (qualified-signup-rate by phrase).

Guardrails: treated against the control: trial to first lease set up, 57 % against 43 %, and comments calling an Ad misleading, 0 of 61 against a Stop at 5 %.

The Campaign card · sample
{
  "$id": "https://api.sb/leaseline.app/campaigns/positioning-tournament",
  "$type": "Campaign",
  "play": "play:positioning-tournament@1",
  "path": [
    "qualified-signup-rate",
    "new-leads",
    "new-arr",
    "net-new-arr",
    "arr"
  ],
  "experiment": {
    "metric": "qualified-signup-rate",
    "unit": "per-1000",
    "heldBack": 42000,
    "decision": null
  },
  "readout": {
    "value": 0.31,
    "n": 84000,
    "maturing": 0,
    "interval": [
      0.07,
      0.55
    ],
    "readability": "B",
    "basis": "held-back",
    "pooled": false
  },
  "ledger": {
    "spend": 1500,
    "workerCost": 4,
    "hours": 4.5,
    "pipelineArr": 0,
    "wonArr": 694,
    "projectedArr": 1290,
    "paybackDays": 420
  }
}

Outside benchmark: the catalog holds none for this Play yet, so Other Startups’ results stand in: +0.20 qualified signups per 1,000 impressions over the control, ■■■□ Likely, n 29 Startups.

Before it · After it. Before it: signup-interviews and objection-watch supply the Customer words it mines. After it: your pick writes the Story, and paid-social, paid-search, organic-social and cold-email open lines carry the winning phrase; the old control keeps 10 % of the Site Flag for 30 days.

Example

Leaseline ran round 1 from 2026-08-24 to 2026-10-19 (56 days): 40 phrases in the screen, 6 finalists and the control in the finals, $1,500 spent, 214 signups, 71 of them qualified. The pick waits for Dana. Read every captured thread at ?threads=all; three follow the tables.

What happened, by optionDecisionsDoneWon (credited, not all caused by this Play)
Start a round140 phrases drafted, 38 approved, 2 refused—
Shift budget to the leaders · Rerun the finalists19 · 1inside the fence every day · 6 finalists and the control, 21 days3 on Pro from tournament signups (+$1,041 ARR)
Ask you to pick · Reopen the tournament · Run a stance probe instead1 · 0 · 0waiting on you · — · ——
At $1,500 a round (the Use it when volume)1 round in 7 weeksfinals assigned at randomOther Startups’ results: +0.20 per 1,000, ■■■□ Likely, n 29 Startups
DayEventWhat happenedYour time
0event_118 Campaign.createdthe three steps; the dry run fails use-when: 22 quotes from 8 members25 min
2event_140 Interview.created2 more ICP members interviewed; 31 quotes from 1050 min
3event_150 Approval.approvedyou approved 38 of 40 phrases; the screen started at equal budgets30 min
5event_190 Lead.createdthe first ICP-matched signup, on phrase 14—
10event_244 Campaign.pausedsignup-quality on Reddit: 34 % excluded; Reddit stayed off for the round10 min
14 · 49event_301 Change.merged · event_861 Readout.createdallocation waits for ICP match: replayed at a 0.68 correlation, read back at 0.715 min
28event_560 Experiment.startedthe finals: 6 phrases and the control on Facebook and the Site split—
56event_1418 Lead.qualifiedthe last finals signup matured; the pick card went to you—
1–56event_131 Approval.created, first of 2the slate and a rival-name check; weekly reviews, from the POST /{namespace}/tasks/next log2 h 40 min · total 4.5 h measured, 4 h asked

Phrase 14 on LinkedIn, Sep 30 · “Every owner’s renewal notice, 60 days out.”¹ The same image, Offer and Page as the other 39.

The signup, Oct 1, 10:20 · a 140-unit manager who fits the ICP; she set up her first lease on Oct 3 (qualified, day 2), replied to the welcome, and talked with Dana on Oct 9. Her words, “I just need every owner to get it on time”, joined the next phrase mine.²

Phrase 9, 2026-08-26 · fact-check refused, then the version that ran
- The fastest renewal software for landlords
+ Renewal notices sent 60 days out, for every owner

The round 1 slate, 2026-08-26 15:00 · 40 phrases, 2 refused by the Checks, and one naming a rival with a dated fact · the Approval went to Dana with a deadline of Aug 27 at 17:00 (approval_52).³ Dana approved 38 phrases on Aug 27 at 11:10, inside the deadline, and struck the rival phrase.

¹ Variants differ in this phrase alone. ² A Customer’s words become a phrase only when three or more people say them. ³ A slate and any rival’s name are yours to approve, on every rung.

Your settings

plays/positioning-tournament.mdx
---
limit: 1500       # dollars a round
venues: [linkedin, facebook]
---
Run a round when the Story has no stance backed by behavior, and $1,500 is funded.
Count a signup only if it fits our ICP and sets up a lease within 14 days.
Shift budget only on ICP-matched signups.
Rerun the best 6 against our current headline.
Text
$ npx startup-builder show plays/positioning-tournament
limit $1,500 a round · venues linkedin, facebook · a lease set up by day 14 · ICP-matched allocation · 6 finalists     your file  plays/positioning-tournament.mdx:2,3,5,6,7,8 (Ask 2026-08-24; line 7 by change_32, 2026-09-07)
family positioning · worker role:marketing-lead, owns never · chooses 6 (4 rule, 2 read) · sequence tournament-welcome · approval every-send · checks fact-check, price-split-by-buyer, evaluator, rival-name, coherence, status, allocation-fence, readability, pick · stops 8 · placements linkedin, facebook + the Site Flag    catalog  play:positioning-tournament@1 (Not live yet, gaps #5)

Every key from the catalog

Key Id What it is
Tactic rival-claim-scan Rival claim scan captures every rival’s homepage, pricing Page and live Ads each month, clusters their claims, and scores how crowded each cluster is. Starts at Sends nothing.
Tactic customer-phrase-mine Customer phrase mine extracts the words ICP members use about the Problem and keeps the ones three or more people say that no rival claims. Starts at Sends nothing.
Tactic phrase-slate Phrase slate drafts 40 Variants that differ in one phrase across three stances, runs the Evaluator and the coherence check, and starts the Experiment. Starts at You approve each one.
Tactic headline-screen Headline screen builds 40 headline-only Ads on one Venue, identical but for the phrase, and runs them at equal budgets for the first 4 days. Starts at You approve each one.
Tactic headline-allocation Headline allocation shifts the screen’s daily budget toward ICP-matched signups with logged propensities, and passes the top 6 cells and control to the finals. Starts at You approve each one.
Tactic qualified-signup-join Qualified signup join resolves each tournament signup’s ICP match at once and its committed action at day 14, turning provisional credit into the lagged reward. Starts at Sends nothing.
Tactic finalist-replication Finalist replication reruns 6 finalists and the control on a Site headline split and a second Venue for 21 days, marking which cells win again. Starts at You approve each one.
Tactic founder-pick Founder pick shows the founder the top three cells with their evidence, records the pick as a Decision, and writes the Story’s stance. Starts at You approve each one.
Tactic tournament-reopen Tournament reopen restarts the tournament with the incumbent as control when rivals copy its cell, its rate decays or the founding Hypothesis changes. Starts at Sends nothing.
Tactic 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 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 story-ungraded Story ungraded: state on Story.revised, window 30d.
Trigger tournament-funded Tournament funded: threshold on PlayMix.compiled, window 7d.
Trigger heatmap-due Heatmap due: at on Dashboard.scheduled, window 7d.
Trigger rival-claim-change Rival claim change: event on Monitor.checked, window 30d.
Trigger cell-filled Cell filled: threshold on Monitor.checked, window 14d.
Trigger customer-words-batch Customer words batch: threshold on Conversation.closed, window 30d.
Trigger signup-matured Tournament signup matured: event on Lead.qualified, window 14d.
Trigger stance-decay Stance decay: threshold on Metric.watched, window 30d.
Trigger sibling-cell-claim Sibling cell claim: event on Territory.requested, window 7d.
Sequence tournament-welcome Tournament welcome: 2 steps; it ends on A reply; A booked Interview; A Reserve deposit; An unsubscribe.

Compiled, not written: edit the Play record positioning-tournament 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.