URL: /plays/signal-expansion · Id: `play:signal-expansion`

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

Shows a growing [Customer](https://startupbuilder.dev/business/nouns/customer) the seats they already use at the live [Price](https://startupbuilder.dev/business/nouns/price), and names a successor within 5 business days when their champion leaves.

```text title="Set it up · Ask"
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.
```

```text title="Run one · Ask"
Hand our latest growing Customer to the CSM agent and show me what it chose and why. Don’t send.
```

```ts title="Run one · SDK"
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 }
```

```json title="Run one · Response · sample"
{
  "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"
    ],
    "by": "worker_5",
    "on": "customer_21",
    "expected": "Subscription.resized for customer_21 within 30d",
    "reopensWhen": "no Subscription.resized for customer_21 within 30d"
  },
  "actions": [
    {
      "$id": "https://api.sb/leaseline.app/actions/action_1",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_5",
      "verb": "Entitlement.check",
      "checks": []
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_2",
      "$type": "Action",
      "status": "pending",
      "reason": "awaitingTime",
      "mode": "try",
      "by": "worker_5",
      "verb": "Message.schedule",
      "checks": [
        {
          "check": "owned-by-a-person",
          "status": "pass"
        },
        {
          "check": "keep-first",
          "status": "pass"
        },
        {
          "check": "customer-ask-cap",
          "status": "pass"
        },
        {
          "check": "reconcile",
          "status": "pass"
        }
      ]
    }
  ]
}
```

`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 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 |

| Worth | Your time | Spend | First signal | Use it when | Instead |
| --- | --- | --- | --- | --- | --- |
| ≈ +$220 ARR a month at 6 | 25 min week 1, then 5 a week | $0.04 per note, ~$0.25 a month at 6 | Day 3: first seats added | 20+ paying Customers on seats, 2+ a month growing past them | Under 20 Customers: `usage-upsell` · no Offer prices seats: add one (<Op verb="Offer.publish" />) |

**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
```bash
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"}'
```


```json title="The dry run’s 422 · 2026-08-24 · sample"
{
  "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"
    }
  ]
}
```

| Check | Fix (one call) | Your time | Wait |
| --- | --- | --- | --- |
| <a id="check-use-when"></a>`use-when` · <a id="check-billing-connected"></a>`billing-connected` · pass: 38 paying Customers, 3 a month past their seats; Stripe connected (gaps #8) | <Op verb="Metric.query" /> on `hiring-expansion-arr`; <Op verb="Integration.connect" /> for Stripe | 1 min | none |
| <a id="check-limit-on-offer"></a>`limit-on-offer` · fail: Team carries no seat Limit | <Op verb="Offer.publish" /> with 3 seats and $12 a seat-month | 10 min | none |
| <a id="check-baseline"></a>`baseline` · warn: 3 seats added by hand in 90 days | <Op verb="Metric.query" />; it fills Before in Your number | 0 min | none |
| <a id="check-role-unstaffed"></a>`role-unstaffed` · warn: 4 of your Customers have a person owner, Priya | <Op verb="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,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 <Op verb="Entitlement.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 <Op verb="Event.capture" /> with `Tenant.invited` |
| Directory sync adds members past the paid seat count | `directory-new-members` | `threshold` on `MemberDirectory.provisioned` where members > seatsPaid | 14d | <Op verb="Integration.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 | <Op verb="Metric.watch" /> on sign-ins |
| + 7 more for openings, funding, two Limits, departures, landings, a former buyer’s move and renewals (`?lanes=all`) | | | | |

```json title="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"
  }
}
```

```bash title="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.

| 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 | <Op verb="Action.cancel" /> inside the hold |
| Name a successor | You approve each one | 2 of 2 | at 10 passed | — | <Op verb="Approval.approve" /> · <Op verb="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 | <Op verb="Action.redirect" /> |
| Founder note at their new company | You send it, from your own Mailbox | 1 drafted, 1 sent by you | never | — | <Op verb="Approval.approve" /> · <Op verb="Approval.refuse" /> |
| Change the hand-setup line | You approve each one | 0 merged | never | — | <Op verb="Change.merge" /> · <Op verb="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 | — | <Op verb="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. <Op verb="Subscription.resize" /> is the Customer’s own press, never the agent’s call. Its tools are <Op verb="Entitlement.check" />, <Op verb="Deal.create" />, <Op verb="Quote.send" />, <Op verb="Task.assign" />, <Op verb="Lead.create" />, and any other call becomes an Approval under the Role’s Mandate.

```md title="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; <Op verb="Role.staff" /> seats it.

```bash
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 | <Op verb="Deal.create" /> → <Op verb="Quote.send" /> co-termed to renewal | <Op verb="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 <Op verb="Lead.create" /> | 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 | — | <Op verb="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 | <Op verb="Conversation.answer" /> |
| Not now | “Let’s wait until January.” | the ask closes, and `customer-ask-cap` frees the Customer | nobody | at once | <Op verb="Decision.list" /> |
| 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 | <Op verb="Conversation.answer" /> |
| Out of office | an auto-reply until Oct 28 | the note waits for the return date | nobody | the return date | <Op verb="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 | <Op verb="Conversation.escalate" /> |
| Stop | “Stop emailing us about seats.” | notes stop; the in-product seat notice keeps running | nobody | at once | <Op verb="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).

