Product signals
Finds teams getting value on a free account and writes to the right person, citing only their own usage, after you approve the first send.
- Channel
- Who’s visiting
- Kind
- Outreach
Be our SDR: when 3 or more people at one free account are using Leaseline, write to them about Team with their own counts only, never a name. A director who signs up gets a note from me. Keep half with no touch, as today. 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":"product-signals"}'import { $ } from 'startup-builder'
return await $.Campaign.create({ play: 'product-signals' }, { 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":"product-signals"}' --try # Not live yet (gaps #2){
"name": "do",
"arguments": {
"code": "return await $.Campaign.create({ play: 'product-signals' }, { 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": "product-signals can’t start yet: 1 check fails and 1 warns.",
"checks": [
{
"check": "use-when",
"status": "pass",
"observed": "99 free accounts in 30 days; Team at $79 for 5 members",
"doc": "/plays/ product-signals#check-use-when",
"fix": "Keep 30 or more free accounts a month, or run signup-interviews.",
"then": "Metric.query"
},
…
Hand our latest free account that crossed a usage line to the SDR agent and show me what it chose and why. Don’t send.
curl "https://api.sb/leaseline.app/events?where=name%20in%20(%22Event.captured%22%2C%20%22Entitlement.checked%22%2C%20%22Customer.activated%22%2C%20%22Page.viewed%22%2C%20%22Customer.stalled%22%2C%20%22Prospect.engaged%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_2211"}'
# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/decisions?where=on%20%3D%20%22lead_63%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%22lead_63%22" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"import { $ } from 'startup-builder'
const { events: [last] } = await $.Event.list({ where: 'name in ("Event.captured", "Entitlement.checked", "Customer.activated", "Page.viewed", "Customer.stalled", "Prospect.engaged")', 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 (\"Event.captured\", \"Entitlement.checked\", \"Customer.activated\", \"Page.viewed\", \"Customer.stalled\", \"Prospect.engaged\")","limit":1}'
npx startup-builder do Trigger.fire --json '{"like":"event_2211"}' --try # Not live yet (gaps #7)
npx startup-builder do Decision.list --json '{"where":"on = \"lead_63\""}' # Not live yet (gaps #7)
npx startup-builder do Action.list --json '{"where":"on = \"lead_63\""}' # Not live yet (gaps #7){
"name": "do",
"arguments": {
"code": "const { events: [last] } = await $.Event.list({ where: 'name in (\"Event.captured\", \"Entitlement.checked\", \"Customer.activated\", \"Page.viewed\", \"Customer.stalled\", \"Prospect.engaged\")', 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_402",
"$type": "Decision",
"choice": "team-plan-note",
"reason": "4 people at Brightwater Property Group drafted 31 notices this week on a free account; Team covers 5 members, and the count is 3 or more",
"stakes": "low",
"options": [
"team-plan-note",
"shared-limit-note",
"senior-welcome",
"reach-who-pays",
"rescue",
"open-a-deal",
…
sample · 4 Not live yetPreviewOn POST /{namespace}/processes/{process}/enableOff POST /{namespace}/processes/{process}/pauseTake this one POST /{namespace}/tasks/{task}/claim
action_2 puts Brightwater in the agent half. action_3 is the team plan note to contact_14, the member who signed up first, waiting for your approval because it is this account’s first send.
+18.5 Deals per 100 · ■■□□ Early sign · n 108 · path → arr
| Try another: the free account | Choice | Reason |
|---|---|---|
| Brightwater Property Group: 4 active people this week on a free account | team-plan-note | 3 or more active people |
| Redfern Homes: 2 people refused at the 5-unit Limit on Tuesday | shared-limit-note | two people met the same Limit |
| Marlow Estates: the Director of Operations activated yesterday | senior-welcome | a director, by their own stated role, at a strong fit |
| Harbor Row: a paying Customer read the cancel page twice | to-the-csm | a paying Customer never gets a sales touch |
- 45 min week 1, then 15 a week
- Your time
- Day 2: the first Meeting booked
- First signal
Where it sits: Stranger → Lead → Trialing → Activated → At risk → Churned → Won back · Triggered Organization Deal rate → Deals and trials opened → New Customers ↑ → ARR added each week → ARR
Worth’s formula: about +18.5 Deals per 100 fired accounts × 69 a month × 0.35 Deals to paid (your last 90 days) × $547 first order ARR ≈ +$2,444. Under First signal: About 8 weeks to tell a +12 lift at 69 a month (5 to read, 3 to mature); at 30 a month, 90 days can tell only +22 or more, so your half starts at 0.
sample · api.sb/
Start it this week
npx startup-builder add play product-signals # 1 · writes plays/product-signals.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":"product-signals"}'
# 3 · keep it, at You approve the first wave
curl -X POST https://api.sb/leaseline.app/campaigns \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"play":"product-signals"}'
# on
curl -X POST https://api.sb/leaseline.app/processes/outbound/enable \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
# off: no touch sends; churn alerts still reach the CSM
curl -X POST https://api.sb/leaseline.app/processes/outbound/pause \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"| Check | Fix | Then |
|---|---|---|
use-when pass · Not live yet, gaps #8 | Keep 30 or more free accounts a month, or run signup-interviews. | POST /{namespace}/metrics/query |
baseline pass · Not live yet, gaps #8 | Read it once; it fills Before in Your number. | POST /{namespace}/metrics/query |
domain-authenticated pass | Add the 3 DNS records shown; it passes once they resolve. | POST /{namespace}/domains/{domain}/authenticate |
mailbox-warm fail · Not live yet, gaps #1 | Warm the agent Mailbox; it passes after 14 days at rising volume. | POST /{namespace}/mailboxes/{mailbox}/warm |
role-unstaffed warn · Not live yet, gaps #8 | Staff the AE and CSM, or leave them: those routes come to you. | POST /{namespace}/roles/{role}/staff |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "product-signals can’t start yet: 1 check fails and 1 warns.",
"checks": [
{
"check": "use-when",
"status": "pass",
"observed": "99 free accounts in 30 days; Team at $79 for 5 members",
"doc": "/plays/product-signals#check-use-when",
"fix": "Keep 30 or more free accounts a month, or run signup-interviews.",
"then": "Metric.query"
},
{
"check": "baseline",
"status": "pass",
"observed": "13.0 Deals per 100 accounts that would have fired, n 140 in 90 days",
"doc": "/plays/product-signals#check-baseline",
"fix": "Read it once; it fills Before in Your number.",
"then": "Metric.query"
},
{
"check": "domain-authenticated",
"status": "pass",
"observed": "leaseline.app",
"doc": "/plays/product-signals#check-domain-authenticated",
"fix": "Add the 3 DNS records shown; it passes once they resolve.",
"then": "Domain.authenticate"
},
{
"check": "mailbox-warm",
"status": "fail",
"observed": "agent@leaseline.app, 3 days into warm-up",
"doc": "/plays/product-signals#check-mailbox-warm",
"fix": "Warm the agent Mailbox; it passes after 14 days at rising volume.",
"then": "Mailbox.warm"
},
{
"check": "role-unstaffed",
"status": "warn",
"roles": [
"sales-lead",
"success-manager"
],
"owned": 3,
"doc": "/plays/product-signals#check-role-unstaffed",
"fix": "Staff the AE and CSM, or leave them: those routes come to you.",
"then": "Role.staff"
}
]
}It says no, says why, and names the fix.
| Check | Fix (one call) | Your time | Wait |
|---|---|---|---|
use-when · baseline · pass: 99 free accounts a month, a team plan; 13.0 Deals per 100, n 140 (gaps #8) | POST /{namespace}/metrics/query on pql-deal-rate, which fills Before in Your number | 1 min | none |
domain-authenticated · pass | POST /{namespace}/domains/{domain}/authenticate | 0 min | none |
mailbox-warm · fail (gaps #1): 3 days into warm-up | POST /{namespace}/mailboxes/{mailbox}/warm on agent@leaseline.app | 2 min | 14 days; in-product cards show meanwhile |
role-unstaffed · warn (gaps #8): 3 free accounts have a person owner, you | POST /{namespace}/roles/{role}/staff, or leave it: AE and CSM routes come to you, and your 3 get drafts in your queue | 0 min | none |
Your number · Before 13.0 · Now 31.5 Deals per 100 · Target 30 by Dec 31 · ■■□□ Early sign · n 108 matured
Each usage card costs $0.01 to compute and each touch $0.04 from your Domain (Worker cost); first-party matching from your own sign-ins costs nothing. That comes to about $3.50 a month at 69 fired accounts and $1.50 at 30, under the $20 model spend Limit.
When it starts
| When | Trigger id | Starts on | Window | How it gets in |
|---|---|---|---|---|
| 3 or more people at one free account are active in 14 days | team-cluster | threshold on Event.captured where 3 or more active People at one free or trial Organization within 14 days | card within 1 h; touch within 1d | your app calls POST /{namespace}/events/capture on each notice drafted |
| 2 or more people at one account are refused at the same Limit | limit-pair | threshold on Entitlement.checked where allowed = false for 2 or more People at one Organization on the same Limit | 7d | your app calls POST /{namespace}/entitlements/check before each unit is added |
| Someone answers a touch with interest | product-hand-raise | event on Prospect.engaged where the answer is to a touch of this Play, read as interested | a Deal within 1 h | replies arrive in the agent Mailbox; POST /{namespace}/conversations/{conversation}/answer reads them |
+ 4 more for senior signups, pricing views, stalled signups and paying Customers on pricing (?lanes=) |
{
"$id": "https://api.sb/leaseline.app/events/event_2211" ,
"$type": "Event",
"name": "Event.captured",
"at": "2026-10-19T08:47:31Z" ,
"seq": 2211,
"data": {
"lead": "lead_63",
"organization": "organization_29",
"event": "notice.drafted",
"people": 4,
"count": 31,
"window": "7d",
"offer": "leaseline-free"
}
}npx startup-builder trigger Event.captured --like event_2211 # replays the delivery
npx startup-builder clock advance --by 3d # runs the team plan note’s day-3 step, which ends on any reply
npx startup-builder listen Event.captured --forward-to http://localhost:3000/hook Who runs it
SDR · your agent (role:, worker_2) holds the Key Result. It hands hand-raises and director signups to AE · Priya Shah (person) (role:, worker_3, staffed on day 9), the who-pays note to you (Dana Okafor, person, worker_1) to send from your own Mailbox, and paying Customers on pricing to the CSM Role, which is unstaffed, so you. Your number links to Did it work.
| Option | Rung today | Evidence (passed of n) | Moves up at | Drops back when | You can |
|---|---|---|---|---|---|
| Team plan note · Shared limit note | It does it, you can stop it | 22 of 22 · 9 of 9 | met on day 16 (the first wave of 20) | any touch fails a Check, or count-floor fires → You approve the first wave | POST /{namespace}/actions/{action}/cancel inside the hold |
| Rescue a stalled signup · Open a Deal · To the CSM | It does it, you can stop it | 7 of 8 (1 redirected) · 14 of 14 · 5 of 5 | the same | the same | the same |
| Founder welcome · Reach who pays | You approve each one | 4 of 4 · 3 of 3 | never: they go out under your name | — | POST /{namespace}/approvals/{approval}/approve · POST /{namespace}/approvals/{approval}/refuse |
| Change a Trigger’s threshold | You approve each one | 1 merged of 1 | never | — | POST /{namespace}/changes/{change}/merge · POST /{namespace}/changes/{change}/revert |
| Never: a colleague’s name, a page view, a count under 3 | Refused, every time | 2 drafts refused | never | — | nothing; count-floor holds it |
What it reads · What it may call. It reads counts from your own product Events, matched to a company by sign-in domain only. A page view, a session length or a stalled state chooses who and when, and never appears in a Message. Its tools are POST /{namespace}/entitlements/check, POST /{namespace}/meetings/book, POST /{namespace}/tasks/{task}/assign, POST /{namespace}/conversations/{conversation}/answer, POST /{namespace}/leads/{lead}/qualify, POST /{namespace}/deals/{deal}/assign, and any other call becomes an Approval under the Role’s Mandate.
You hold the SDR Key Result: 30 Deals per 100 fired accounts by 2026-12-31. ← worker, metric
On each Trigger, choose one option and say why; a paying Customer goes To the CSM. ← triggers, chooses
A count shown to anyone covers 3 or more people and names none of them. ← copyRules 2, 5
State only numbers a live usage card line holds; never a page view. ← copyRules 1, 3
Team note at 3 active people; founder note for a director (your file, lines 4–5). ← your file
Never write to a Lead or Deal a person owns: draft it into their queue. ← worker.owns, owned-by-a-person The slow loop, as a Change. decision_311 (2026-09-21, weekly) read that reviewers rejected 13 of the last 30 team-cluster fires at 2 active people, so change_23 raised the line to 3 (your file, line 4) and Dana merged it with POST /{namespace}/changes/{change}/merge. Replayed on past fires, POST /{namespace}/changes/{change}/review put the agent half at 29.8 per 100; read back at +21d it is 31.5, n 54, ■■□□ Early sign (Example, day 49).
Bring your own agent (folded) starts at It does it, you can stop it when it matches or beats the seated agent on the eval suite, and at You approve the first wave when it does not; POST /{namespace}/roles/{role}/staff seats it.
npx startup-builder eval --scenario product-signals-120 --agent "claude -p" --seat prospector # seated: 31.5 per 100, 0 Checks failed sent, $0.05 a fire · yours: 27.2, 1 count-floor refusal, $0.07 → You approve the first wave What it can do next
| Option | Picked when | Decided by | Runs | Hands to | If it’s just you |
|---|---|---|---|---|---|
Team plan note team-plan-note | 3 or more active people at one free account | Your rule (line 4) | team-plan-note, in the product and from the agent Mailbox | the reader’s press, or Open a Deal on a reply | the same |
Shared limit note shared-limit-note | 2 or more people refused at one Limit | Your rule (line 4) | POST /{namespace}/entitlements/check → shared-limit-note | the same | the same |
Founder welcome senior-welcome | a director or above activates at a strong fit | Agent’s call, with its reason | senior-welcome from your Mailbox → on reply POST /{namespace}/meetings/book | POST /{namespace}/tasks/{task}/assign → AE · Priya Shah, due 60 min · Take this one | you, within 60 min |
Reach who pays reach-who-pays | an active team and no known Contact who pays | Agent’s call, with its reason | buyer-note, drafted for you → POST /{namespace}/tasks/{task}/assign | you, to send from your own Mailbox · Take this one | you |
Rescue a stalled signup rescue | a fit signup stopped short of first value | Agent’s call, with its reason | dormant-rescue: their own last failed step, no plan | nobody | the same |
Open a Deal open-a-deal | someone answers a touch with interest | Your rule (the hand-raise) | POST /{namespace}/leads/{lead}/qualify → POST /{namespace}/deals/{deal}/assign | POST /{namespace}/tasks/{task}/assign → AE · Priya Shah, due 60 min (response-60m), Brief attached · Take this one | you, within 60 min |
To the CSM to-the-csm | a paying Customer reads pricing, billing or cancel | Your rule (the Customer guard) | POST /{namespace}/tasks/{task}/assign with a health brief; no Message | CSM, so you · Take this one | you |
| When they write back | Fixture reply | What happens | Who answers | By when | Call |
|---|---|---|---|---|---|
| Interested | “Yes, set us up on Team.” | opens a Deal and books it; the Brief goes to the AE (When it hands to you) | AE · Priya Shah (person) | 60 min | POST /{namespace}/meetings/book |
| Send me pricing | “What would Team cost for the four of us?” | Team’s live Price for the active count, never more seats | SDR · your agent | 5 min | POST /{namespace}/conversations/{conversation}/answer |
| Not now | “We’re fine on free for now.” | the note stops; the account rests 30 days before any new fire | nobody | at once | POST /{namespace}/leads/{lead}/nurture |
| Wrong person | “Our owner signs off on software.” | asks them to forward the team-totals link; it never writes to an address from their text | SDR · your agent | 1 business day | POST /{namespace}/conversations/{conversation}/answer |
| Out of office | an auto-reply until Oct 30 | the follow-up waits for the return date | nobody | the return date | POST /{namespace}/messages/{message}/schedule |
| Question it can’t answer | “Can Team sync with our accounting system?” | To a person, the question in the Brief | AE · Priya Shah | 60 min | POST /{namespace}/conversations/{conversation}/escalate |
| Stop | “How do you know how many of us use it? Stop.” | every touch to that company stops for 90 days, and the reply goes to you (privacy) | you | at once | POST /{namespace}/contacts/{contact}/suppress |
When it hands to you
Hand-raises and director signups go to AE · Priya Shah (person); the who-pays note and paying Customers on pricing come to you, and before day 9 every hand-off did. One Brief per Role, as captured (brief is Not live yet, gaps #7).
[
{
"$id": "https://api.sb/leaseline.app/tasks/task_509" ,
"$type": "Task",
"assignee": "worker_3",
"due": "2026-10-19T10:58:00Z" ,
"on": "lead_63",
"brief": {
"who": "Brightwater Property Group, free account" ,
"said": "Yes, set us up on Team. We’re 4 now, maybe 6 by spring." ,
"chose": "open-a-deal",
"why": "answered the team plan note with interest",
"counted": "4 active people, 31 notices in 7 days" ,
"never": [
"a colleague’s name",
"a page view",
"a discount"
]
}
},
{
"$id": "https://api.sb/leaseline.app/tasks/task_477" ,
"$type": "Task",
"assignee": "worker_1",
"due": "2026-10-06T17:00:00Z" ,
"on": "lead_58",
"brief": {
"who": "Tidewater Rentals, free account" ,
"chose": "reach-who-pays",
"why": "5 active people, and none of them is the owner who pays" ,
"draft": "a 3-line note from you, citing the team’s own totals"
}
},
{
"$id": "https://api.sb/leaseline.app/approvals/approval_61" ,
"$type": "Approval",
"verdict": null,
"deadline": "2026-09-03T15:00:00Z" ,
"draft": "Hi Sam, your team of 4 drafted 31 renewal notices on Leaseline this week…" ,
"route": "team-plan-note",
"checks": [
{
"check": "count-floor",
"status": "pass"
},
{
"check": "customer-collision",
"status": "pass"
}
]
}
]Each receiver answers with POST /{namespace}/tasks/{task}/claim (Take this one), POST /{namespace}/tasks/{task}/return with a reason, POST /{namespace}/actions/{action}/redirect, POST /{namespace}/approvals/{approval}/approve or POST /{namespace}/approvals/{approval}/refuse, each a one-call window. Once a person claims, the agent’s pending touches to that company cancel with claimedByPerson (Not live yet, gaps #7), and every reply goes to the claimer. A Task nobody claims by its due time goes up a Role through POST /{namespace}/tasks/{task}/escalate (AE to you), and person-promise fires.
On time: AE · Priya Shah, 13 of 14 claimed within 60 min, median 21 min, ■■□□ Early sign, n 14; you, 8 of 8, median 3 h on who-pays notes, n 8. Your queue: in week 1, 20 approvals and 1 weekly review; after the promotion on day 16, 1 weekly review, about 2 hand-offs and 1 who-pays note 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 touch to a Contact whose Lead or 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 |
customer-collision (the record’s Paying Customer guard; gaps #1) | a sales touch to a paying Customer | Send it to their CSM instead. | POST /{namespace}/leads/{lead}/assign |
mailbox-warm (gaps #1) | a touch from a Mailbox still in warm-up | Warm the Mailbox; it passes after 14 days at rising volume. | POST /{namespace}/mailboxes/{mailbox}/warm |
count-floor (the record’s Count-floor stop, held at the send) | a count under 3 people, a colleague’s name, or someone who left the totals | Say “2 people on your team”, never a name. | POST /{namespace}/messages/{message}/schedule |
| Stop (pauses the Play) | Pauses when | Then |
|---|---|---|
privacy | one reply objects to being watched; 2 in 30 days | that company rests 90 days; 2 pause the Play |
reply-floor | interested replies plus checkouts under 1 % after 300 first touches | the Play run stops |
unanswered-positive (the record’s Owner stop; standard response-60m) | a hand-raise waits past 1 business hour, or 30 % of a week’s approvals expire | new fires pause; POST /{namespace}/approvals/{approval}/approve resumes it |
false-fire | reviewers reject over 40 % of a Trigger’s last 30 fires | its threshold rises one notch |
holdout-integrity | a touch reaches a held-back account; over 5 % voids the readout | POST /{namespace}/tasks/{task}/assign names the Play that sent it |
spend-limit | month-to-date model spend reaches its Limit | that part pauses until the month turns (POST /{namespace}/limits/{limit}/raise) |
bounce (email) | bounces above 2 % of a day’s sends | that Mailbox pauses |
complaint (email) | complaints near 0.1 % | the Play’s email pauses on every Mailbox |
stop-line (email) | stop clicks above 2 % of a template’s last 100 delivered | that template pauses and returns to You approve each one |
sync-lag (in-product) | Events landing over 60 min late, or a fire over 24 h after its crossing | every route with a window under 7 days pauses |
kill-switch | you press it | POST /{namespace}/processes/{process}/pause on outbound; churn alerts still reach the CSM |
The captured refusal, then the Stop that fired as a Campaign.paused reason (Not live yet, gaps #2); the rewrite that sent is the Redfern Homes thread in Example.
| Check | Fix | Then |
|---|---|---|
count-floor fail | Say “2 people on your team”, never a name. | POST /{namespace}/messages/{message}/schedule |
customer-collision pass · Not live yet, gaps #1 | — | POST /{namespace}/leads/{lead}/assign |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "The note to Redfern Homes did not send: it names a colleague.",
"checks": [
{
"check": "count-floor",
"status": "fail",
"doc": "/plays/product-signals#check-count-floor",
"fix": "Say “2 people on your team”, never a name.",
"then": "Message.schedule"
},
{
"check": "customer-collision",
"status": "pass"
}
]
}It says no, says why, and names the fix.
[
{
"reason": {
"limit": "unanswered-positive",
"observed": "1h 22m",
"bound": "1 business hour",
"window": "1 hand-raise"
},
"doc": "/plays/product-signals#stop-unanswered-positive" ,
"task": "task_488"
}
]Did it work
Both halves are read from the same random assignment at the fire (rules m–o), and each figure’s call is POST /{namespace}/metrics/query by node and by arm; the leaf and every node above it are Not live yet (gaps #4).
+18.5 Deals per 100 · ■■□□ Early sign · n 108 · path → arr
- Chose
- team-plan-note
- Why
- 4 people at Brightwater Property Group drafted 31 notices this week on a free account; Team covers 5 members, and the count is 3 or more
- From
- team-plan-note · shared-limit-note · senior-welcome · reach-who-pays · rescue · open-a-deal · to-the-csm
- Expects
- a Deal or a Team trial opened by day 21
- Reopens when
- no Deal opened and no trial started for lead_63 within 21d
- By
- worker_2
JSON
{
"$id": "decisions/decision_402",
"$type": "Decision",
"choice": "team-plan-note",
"reason": "4 people at Brightwater Property Group drafted 31 notices this week on a free account; Team covers 5 members, and the count is 3 or more",
"stakes": "low",
"options": [
"team-plan-note",
"shared-limit-note",
"senior-welcome",
"reach-who-pays",
"rescue",
"open-a-deal",
"to-the-csm"
],
"by": "worker_2",
"on": "lead_63",
"expected": "a Deal or a Team trial opened by day 21",
"reopensWhen": "no Deal opened and no trial started for lead_63 within 21d"
}| Node | Agent half | Your half | Added | Confidence | Basis |
|---|---|---|---|---|---|
pql-deal-rate | 31.5 per 100 (17 of 54) | 13.0 (7 of 54) | +18.5 [3.2, 33.8] | ■■□□ Early sign | held back at random · n 108 · 12 still maturing |
opened-deals-and-trials | 38.9 per 100 (21 of 54) | 22.2 (12 of 54) | +16.7 [−0.4, 33.8] · Not carrying up yet | ■□□□ Too early to tell | the same assignment; your half’s trials started on their own |
new-arr ↑ | $3,284, 6 Customers · Booked: credited, not all caused by this Play | $694, 2 Customers | +$2,590 [−$701, +$5,077] · Worth about +$2,400 a month | ■□□□ Too early to tell (6 vs 2 Customers) | the same · 7 Deals still deciding |
net-new-arr · arr | +$3,284 | +$694 | +$2,590, carried: it moves no other bridge term | ■□□□ Too early to tell | carried from new-arr |
The leaf counts Deals or Team trials opened within 21 days per 100 free accounts assigned at their first fire, once paying Customers and held accounts are left out (rule m), to 100 matured in each half; at 54 and 54 so far the Experiment runs on.
How it moves it: a touch on a median of day 1 after the crossing, citing the team’s own counts, where your half heard nothing until they asked.
Guardrails: treated against held back: paid per Deal opened, 0.29 against 0.29 (new-paying-customers per Deal), and stop clicks, 0.9 % against no sends.
{
"$id": "https://api.sb/leaseline.app/campaigns/product-signals" ,
"$type": "Campaign",
"play": "play:product-signals@1" ,
"path": [
"pql-deal-rate",
"opened-deals-and-trials",
"new-arr",
"net-new-arr",
"arr"
],
"experiment": {
"metric": "pql-deal-rate",
"unit": "per-100",
"heldBack": 60,
"decision": null
},
"readout": {
"value": 18.5,
"n": 108,
"maturing": 12,
"interval": [
3.2,
33.8
],
"readability": "C",
"basis": "held-back",
"pooled": false
},
"ledger": {
"spend": 2,
"workerCost": 4,
"hours": 2.4,
"pipelineArr": 5688,
"wonArr": 3284,
"projectedArr": 2444,
"paybackDays": 2
}
}Outside benchmark: a median 8 % of free accounts convert to paid within six months, across self-serve B2B products (a growth analyst’s survey with two vendors, 2026-02-04).
Before it · After it. Before it: product-led and programmatic-seo bring the free accounts. After it: closer-handoff hears each Deal it opens, and usage-upsell hears the Team accounts that pace past 150 units; churn alerts it raises feed the CSM’s queue, never a sales touch.
Example
Leaseline ran it from 2026-08-24 to 2026-10-19 (56 days). 128 free accounts fired a Trigger, 8 were left out as paying Customers or held by a person, and 120 were assigned at random, 60 to each half; 108 have matured and 12 are still maturing. Read every captured thread at ?threads=; three follow the tables.
| What happened, agent half, by option | Decisions | Deals or trials opened | Won (credited, not all caused by this Play) |
|---|---|---|---|
| Team plan note · Shared limit note | 22 · 9 | 9 · 4 | 3 on Team (+$2,844 ARR) |
| Founder welcome · Reach who pays | 4 · 3 | 2 · 1 | 1 on Pro (+$347 ARR) · 0, 1 open |
| Rescue a stalled signup · Open a Deal · To the CSM | 8 · 14 · 5 | 1 · 14 (the Deals above) · 0 | 1 on Pro · counted above · 0 |
| Agent half · your half | 60 · 60 assigned | 21 of 54 matured · 12 of 54 | $3,284 · $694 |
| At 30 a month (the Use it when volume) | about 56 in 56 days | held back at random, half the fires | Other Startups’ results: +12.0 per 100, ■■■□ Likely, n 2,860 |
| Day | Event | What happened | Your time |
|---|---|---|---|
| 0 | event_2003 Campaign.created | the three steps; the dry run fails mailbox-warm | 15 min |
| 0–14 | event_2006 Mailbox.warmed | agent@leaseline.app warms; in-product cards show meanwhile | 2 min |
| 2 | event_2031 Meeting.booked | the first Meeting: Marlow Estates’ director, from your welcome note | — |
| 9 | event_2089 Role.staffed | AE · Priya Shah (person) takes role: | — |
| 16 | event_2120 Role.updated | promoted: team and limit notes send after a 2-minute hold | — |
| 28 · 49 | event_2144 Change.merged · event_2195 Readout.created | the team line from 2 to 3 people: replayed at 29.8 per 100, read back at +21d at 31.5, n 54 | 5 min |
| 33 | event_2160 Campaign.paused | unanswered-positive: a hand-raise waited 1 h 22 min; resumed in 9 min | 6 min |
| 1–56 | event_2012 Approval.created, first of 27 | you approved the first wave and 7 founder notes; count-floor refused 2 drafts, each rewritten once and sent; weekly reviews | 1 h 12 min · total 2.4 h measured, 2.5 h asked |
Leaseline → Sam at Brightwater, Oct 19 09:02 · Your team of 4 drafted 31 renewal notices in Leaseline this week.¹ Team is $79 a month for up to 5 members and 150 units, and keeps everyone’s notices in one place.² Want me to set it up for the 4 of you? *Signed with the Role’s disclosure line.*³
Sam, 09:57 · “Yes, set us up on Team. We’re 4 now, maybe 6 by spring.” A Deal opened, and the Brief reached Priya at 09:58 (When it hands to you).
- Hi Lee, Maria on your team hit the 5-unit limit on Tuesday too.
+ Hi Lee, 2 people on your team hit the 5-unit Limit this week. Pro covers 50 units for $29 a month. Marlow Estates, 2026-08-26 10:14 · the Director of Operations activated · Dana, from her own Mailbox, 10:40 · a 3-line welcome, approved by Dana · Reply, 13:05 · “Could we talk Thursday?” · *Priya, our account lead, has Thursday at 14:00 or 16:00.*⁴ The Brief went to Priya (
task_451).
¹ A count of 3 or more people, from a live usage card line, naming none of them. ² The live Offer’s Price, for the active count only. ³ The fixed signature line, compiled from the Role’s disclosure Setting. ⁴ The routed line, compiled from the Role’s Settings.
Your settings
---
holdout: 0.5 # half the fired accounts get no touch for 60 days, as today
---
Write about Team when 3 or more people at one free account are active.
A director who signs up gets a note from Dana, approved each time.
Hand-raises go to the AE.$ npx startup-builder show plays/product-signals
holdout 0.5 · team line 3 people · founder note for a director · hand-raises to the AE your file plays/product-signals.mdx:2,4,5,6 (Ask 2026-08-24; line 4 by change_23, 2026-09-21)
worker role:prospector, owns never · chooses 7 (4 rule, 3 read) · sequences team-plan-note, shared-limit-note, senior-welcome, dormant-rescue, buyer-note · approval first-send, every-send · checks owned-by-a-person, customer-collision, mailbox-warm · stops 11 + count-floor held at the send · placements gmail, outlook, yahoo-mail, linkedin + in-product card catalog play:product-signals@1 (Not live yet, gaps #5) Every key from the catalog
Compiled, not written: edit the Play record product-signals 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.