Skip to content

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
Run oneMode try
You, to your agent

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"
  }
}
Response200application/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 yetPreview

On 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,867 hiring expansion ARR against $268 before · before-after, not proof · n 9 · path → arr

Try another: the CustomerChoiceReason
Oakmont Rentals: 4 invites in 9 days, 7 members on 3 paid seatsoffer-the-seatsfirst-party growth, $574 of ARR, under the $1,000 line
Birchway Properties: the directory added 9 members past the plan’s 10 seatsto-the-ae$1,077 of ARR is over the $1,000 line
Cedar Ridge Homes: the only admin’s seat ended Oct 6name-a-successorno second admin; renewal in 74 days
Harbor Row: 2 invites, at risk since Oct 2not-nowkeep-first: their CSM sees them first

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: 0 and reads before-after.

sample · api.sb/leaseline.app · 41 Customers at 2026-10-19 · on Tenant.invited · first note 4m 05s · 8 keys Not live yet

Start it this week

Terminal
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"}'
422Refusedsignal-expansion can’t start yet: 1 check fails and 2 warn.
CheckFixThen
use-when pass · Not live yet, gaps #8Nothing to fix: 20 or more Customers grow past their seats.POST /{namespace}/metrics/query
limit-on-offer fail · Not live yet, gaps #8Add 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 #8Nothing to fix: every seat charge reads from Stripe.POST /{namespace}/integrations/connect
baseline warn · Not live yet, gaps #8n 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 #8Staff 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.

CheckFix (one call)Your timeWait
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 Stripe1 minnone
limit-on-offer · fail: Team carries no seat LimitPOST /{namespace}/offers/{offer}/publish with 3 seats and $12 a seat-month10 minnone
baseline · warn: 3 seats added by hand in 90 daysPOST /{namespace}/metrics/query; it fills Before in Your number0 minnone
role-unstaffed · warn: 4 of your Customers have a person owner, PriyaPOST /{namespace}/roles/{role}/staff, or leave it: CSM routes come to you, and Priya’s 4 get drafts in her queue0 minnone

Your number · Before $268 · Now $1,867 hiring expansion ARR · Target $3,000 by Dec 31 · before-after, not proof · n 9 matured

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