```json title="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 <Op verb="Task.claim" /> (Take this one), <Op verb="Task.return" /> with a reason, or <Op verb="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 <Op verb="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:** <Op verb="Deal.assign" /> sets `owner` to the receiving person, the agent is never a Deal’s owner, and Meetings land on the owner’s calendar under the owner’s name.

## When it won’t send
| Check (refuses one send) | Refuses | Fix | Then |
| --- | --- | --- | --- |
| <a id="check-owned-by-a-person"></a>`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. | <Op verb="Task.assign" /> |
| <a id="check-keep-first"></a>`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. | <Op verb="Task.next" /> |
| <a id="check-customer-ask-cap"></a>`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. | <Op verb="Decision.list" /> |
| <a id="check-reconcile"></a>`reconcile` (the record’s Reconcile stop; Not live yet, gaps #8) | a Customer whose Subscription and Stripe disagree | Clear the Stripe mismatch first. | <Op verb="Task.next" /> |
| <a id="check-price"></a>`price` (hub guard on <Op verb="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. | <Op verb="Approval.approve" /> |

| Stop (pauses the Play) | Pauses when | Then |
| --- | --- | --- |
| <a id="stop-misquote"></a>`misquote` | a Price, charge, seat count or renewal date differs from the live Price or the growth card | You approve each one |
| <a id="stop-leak"></a>`leak` | an output names a watched fact, or tells an old company where its champion went | You approve each one |
| <a id="stop-recipient"></a>`recipient` | a seat note goes to a member without admin or billing rights | the growth lane pauses until you clear the template |
| <a id="stop-downgrade"></a>`downgrade` | over 10 % of added seats are removed within 90 days | the growth lane pauses; the seat map is flagged to you |
| <a id="stop-successor-deadline"></a>`successor-deadline` | 3 departures in 30 days with no successor after 5 business days | the growth lane pauses; <Op verb="Task.assign" /> to you |
| <a id="stop-holdout-integrity"></a>`holdout-integrity` | a timed note reaches a scheduled-arm Customer; over 5 % voids the readout | <Op verb="Task.assign" /> |
| <a id="stop-spend-limit"></a>`spend-limit` | month-to-date model spend reaches its Limit | every seat question and founder draft goes to a person; <Op verb="Limit.raise" /> |
| <a id="stop-kill-switch"></a>`kill-switch` | you press it | every 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.

```json title="The captured refusal · sample"
{
  "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"
    }
  ]
}
```


```json title="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 <Op verb="Metric.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`

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

```json title="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 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 <Op verb="Task.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.

```diff title="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
```mdx title="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 | <a id="tactic-team-growth-watch"></a>`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 | <a id="tactic-seat-offer-note"></a>`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 | <a id="tactic-seat-change"></a>`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 | <a id="tactic-successor-handoff"></a>`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 | <a id="tactic-champion-landing-join"></a>`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 | <a id="tactic-former-buyer-note"></a>`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 | <a id="tactic-team-growth-readout"></a>`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 | <a id="tactic-churn-alert"></a>`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 | <a id="tactic-send-gate-check"></a>`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 | <a id="tactic-reply-matrix"></a>`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 | <a id="trigger-customer-hiring"></a>`customer-hiring` | Customer hiring: `event` on `Opening.published`, window 30d. |
| Trigger | <a id="trigger-customer-funded"></a>`customer-funded` | Customer funded: `event` on `Company.funded`, window 90d. |
| Trigger | <a id="trigger-invite-burst"></a>`invite-burst` | Invite burst: `threshold` on `Tenant.invited`, window 14d. |
| Trigger | <a id="trigger-directory-new-members"></a>`directory-new-members` | Directory new members: `threshold` on `MemberDirectory.provisioned`, window 14d. |
| Trigger | <a id="trigger-team-limit-handoff"></a>`team-limit-handoff` | Team limit handoff: `threshold` on `Entitlement.checked`, window 30d. |
| Trigger | <a id="trigger-champion-departure-handoff"></a>`champion-departure-handoff` | Champion departure handoff: `event` on `Task.assigned`, window 3d. |
| Trigger | <a id="trigger-champion-landing-handoff"></a>`champion-landing-handoff` | Champion landing handoff: `event` on `Task.assigned`, window 7d. |
| Trigger | <a id="trigger-approver-move-handoff"></a>`approver-move-handoff` | Approver move handoff: `event` on `Task.assigned`, window 60d. |
| Trigger | <a id="trigger-champion-quiet"></a>`champion-quiet` | Champion quiet: `state` on `Metric.watched`, window 14d. |
| Trigger | <a id="trigger-departed-champion-renewal"></a>`departed-champion-renewal` | Departed champion renewal: `at` on `Renewal.opened`, window 30d. |
| Sequence | <a id="sequence-team-growth-note"></a>`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 | <a id="sequence-seat-change-confirm"></a>`seat-change-confirm` | Seat change confirmation: 2 steps; it ends on The summary sent; Seats removed; An unsubscribe. |
| Sequence | <a id="sequence-successor-welcome"></a>`successor-welcome` | Successor welcome: 3 steps; it ends on A call booked; A reply; An opt-out; 30 days from the welcome. |
| Sequence | <a id="sequence-former-buyer-warm"></a>`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 | <a id="benchmark-signal-expansion-month-one-seat-add"></a>`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 | <a id="benchmark-signal-expansion-month-four-seat-add"></a>`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). |