Signal expansion
Shows a growing Customer the seats they already use at the live Price, and names a successor within 5 business days when their champion leaves.
- Channel
- More seats and upgrades
- Kind
- Conversion
- Summary
- startupbuilder.dev
Be our CSM: when a Customer invites more people than they pay seats for, show their admin the seats at today’s charge. At $1,000 of added ARR or more, bring in our AE. When a champion leaves, find who owns the account now. Show me what’s missing; don’t send.
# Not live yet (gaps #2)
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":"signal-expansion"}'import { $ } from 'startup-builder'
return await $.Campaign.create({ play: 'signal-expansion' }, { mode: 'try' }) // dry run on the day-0 Startup, 2026-08-24 · Not live yet, gaps #2
npx startup-builder do Campaign.create --json '{"play":"signal-expansion"}' --try # Not live yet (gaps #2){
"name": "do",
"arguments": {
"code": "return await $.Campaign.create({ play: 'signal-expansion' }, { mode: 'try' }) // dry run on the day-0 Startup, 2026-08-24 · Not live yet, gaps #2",
"mode": "try"
}
}application/json{
"type": "https: //startupbuilder.dev/ errors/ refused",
"title": "Refused",
"status": 422,
"detail": "signal-expansion can’t start yet: 1 check fails and 2 warn.",
"checks": [
{
"check": "use-when",
"status": "pass",
"observed": "38 paying Customers, 3 a month inviting past their seats",
"doc": "/plays/ signal-expansion#check-use-when",
"fix": "Nothing to fix: 20 or more Customers grow past their seats.",
"then": "Metric.query"
},
…
Hand our latest growing Customer to the CSM agent and show me what it chose and why. Don’t send.
curl "https://api.sb/leaseline.app/events?where=name%20in%20(%22Opening.published%22%2C%20%22Company.funded%22%2C%20%22Tenant.invited%22%2C%20%22MemberDirectory.provisioned%22%2C%20%22Entitlement.checked%22%2C%20%22Task.assigned%22%2C%20%22Metric.watched%22%2C%20%22Renewal.opened%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_1311"}'
# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/decisions?where=on%20%3D%20%22customer_21%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%22customer_21%22" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"import { $ } from 'startup-builder'
const { events: [last] } = await $.Event.list({ where: 'name in ("Opening.published", "Company.funded", "Tenant.invited", "MemberDirectory.provisioned", "Entitlement.checked", "Task.assigned", "Metric.watched", "Renewal.opened")', 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 (\"Opening.published\", \"Company.funded\", \"Tenant.invited\", \"MemberDirectory.provisioned\", \"Entitlement.checked\", \"Task.assigned\", \"Metric.watched\", \"Renewal.opened\")","limit":1}'
npx startup-builder do Trigger.fire --json '{"like":"event_1311"}' --try # Not live yet (gaps #7)
npx startup-builder do Decision.list --json '{"where":"on = \"customer_21\""}' # Not live yet (gaps #7)
npx startup-builder do Action.list --json '{"where":"on = \"customer_21\""}' # Not live yet (gaps #7){
"name": "do",
"arguments": {
"code": "const { events: [last] } = await $.Event.list({ where: 'name in (\"Opening.published\", \"Company.funded\", \"Tenant.invited\", \"MemberDirectory.provisioned\", \"Entitlement.checked\", \"Task.assigned\", \"Metric.watched\", \"Renewal.opened\")', 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"
}
}application/json{
"decision": {
"$id": "https: //api.sb/ leaseline.app/ decisions/ decision_352",
"$type": "Decision",
"choice": "offer-the-seats",
"reason": "4 invites in 9 days put Oakmont Rentals at 7 members on 3 paid seats: $574 of added ARR, under the $1, 000 line in your file",
"stakes": "low",
"options": [
"offer-the-seats",
"to-the-ae",
"name-a-successor",
"note-the-former-buyer",
"not-now"
],
…
sample · 4 Not live yetPreviewOn PATCH /{namespace}/campaigns/{id}Off PATCH /{namespace}/campaigns/{id}Take this one POST /{namespace}/tasks/{task}/claim
action_2 is the note to Oakmont’s admin, held 2 minutes; the in-product seat notice shows at their next sign-in.
$1,
| Try another: the Customer | Choice | Reason |
|---|---|---|
| Oakmont Rentals: 4 invites in 9 days, 7 members on 3 paid seats | offer-the-seats | first-party growth, $574 of ARR, under the $1,000 line |
| Birchway Properties: the directory added 9 members past the plan’s 10 seats | to-the-ae | $1,077 of ARR is over the $1,000 line |
| Cedar Ridge Homes: the only admin’s seat ended Oct 6 | name-a-successor | no second admin; renewal in 74 days |
| Harbor Row: 2 invites, at risk since Oct 2 | not-now | keep-first: their CSM sees them first |
- 25 min week 1, then 5 a week
- Your time
- Day 3: first seats added
- First signal
Where it sits: Stranger → Lead → Trialing → Activated → At risk → Churned → Won back · Hiring expansion ARR → Upgrades ↑ → ARR added each week → ARR
Worth’s formula: about +$36 ARR per growth card (Other Startups’ results; no lift of your own yet) × 6 cards a month ≈ +$216. Under First signal: At 6 cards a month, 90 days can tell only +$120 per card or more, so your file starts at holdout: and reads before-after.
sample · api.sb/
Start it this week
npx startup-builder add play signal-expansion # 1 · writes plays/signal-expansion.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":"signal-expansion"}'
# 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":"signal-expansion"}'
# on
curl -X PATCH https://api.sb/leaseline.app/campaigns/signal-expansion \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"endsAt":null}'
# off: no seat note sends; the successor prompt keeps showing
curl -X PATCH https://api.sb/leaseline.app/campaigns/signal-expansion \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"endsAt":"now"}' | Check | Fix | Then |
|---|---|---|
use-when pass · Not live yet, gaps #8 | Nothing to fix: 20 or more Customers grow past their seats. | POST /{namespace}/metrics/query |
limit-on-offer fail · Not live yet, gaps #8 | Add a 3-seat Limit to Team at $12 a seat-month, then publish it. | POST /{namespace}/offers/{offer}/publish |
billing-connected pass · Not live yet, gaps #8 | Nothing to fix: every seat charge reads from Stripe. | POST /{namespace}/integrations/connect |
baseline warn · Not live yet, gaps #8 | n 3: it reads before-after, not proof, until volume allows a held-back half. | POST /{namespace}/metrics/query |
role-unstaffed warn · Not live yet, gaps #8 | Staff the CSM, or leave it: 4 Customers Priya owns get drafts in her queue. | POST /{namespace}/roles/{role}/staff |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "signal-expansion can’t start yet: 1 check fails and 2 warn.",
"checks": [
{
"check": "use-when",
"status": "pass",
"observed": "38 paying Customers, 3 a month inviting past their seats",
"doc": "/plays/signal-expansion#check-use-when",
"fix": "Nothing to fix: 20 or more Customers grow past their seats.",
"then": "Metric.query"
},
{
"check": "limit-on-offer",
"status": "fail",
"observed": "Team carries no seat Limit",
"doc": "/plays/signal-expansion#check-limit-on-offer",
"fix": "Add a 3-seat Limit to Team at $12 a seat-month, then publish it.",
"then": "Offer.publish"
},
{
"check": "billing-connected",
"status": "pass",
"observed": "Stripe, connected 2026-08-24",
"doc": "/plays/signal-expansion#check-billing-connected",
"fix": "Nothing to fix: every seat charge reads from Stripe.",
"then": "Integration.connect"
},
{
"check": "baseline",
"status": "warn",
"observed": "3 seats added by hand in 90 days",
"doc": "/plays/signal-expansion#check-baseline",
"fix": "n 3: it reads before-after, not proof, until volume allows a held-back half.",
"then": "Metric.query"
},
{
"check": "role-unstaffed",
"status": "warn",
"roles": [
"success-manager",
"customer-lead"
],
"owned": 4,
"doc": "/plays/signal-expansion#check-role-unstaffed",
"fix": "Staff the CSM, or leave it: 4 Customers Priya owns get drafts in her queue.",
"then": "Role.staff"
}
]
}It says no, says why, and names the fix.
| Check | Fix (one call) | Your time | Wait |
|---|---|---|---|
use-when · billing-connected · pass: 38 paying Customers, 3 a month past their seats; Stripe connected (gaps #8) | POST /{namespace}/metrics/query on hiring-expansion-arr; POST /{namespace}/integrations/connect for Stripe | 1 min | none |
limit-on-offer · fail: Team carries no seat Limit | POST /{namespace}/offers/{offer}/publish with 3 seats and $12 a seat-month | 10 min | none |
baseline · warn: 3 seats added by hand in 90 days | POST /{namespace}/metrics/query; it fills Before in Your number | 0 min | none |
role-unstaffed · warn: 4 of your Customers have a person owner, Priya | POST /{namespace}/roles/{role}/staff, or leave it: CSM routes come to you, and Priya’s 4 get drafts in her queue | 0 min | none |
Your number · Before $268 · Now $1,
Each note costs $0.04 from your Domain (Worker cost); reading invites, the directory and POST /{namespace}/entitlements/check costs nothing. That comes to about $0.25 a month at 6 growth cards and $0.10 at 2.
When it starts
| When | Trigger id | Starts on | Window | How it gets in |
|---|---|---|---|---|
| Members invite 3 or more people in 14 days, past the paid seats | invite-burst | threshold on Tenant.invited where invites >= 3 within 14d | 14d; one card per growth episode | your app calls POST /{namespace}/events/capture with Tenant.invited |
| Directory sync adds members past the paid seat count | directory-new-members | threshold on MemberDirectory.provisioned where members > seatsPaid | 14d | POST /{namespace}/integrations/connect for your directory |
| The only champion has not signed in for 21 days while the team stays active | champion-quiet | state on Metric.watched where champion.lastSignInAt < now - 21d and tenant.status = 'active' | a state, read each day; 14d | POST /{namespace}/metrics/{metric}/watch on sign-ins |
+ 7 more for openings, funding, two Limits, departures, landings, a former buyer’s move and renewals (?lanes=) |
{
"$id": "https://api.sb/leaseline.app/events/event_1311" ,
"$type": "Event",
"name": "Tenant.invited",
"at": "2026-10-19T13:12:44Z" ,
"seq": 1311,
"data": {
"customer": "customer_21",
"invited": 4,
"within": "9d",
"members": 7,
"seatsPaid": 3,
"offer": "team"
}
}npx startup-builder trigger Tenant.invited --like event_1311 # replays the delivery
npx startup-builder clock advance --by 5d # then runs the note’s growth check
npx startup-builder listen Tenant.invited --forward-to http://localhost:3000/hook Who runs it
CSM · your agent (role:, worker_5) holds the Key Result. It hands seats at the $1,000 line to AE · Priya Shah (person) (role:, worker_3), and the founder note and successor checks to you (Dana Okafor, person, worker_1), because the CSM and Customer Lead Roles have no person. Your number links to Did it work.
| Option | Rung today | Evidence (passed of n) | Moves up at | Drops back when | You can |
|---|---|---|---|---|---|
| Offer the seats | It does it, you can stop it | 6 of 6 | met on day 14 (the first 5 notes) | any note fails a Check, or the misquote Stop fires → You approve each one | POST /{namespace}/actions/{action}/cancel inside the hold |
| Name a successor | You approve each one | 2 of 2 | at 10 passed | — | POST /{namespace}/approvals/{approval}/approve · POST /{namespace}/approvals/{approval}/refuse |
| To the AE · Not now | It does it | 1 of 1 · 2 of 2 | — | a held Customer is asked anyway → You approve each one | POST /{namespace}/actions/{action}/redirect |
| Founder note at their new company | You send it, from your own Mailbox | 1 drafted, 1 sent by you | never | — | POST /{namespace}/approvals/{approval}/approve · POST /{namespace}/approvals/{approval}/refuse |
| Change the hand-setup line | You approve each one | 0 merged | never | — | POST /{namespace}/changes/{change}/merge · POST /{namespace}/changes/{change}/revert |
| Never: a discount, a seat added without their press, where a champion went | To a person, every time | 1 asked, 1 handed over | never | — | POST /{namespace}/approvals/{approval}/approve on the AE’s Quote |
What it reads · What it may call. It reads invites, the directory, the Entitlement and the live Price, and never a sign-in gap, a funding estimate or where a champion went in what it writes. POST /{namespace}/subscriptions/{subscription}/resize is the Customer’s own press, never the agent’s call. Its tools are POST /{namespace}/entitlements/check, POST /{namespace}/deals, POST /{namespace}/quotes/{quote}/send, POST /{namespace}/tasks/{task}/assign, POST /{namespace}/leads, and any other call becomes an Approval under the Role’s Mandate.
You hold the CSM Key Result: $3,000 hiring expansion ARR by 2026-12-31. ← worker, metric
On each growth or departure Trigger, choose one option and say why; a held Customer gets Not now. ← triggers, chooses, keep-first
Every count comes from the growth card, with its as-of time. ← copyRules 1
At $1,000 of added ARR or more, choose To the AE. ← your file, line 4
Never tell an old company where its champion went, or that a move happened. ← copyRules 4
Removing a seat sits on the same billing Page as adding one. ← copyRules 12The slow loop. The weekly Decisions kept $1,000 as the hand-setup line, and no Change was merged in the window, so there is no replay or read-back to print.
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; POST /{namespace}/roles/{role}/staff seats it.
npx startup-builder eval --scenario signal-expansion-12 --agent "claude -p" --seat success-manager # seated: 4 of 5 added seats, 0 Checks failed sent, $0.04 a card · yours: 3 of 5, 1 failed, $0.06 → You approve each one What it can do next
| Option | Picked when | Decided by | Runs | Hands to | If it’s just you |
|---|---|---|---|---|---|
Offer the seats offer-the-seats | invites or the directory pass the paid seats, under $1,000 of ARR | Your rule (line 4) | team-growth-note → on press change-plan → seat-change-confirm | the admin’s press | the same |
To the AE to-the-ae | $1,000 of added ARR or more, or they ask for terms | Agent’s call, with its reason | POST /{namespace}/deals → POST /{namespace}/quotes/{quote}/send co-termed to renewal | POST /{namespace}/tasks/{task}/assign → AE · Priya Shah, due 60 min · Take this one | you |
Name a successor name-a-successor | the champion left, or is quiet 21 days | Agent’s call, with its reason | the in-product successor prompt → successor-welcome | you approve the named successor, within 5 business days · Take this one | you |
Founder note at their new company note-the-former-buyer | a former buyer’s public move to a company that fits | Agent’s call, with its reason | former-buyer-warm, drafted into your send tray → on reply POST /{namespace}/leads | you, from your own Mailbox · Take this one | you |
Not now not-now | another Play holds them, or keep-first applies | Your rule (rule j) | nothing; names the holder | nobody; the holder keeps them | the same |
| When they write back | Fixture reply | What happens | Who answers | By when | Call |
|---|---|---|---|---|---|
| Interested | “Add the 4 seats.” | the note’s one-press link; nothing is charged until they press | the Customer | — | POST /{namespace}/subscriptions/{subscription}/resize (theirs) |
| Send me pricing | “What would 10 seats cost us?” | the growth card again, with the live seat Price and today’s charge | CSM · your agent | 5 min | POST /{namespace}/conversations/{conversation}/answer |
| Not now | “Let’s wait until January.” | the ask closes, and customer-ask-cap frees the Customer | nobody | at once | GET /{namespace}/decisions |
| Wrong person | “Seats are Lee’s call.” | asks them to forward it; it never writes to an address from their text | CSM · your agent | 1 business day | POST /{namespace}/conversations/{conversation}/answer |
| Out of office | an auto-reply until Oct 28 | the note waits for the return date | nobody | the return date | POST /{namespace}/messages/{message}/schedule |
| Question it can’t answer | “Can new seats be view-only at a lower price?” | To a person: a price is never the agent’s call | AE · Priya Shah, or you | 60 min | POST /{namespace}/conversations/{conversation}/escalate |
| Stop | “Stop emailing us about seats.” | notes stop; the in-product seat notice keeps running | nobody | at once | POST /{namespace}/contacts/{contact}/suppress |
When it hands to you
Seats at the $1,000 line go to AE · Priya Shah (person); successors and founder notes come to you. One Brief per Role, as captured (brief is Not live yet, gaps #7).
[
{
"$id": "https://api.sb/leaseline.app/tasks/task_512" ,
"$type": "Task",
"assignee": "worker_3",
"due": "2026-10-14T15:20:00Z" ,
"on": "customer_7",
"brief": {
"who": "Birchway Properties, on the $2,400 plan" ,
"said": "The directory added 9 members past the plan’s 10 seats.",
"chose": "to-the-ae",
"why": "$1,077 of added ARR is over the $1,000 line" ,
"never": [
"a discount",
"a deadline that isn’t real"
]
}
},
{
"$id": "https://api.sb/leaseline.app/tasks/task_498" ,
"$type": "Task",
"assignee": "worker_1",
"due": "2026-10-13T17:00:00Z" ,
"on": "customer_26",
"brief": {
"who": "Cedar Ridge Homes",
"chose": "name-a-successor",
"why": "the only admin’s seat ended Oct 6; renewal in 74 days" ,
"candidate": "Mara Lind, billing owner" ,
"never": [
"where the old admin went"
]
}
},
{
"$id": "https://api.sb/leaseline.app/tasks/task_544" ,
"$type": "Task",
"assignee": "worker_1",
"due": "2026-10-17T17:00:00Z" ,
"on": "contact_52",
"brief": {
"who": "Theo Grant, now operations lead at Alder Park Rentals" ,
"chose": "note-the-former-buyer",
"why": "he signed Juniper Homes’ plan in March; his move is public since Sep 15" ,
"draft": "in your send tray",
"never": [
"Juniper Homes’ data or results"
]
}
}
]The owner answers with 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. Once a person claims, the agent’s pending asks to that Customer cancel with claimedByPerson (Not live yet, gaps #7), and every reply goes to the owner. A Task nobody claims by its due time goes up a Role through POST /{namespace}/tasks/{task}/escalate, and person-promise fires.
On time: 4 of 4 claimed within their due time, median 35 min, n 4, ■□□□ Too early to tell beside Other Startups’ 93 % on time. Your queue: in week 1, 5 approvals and 1 hand-off; after the promotion, about 1 hand-off and 1 successor check 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) | Refuses | Fix | Then |
|---|---|---|---|
owned-by-a-person (rule j, gaps #8) | a note to a Customer whose open Deal a person owns, or whose Conversation a person claimed | Leave it to the person who owns it: the draft waits in their queue. | POST /{namespace}/tasks/{task}/assign |
keep-first (rule j, gaps #8; the record’s At-risk hold) | a seat ask to a Customer who is at risk, past due or has an unsubscribe pending | Let their CSM reach them first, then ask again once they are healthy. | POST /{namespace}/tasks/next |
customer-ask-cap (gaps #3) | a second open ask to one Customer in a window, from any Play | usage-upsell holds them until its note closes. | GET /{namespace}/decisions |
reconcile (the record’s Reconcile stop; Not live yet, gaps #8) | a Customer whose Subscription and Stripe disagree | Clear the Stripe mismatch first. | POST /{namespace}/tasks/next |
price (hub guard on POST /{namespace}/quotes/{quote}/send, gaps #1) | a seat Price or discount the live Offer does not show | Quote the live seat Price, or get the discount approved. | POST /{namespace}/approvals/{approval}/approve |
| Stop (pauses the Play) | Pauses when | Then |
|---|---|---|
misquote | a Price, charge, seat count or renewal date differs from the live Price or the growth card | You approve each one |
leak | an output names a watched fact, or tells an old company where its champion went | You approve each one |
recipient | a seat note goes to a member without admin or billing rights | the growth lane pauses until you clear the template |
downgrade | over 10 % of added seats are removed within 90 days | the growth lane pauses; the seat map is flagged to you |
successor-deadline | 3 departures in 30 days with no successor after 5 business days | the growth lane pauses; POST /{namespace}/tasks/{task}/assign to you |
holdout-integrity | a timed note reaches a scheduled-arm Customer; over 5 % voids the readout | POST /{namespace}/tasks/{task}/assign |
spend-limit | month-to-date model spend reaches its Limit | every seat question and founder draft goes to a person; POST /{namespace}/limits/{limit}/raise |
kill-switch | you press it | every note stops; the successor prompt keeps showing |
+ 4 more for the announced and note lanes (?lanes=) |
The captured refusal, then the Stop that fired as a Campaign.paused reason (Not live yet, gaps #2); the rewrite that sent is the Willow Street thread in Example.
| Check | Fix | Then |
|---|---|---|
price fail · Not live yet, gaps #1 | Quote the live seat Price, or get the discount approved. | POST /{namespace}/approvals/{approval}/approve |
keep-first pass · Not live yet, gaps #8 | — | POST /{namespace}/tasks/next |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "The note to Willow Street did not send: it offers $9 seats that the live Offer does not show.",
"checks": [
{
"check": "price",
"status": "fail",
"doc": "/plays/signal-expansion#check-price",
"fix": "Quote the live seat Price, or get the discount approved.",
"then": "Approval.approve"
},
{
"check": "keep-first",
"status": "pass"
}
]
}It says no, says why, and names the fix.
[
{
"reason": {
"limit": "recipient",
"observed": "note addressed to a member with no billing rights",
"bound": "0 notes",
"window": "1 note"
},
"doc": "/plays/signal-expansion#stop-recipient" ,
"task": "task_471"
}
]Did it work
At 6 growth cards a month, no held-back half reads within 90 days, so the basis is before-after (rule o): the columns read Now and Before, and nothing prints as Added. Each figure’s call is POST /{namespace}/metrics/query by node, and every node above the leaf is Not live yet (gaps #4).
$1,
- Chose
- offer-the-seats
- Why
- 4 invites in 9 days put Oakmont Rentals at 7 members on 3 paid seats: $574 of added ARR, under the $1,000 line in your file
- From
- offer-the-seats · to-the-ae · name-a-successor · note-the-former-buyer · not-now
- Expects
- Subscription.resized for customer_21 within 30d
- Reopens when
- no Subscription.resized for customer_21 within 30d
- By
- worker_5
JSON
{
"$id": "decisions/decision_352",
"$type": "Decision",
"choice": "offer-the-seats",
"reason": "4 invites in 9 days put Oakmont Rentals at 7 members on 3 paid seats: $574 of added ARR, under the $1,000 line in your file",
"stakes": "low",
"options": [
"offer-the-seats",
"to-the-ae",
"name-a-successor",
"note-the-former-buyer",
"not-now"
],
"by": "worker_5",
"on": "customer_21",
"expected": "Subscription.resized for customer_21 within 30d",
"reopensWhen": "no Subscription.resized for customer_21 within 30d"
}| Node | Now (56 days) | Before (the 90 days, per 56) | Added | Confidence | Basis |
|---|---|---|---|---|---|
hiring-expansion-arr | $1,867: 4 of 5 matured seat cards added 13 seats, net of 1 removed · 3 still maturing · Booked $2,585: credited, not all caused by this Play | $268: 3 seats added by hand | — · Worth about +$220 a month | — | before-after, not proof · Other Startups’ results +$36 per card, ■■■□ Likely, n 1,380 |
expansion-arr ↑ | $4,259 | $1,464 | — | — | before-after; includes usage-upsell’s $2,392, which this Play never asked for |
net-new-arr · arr | carried from expansion-arr | carried | — | — | carried |
Before against after reads one window against the 90 days before it, scaled to 56 days; it is not proof, and Other Startups’ results print beside it. A card counts once its 30 days have passed, net of seats removed within 90 days.
How it moves it: each note shows the members invited, the seats paid, the live $12 seat Price and today’s prorated charge ($20.13 for Oakmont), the week the team grows.
Guardrails: before against after: logo-churn, 0 of 4 Customers that added seats gone against a trailing 2.1 % a month, and contraction-arr, $144 from 1 seat removed against $0 before; both departed-champion accounts kept their plans, 1 renewal still deciding.
{
"$id": "https://api.sb/leaseline.app/campaigns/signal-expansion" ,
"$type": "Campaign",
"play": "play:signal-expansion@1" ,
"path": [
"hiring-expansion-arr",
"expansion-arr",
"net-new-arr",
"arr"
],
"experiment": {
"metric": "hiring-expansion-arr",
"unit": "usd",
"heldBack": 0,
"decision": null
},
"readout": {
"value": null,
"n": 9,
"maturing": 3,
"interval": null,
"readability": "inadmissible",
"basis": "before-after",
"pooled": true
},
"ledger": {
"spend": 1,
"workerCost": 1,
"hours": 1.1,
"pipelineArr": 1077,
"wonArr": 2585,
"projectedArr": 216,
"paybackDays": 1
}
}Outside benchmark: 4.1 % of seat-priced Subscriptions add seats in their first month and 2.6 % in their fourth (a subscription-analytics firm’s billing data, 2026-09-30).
Before it · After it. Before it: usage-upsell hands it a Customer that hit two Limits in one period, and champion-follow hands it departures and landings. After it: sso-rung hears a team that reaches 20 active members, and annual-prepay hears a Customer steady on its new seats for 3 periods; keep-first holds both while a Customer is at risk.
Example
Leaseline ran it from 2026-08-24 to 2026-10-19 (56 days): 12 growth and departure cards on 11 Customers, 9 of them matured past their 30-day window. Read every captured thread at ?threads=; three follow the tables.
| What happened, by option | Cards | Seats added or kept | Booked (credited, not all caused by this Play) |
|---|---|---|---|
| Offer the seats | 6 | 18 seats on 5 Customers (Oakmont’s 4 still maturing), 1 removed, 1 card with none | $2,585 |
| To the AE · Name a successor · Founder note · Not now | 1 · 2 · 1 · 2 | 1 Quote open · 2 successors named, both plans kept · 1 reply, 1 Lead · 0 | $1,077 in an open Quote |
| At 2 a month (the Use it when volume) | about 4 in 56 days | before-after, not proof | Other Startups’ results: +$36 per card, ■■■□ Likely, n 1,380 |
| Day | Event | What happened | Your time |
|---|---|---|---|
| 0 | event_119 Campaign.created | the three steps; dry run fails limit-on-offer | 10 min |
| 0 | event_124 Offer.published | Team gains 3 seats and $12 a seat-month | 10 min |
| 3 | event_188 Subscription.resized | the first seats added: Juniper Homes, 2 seats, +$287 ARR | — |
| 14 | event_402 Role.updated | promoted: seat notes send after a 2-minute hold | — |
| 22 | event_517 Campaign.paused | recipient, the template fixed by you in 20 min | 20 min |
| 43 | event_808 Action.refused | price refused the Willow Street draft; rewritten once and sent | 5 min |
| 1–56 | event_141 Approval.created, first of 7 | you approved 5 notes and 2 successors; weekly reviews, from the POST /{namespace}/tasks/next log | 20 min · total 1.1 h measured, 1.2 h asked |
Leaseline → Oakmont’s admin, Oct 19, 13:17 · Your team invited 4 people in the last 9 days, so you have 7 members on 3 paid seats.¹ Each seat past 3 is $12 a month: $20.13 today, then $127 a month in all.² Add them with one press; removing a seat sits on the same page.³ *Signed with the Role’s disclosure line.*⁴
Oakmont, Oct 19, 13:40 · pressed Add 4 seats.
Subscription.resized, +$574 ARR.
- Add your 3 new people this week and we’ll lock seats at $9 a month.
+ Your 3 new people need 3 more seats at $12 a month: $31.35 today, then $115 a month in all. Birchway Properties, 2026-10-14 14:20 · the directory added 9 members past the plan’s 10 seats. · Leaseline, 14:21 · *Thanks. Priya, your account lead, replies by 15:20 with a Quote co-termed to your renewal.*⁵ The Brief went to Priya (
task_512); she sent the Quote at 15:02.
¹ Every count comes from the growth card, as of 13:12. ² The live seat Price and today’s prorated charge. ³ Removing a seat, one press away. ⁴ The fixed signature line, compiled from the Role’s disclosure Setting. ⁵ The routed line, compiled from the Role’s Settings.
Your settings
---
holdout: 0 # 6 cards a month is too few to hold any back; it reads before-after
---
Offer seats when invites or the directory pass the paid seats. At $1,000 of added ARR or more, bring in the AE.
When a champion leaves, name a successor within 5 business days. $ npx startup-builder show plays/signal-expansion
holdout 0 · seat line $1,000 · successor 5 business days your file plays/signal-expansion.mdx:2,4,5 (Ask 2026-08-24)
family expansion · worker role:success-manager, owns never · chooses 5 (2 rule, 3 read) · sequences team-growth-note, seat-change-confirm, successor-welcome, former-buyer-warm · checks owned-by-a-person, keep-first, customer-ask-cap, reconcile, price · stops 12 · placements gmail, outlook, postal-mail + in-product notice catalog play:signal-expansion@1 (Not live yet, gaps #5) Every key from the catalog
Compiled, not written: edit the Play record signal-expansion 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.