WhenTrigger idStarts onWindowHow it gets in
Members invite 3 or more people in 14 days, past the paid seatsinvite-burstthreshold on Tenant.invited where invites >= 3 within 14d14d; one card per growth episodeyour app calls POST /{namespace}/events/capture with Tenant.invited
Directory sync adds members past the paid seat countdirectory-new-membersthreshold on MemberDirectory.provisioned where members > seatsPaid14dPOST /{namespace}/integrations/connect for your directory
The only champion has not signed in for 21 days while the team stays activechampion-quietstate on Metric.watched where champion.lastSignInAt < now - 21d and tenant.status = 'active'a state, read each day; 14dPOST /{namespace}/metrics/{metric}/watch on sign-ins
+ 7 more for openings, funding, two Limits, departures, landings, a former buyer’s move and renewals (?lanes=all)
One signed delivery · StartupBuilder-Signature
{
  "$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"
  }
}
Reproduce it in test
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:success-manager, worker_5) holds the Key Result. It hands seats at the $1,000 line to AE · Priya Shah (person) (role:sales-lead, 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.

OptionRung todayEvidence (passed of n)Moves up atDrops back whenYou can
Offer the seatsIt does it, you can stop it6 of 6met on day 14 (the first 5 notes)any note fails a Check, or the misquote Stop fires → You approve each onePOST /{namespace}/actions/{action}/cancel inside the hold
Name a successorYou approve each one2 of 2at 10 passed—POST /{namespace}/approvals/{approval}/approve · POST /{namespace}/approvals/{approval}/refuse
To the AE · Not nowIt does it1 of 1 · 2 of 2—a held Customer is asked anyway → You approve each onePOST /{namespace}/actions/{action}/redirect
Founder note at their new companyYou send it, from your own Mailbox1 drafted, 1 sent by younever—POST /{namespace}/approvals/{approval}/approve · POST /{namespace}/approvals/{approval}/refuse
Change the hand-setup lineYou approve each one0 mergednever—POST /{namespace}/changes/{change}/merge · POST /{namespace}/changes/{change}/revert
Never: a discount, a seat added without their press, where a champion wentTo a person, every time1 asked, 1 handed overnever—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.

The brief (folded): compiled Agent-register Markdown · play:signal-expansion@1 · each line names its record key
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 12

The 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.

Terminal
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

OptionPicked whenDecided byRunsHands toIf it’s just you
Offer the seats offer-the-seatsinvites or the directory pass the paid seats, under $1,000 of ARRYour rule (line 4)team-growth-note → on press change-plan → seat-change-confirmthe admin’s pressthe same
To the AE to-the-ae$1,000 of added ARR or more, or they ask for termsAgent’s call, with its reasonPOST /{namespace}/deals → POST /{namespace}/quotes/{quote}/send co-termed to renewalPOST /{namespace}/tasks/{task}/assign → AE · Priya Shah, due 60 min · Take this oneyou
Name a successor name-a-successorthe champion left, or is quiet 21 daysAgent’s call, with its reasonthe in-product successor prompt → successor-welcomeyou approve the named successor, within 5 business days · Take this oneyou
Founder note at their new company note-the-former-buyera former buyer’s public move to a company that fitsAgent’s call, with its reasonformer-buyer-warm, drafted into your send tray → on reply POST /{namespace}/leadsyou, from your own Mailbox · Take this oneyou
Not now not-nowanother Play holds them, or keep-first appliesYour rule (rule j)nothing; names the holdernobody; the holder keeps themthe same
When they write backFixture replyWhat happensWho answersBy whenCall
Interested“Add the 4 seats.”the note’s one-press link; nothing is charged until they pressthe 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 chargeCSM · your agent5 minPOST /{namespace}/conversations/{conversation}/answer
Not now“Let’s wait until January.”the ask closes, and customer-ask-cap frees the Customernobodyat onceGET /{namespace}/decisions
Wrong person“Seats are Lee’s call.”asks them to forward it; it never writes to an address from their textCSM · your agent1 business dayPOST /{namespace}/conversations/{conversation}/answer
Out of officean auto-reply until Oct 28the note waits for the return datenobodythe return datePOST /{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 callAE · Priya Shah, or you60 minPOST /{namespace}/conversations/{conversation}/escalate
Stop“Stop emailing us about seats.”notes stop; the in-product seat notice keeps runningnobodyat oncePOST /{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).

What lands in each queue · sample
[
  {
    "$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)RefusesFixThen
owned-by-a-person (rule j, gaps #8)a note to a Customer whose open Deal a person owns, or whose Conversation a person claimedLeave 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 pendingLet 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 Playusage-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 disagreeClear 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 showQuote the live seat Price, or get the discount approved.POST /{namespace}/approvals/{approval}/approve
Stop (pauses the Play)Pauses whenThen
misquotea Price, charge, seat count or renewal date differs from the live Price or the growth cardYou approve each one
leakan output names a watched fact, or tells an old company where its champion wentYou approve each one
recipienta seat note goes to a member without admin or billing rightsthe growth lane pauses until you clear the template
downgradeover 10 % of added seats are removed within 90 daysthe growth lane pauses; the seat map is flagged to you
successor-deadline3 departures in 30 days with no successor after 5 business daysthe growth lane pauses; POST /{namespace}/tasks/{task}/assign to you
holdout-integritya timed note reaches a scheduled-arm Customer; over 5 % voids the readoutPOST /{namespace}/tasks/{task}/assign
spend-limitmonth-to-date model spend reaches its Limitevery seat question and founder draft goes to a person; POST /{namespace}/limits/{limit}/raise
kill-switchyou press itevery note stops; the successor prompt keeps showing
+ 4 more for the announced and note lanes (?lanes=all)

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.

422RefusedThe note to Willow Street did not send: it offers $9 seats that the live Offer does not show.
CheckFixThen
price fail · Not live yet, gaps #1Quote 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.

The Stops that fired, as Campaign.paused reasons · sample
[
  {
    "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,867 hiring expansion ARR against $268 before · before-after, not proof · n 9 · path → arr

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"
}
NodeNow (56 days)Before (the 90 days, per 56)AddedConfidenceBasis
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 · arrcarried from expansion-arrcarried——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.

The Campaign card · sample
{
  "$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=all; three follow the tables.

What happened, by optionCardsSeats added or keptBooked (credited, not all caused by this Play)
Offer the seats618 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 now1 · 2 · 1 · 21 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 daysbefore-after, not proofOther Startups’ results: +$36 per card, ■■■□ Likely, n 1,380
DayEventWhat happenedYour time
0event_119 Campaign.createdthe three steps; dry run fails limit-on-offer10 min
0event_124 Offer.publishedTeam gains 3 seats and $12 a seat-month10 min
3event_188 Subscription.resizedthe first seats added: Juniper Homes, 2 seats, +$287 ARR—
14event_402 Role.updatedpromoted: seat notes send after a 2-minute hold—
22event_517 Campaign.pausedrecipient, the template fixed by you in 20 min20 min
43event_808 Action.refusedprice refused the Willow Street draft; rewritten once and sent5 min
1–56event_141 Approval.created, first of 7you approved 5 notes and 2 successors; weekly reviews, from the POST /{namespace}/tasks/next log20 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.

Willow Street, 2026-10-05 · the draft price refused, then the rewrite that sent
- 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

plays/signal-expansion.mdx
---
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.
Text
$ 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

Key Id What it is
Tactic team-growth-watch Team growth watch opens one growth card per paying Customer when its Tenant or announced openings show a team growing past its paid seats. Starts at Sends nothing.
Tactic seat-offer-note Seat offer note shows a growing team’s admins the seats it needs, at the live Price with today’s charge, in product and in one note. Starts at You approve each one.
Tactic seat-change Seat change adds seats or moves to the team Offer through Stripe, only on the Customer’s own press or signature, and welcomes each new member. Starts at You approve the first wave.
Tactic successor-handoff Successor handoff gets a successor named within 5 business days of a champion’s departure, moves their duties, and welcomes them before the renewal. Starts at You approve the first wave.
Tactic champion-landing-join Champion landing join welcomes a moved champion at an existing Customer through its admin’s invite, and opens a card when the new team needs seats. Starts at You approve each one.
Tactic former-buyer-note Former buyer note drafts one founder note about 30 days after a former buyer’s announced move, recalling only the real shared Deal. Starts at You approve each one.
Tactic team-growth-readout Team growth readout counts seats added per growth card against the scheduled arm, reads the save guardrail, and resets each window. Starts at Sends nothing.
Tactic churn-alert Churn alert: Turns a paying Customer’s visit to pricing, billing or cancel Pages into a Success Manager Task with a health brief, and sends no Message. Starts at Sends nothing.
Tactic send-gate-check Send gate check: Checks Suppression, a live reason, the Market, the basis stamp, the Person-month grant, the facts and the labels before any cold Message exists. Starts at Sends nothing.
Tactic reply-matrix Reply matrix: Labels every outreach reply with one of 11 labels, maps it to one of seven reply kinds, and runs that kind’s move by its deadline. Starts at You approve each one.
Trigger customer-hiring Customer hiring: event on Opening.published, window 30d.
Trigger customer-funded Customer funded: event on Company.funded, window 90d.
Trigger invite-burst Invite burst: threshold on Tenant.invited, window 14d.
Trigger directory-new-members Directory new members: threshold on MemberDirectory.provisioned, window 14d.
Trigger team-limit-handoff Team limit handoff: threshold on Entitlement.checked, window 30d.
Trigger champion-departure-handoff Champion departure handoff: event on Task.assigned, window 3d.
Trigger champion-landing-handoff Champion landing handoff: event on Task.assigned, window 7d.
Trigger approver-move-handoff Approver move handoff: event on Task.assigned, window 60d.
Trigger champion-quiet Champion quiet: state on Metric.watched, window 14d.
Trigger departed-champion-renewal Departed champion renewal: at on Renewal.opened, window 30d.
Sequence team-growth-note Team growth note: 4 steps; it ends on Seats added; A reply; A not-now; An opt-out; An at-risk hold; 14 days from the note.
Sequence seat-change-confirm Seat change confirmation: 2 steps; it ends on The summary sent; Seats removed; An unsubscribe.
Sequence successor-welcome Successor welcome: 3 steps; it ends on A call booked; A reply; An opt-out; 30 days from the welcome.
Sequence former-buyer-warm Former buyer warm note: 2 steps; it ends on A reply; A booked Meeting; An opt-out; 14 days from the follow-up.
Benchmark signal-expansion-month-one-seat-add Share of Subscriptions adding seats in their first month: 0.83 % (A subscription-analytics firm’s billing data, published in a pricing newsletter, 2026-09-30).
Benchmark signal-expansion-month-four-seat-add Share of Subscriptions adding seats in their fourth month: 0.19 % (A subscription-analytics firm’s billing data, published in a pricing newsletter, 2026-09-30).

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.