Run it beside the old tool
Runs a new Customer’s work beside their old tool every day, fixes each difference you caused, and books the switch when the report reads zero.
- Step
- Keep Customers
- Channel
- Onboarding
- Summary
- startupbuilder.dev
Be our CSM: when a Customer pays while their old tool still sends their notices, run Leaseline beside it every day, fix every difference we caused, and book the switch when the report reads zero for 5 days. Bring me any run that stalls. 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":"run-beside"}'import { $ } from 'startup-builder'
return await $.Campaign.create({ play: 'run-beside' }, { 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":"run-beside"}' --try # Not live yet (gaps #2){
"name": "do",
"arguments": {
"code": "return await $.Campaign.create({ play: 'run-beside' }, { 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": "run-beside can’t start yet: 1 check fails and 2 warn.",
"checks": [
{
"check": "use-when",
"status": "pass",
"observed": "41 Customers; 6 switchers a month name an old tool at intake",
"doc": "/plays/ run-beside#check-use-when",
"fix": "Keep 4 or more switchers a month, or use standard onboarding.",
"then": "Metric.query"
},
…
Hand our latest switching Customer’s run 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(%22Order.paid%22%2C%20%22Integration.connected%22%2C%20%22Integration.synced%22%2C%20%22Monitor.checked%22%2C%20%22Integration.revoked%22%2C%20%22Subscription.unsubscribed%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_9910"}'
# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/decisions?where=on%20%3D%20%22customer_71%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_71%22" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"import { $ } from 'startup-builder'
const { events: [last] } = await $.Event.list({ where: 'name in ("Order.paid", "Integration.connected", "Integration.synced", "Monitor.checked", "Integration.revoked", "Subscription.unsubscribed")', 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 (\"Order.paid\", \"Integration.connected\", \"Integration.synced\", \"Monitor.checked\", \"Integration.revoked\", \"Subscription.unsubscribed\")","limit":1}'
npx startup-builder do Trigger.fire --json '{"like":"event_9910"}' --try # Not live yet (gaps #7)
npx startup-builder do Decision.list --json '{"where":"on = \"customer_71\""}' # Not live yet (gaps #7)
npx startup-builder do Action.list --json '{"where":"on = \"customer_71\""}' # Not live yet (gaps #7){
"name": "do",
"arguments": {
"code": "const { events: [last] } = await $.Event.list({ where: 'name in (\"Order.paid\", \"Integration.connected\", \"Integration.synced\", \"Monitor.checked\", \"Integration.revoked\", \"Subscription.unsubscribed\")', 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_866",
"$type": "Decision",
"choice": "book-the-cutover",
"reason": "Ashgrove Lettings’ run read 0 open differences for 5 run days over 170 compared notices; all 4 ours patterns are fixed",
"stakes": "medium",
"options": [
"start-the-run",
"standard-onboarding",
"fix-ours",
"ask-the-choice",
"meet-on-a-stall",
"book-the-cutover",
…
sample · 4 Not live yetPreviewOn PATCH /{namespace}/campaigns/{id}Off PATCH /{namespace}/campaigns/{id}Take this one POST /{namespace}/tasks/{task}/claim
action_2 is the cutover ask to Ashgrove’s admin, waiting for your approval because the stakes are medium. It asks them for a date; nothing Leaseline makes reaches their live records before it.
77.8 cutovers per 100 switchers against 37.5 before · before-after,
| Try another: the Customer | Choice | Reason |
|---|---|---|
| Ashgrove Lettings: 0 open differences for 5 run days, 170 notices | book-the-cutover | a zero streak over 100 items |
| Linden Court: paid Pro; the old tool sends 34 notices a day | start-the-run | a switcher with read-only access |
| Ashgrove, day 4: 9 notices dated a day late on our side | fix-ours | a difference you caused, listed first |
| Rowan Place: open differences flat at 7 for 5 run days | meet-on-a-stall | a stall gets one Meeting |
| Quarry Homes: paid Pro, keeps everything in paper files | standard-onboarding | nothing to compare against |
- 40 min week 1, then 10 a week
- Your time
- Day 19: the first cutover booked
- First signal
- 4+ switchers a month, 20+ items a day
- Use it when
Where it sits: Stranger → Lead → Trialing → Activated → At risk → Churned → Won back · Cutover rate → Month-3 retention → Lost Customers ↓ → ARR added each week → ARR
Worth’s formula: about +22 cutovers per 100 switchers (Other Startups’ results; no lift of your own yet) × 6 switchers a month × 0.5 kept to month 3 that would have left × $347 Pro ARR ≈ +$229 of churn avoided. Under First signal: At 6 switchers a month, 90 days can tell only +40 per 100 or more, so your file starts at holdout: and reads before-after.
sample · api.sb/
Start it this week
npx startup-builder add play run-beside # 1 · writes plays/run-beside.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":"run-beside"}'
# 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":"run-beside"}'
# on
curl -X PATCH https://api.sb/leaseline.app/campaigns/run-beside \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"endsAt":null}'
# off: open runs end with their report; switchers get standard onboarding
curl -X PATCH https://api.sb/leaseline.app/campaigns/run-beside \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"endsAt":"now"}' | Check | Fix | Then |
|---|---|---|
use-when pass · Not live yet, gaps #8 | Keep 4 or more switchers a month, or use standard onboarding. | POST /{namespace}/metrics/query |
read-only fail | Connect the old tool with its read-only export scope instead. | POST /{namespace}/integrations/connect |
baseline warn · Not live yet, gaps #8 | n 8: it reads before-after, not proof, until volume allows a held-back half. | POST /{namespace}/metrics/query |
role-unstaffed warn · Not live yet, gaps #8 | Staff the CSM, or leave it: stalls come to you, and Priya’s 4 get drafts. | POST /{namespace}/roles/{role}/staff |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "run-beside can’t start yet: 1 check fails and 2 warn.",
"checks": [
{
"check": "use-when",
"status": "pass",
"observed": "41 Customers; 6 switchers a month name an old tool at intake",
"doc": "/plays/run-beside#check-use-when",
"fix": "Keep 4 or more switchers a month, or use standard onboarding.",
"then": "Metric.query"
},
{
"check": "read-only",
"status": "fail",
"observed": "the old-tool connection asks for write access",
"doc": "/plays/run-beside#check-read-only",
"fix": "Connect the old tool with its read-only export scope instead.",
"then": "Integration.connect"
},
{
"check": "baseline",
"status": "warn",
"observed": "3 of 8 switchers retired the old tool within 45 days, in 90 days",
"doc": "/plays/run-beside#check-baseline",
"fix": "n 8: 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/run-beside#check-role-unstaffed",
"fix": "Staff the CSM, or leave it: stalls come to you, and Priya’s 4 get drafts.",
"then": "Role.staff"
}
]
}It says no, says why, and names the fix.
| Check | Fix (one call) | Your time | Wait |
|---|---|---|---|
use-when · baseline · pass: 6 switchers a month; warn: 3 of 8 in 90 days (gaps #8) | POST /{namespace}/metrics/query on cutover-rate, which fills Before in Your number | 1 min | none |
read-only · fail: the connection asks for write | POST /{namespace}/integrations/connect with the read-only export scope | 10 min | the first backfill, about 3 h |
role-unstaffed · warn (gaps #8): 4 of your Customers have a person owner, Priya | POST /{namespace}/roles/{role}/staff, or leave it: stalls come to you, and Priya’s 4 get drafts in her queue | 0 min | none |
Your number · Before 37.5 · Now 77.8 cutovers per 100 switchers · Target 80 by Dec 31 · before-after,
Each run costs about $1.10 in shadow compute and model calls on differences, and each update $0.04 (Worker cost). The miss rule refunds the first month: 1 so far, $29. That comes to about $7 a month at 6 switchers, plus refunds.
When it starts
| When | Trigger id | Starts on | Window | How it gets in |
|---|---|---|---|---|
| A Customer pays while their old tool still does the work | switcher-first-order | event on Order.paid where the first paid Order, while the intake or the Deal names an old tool that still does the work | set up within 7d | POST /{namespace}/integrations/connect (Stripe) for the payment; the intake names the old tool |
| Both tools hold the same run day’s notices | run-day-synced | state on Integration.synced where both sides of an open run hold the same run day's items | compared within 1d | the old tool’s read-only export, through POST /{namespace}/integrations/{integration}/sync |
| 0 open differences for 5 run days, over 100 items | zero-streak | threshold on Monitor.checked where 0 open differences for 5 run days in a row, over 100 or more items, every ours pattern fixed | the cutover ask within 2d | the daily difference report |
+ 5 more for connects, stalls, day 45, the old tool going off and cancels (?lanes=) |
{
"$id": "https://api.sb/leaseline.app/events/event_9910" ,
"$type": "Event",
"name": "Monitor.checked",
"at": "2026-10-18T07:00:00Z" ,
"seq": 9910,
"data": {
"customer": "customer_71",
"run": "run_9",
"open": 0,
"streak": 5,
"compared": 170,
"oursFixed": 4,
"day": 23
}
}npx startup-builder trigger Monitor.checked --like event_9910 # replays the fifth zero day
npx startup-builder clock advance --by 2d # runs the cutover ask’s day-2 step
npx startup-builder listen Monitor.checked --forward-to http://localhost:3000/hook Who runs it
CSM · your agent (role:, worker_5) holds the Key Result. Stalls go to the Head of customers Role, which has no person, so you (Dana Okafor, person, worker_1), and so do day-45 refunds and every cutover ask, which you approve. The 4 Customers AE · Priya Shah (person) owns get drafts in her queue. Your number links to Did it work.
| Option | Rung today | Evidence (passed of n) | Moves up at | Drops back when | You can |
|---|---|---|---|---|---|
| Start the run · Standard onboarding | It does it | 11 of 11 · 3 of 3 | — | — | POST /{namespace}/actions/{action}/redirect |
| Fix what you caused · Ask the choice once | It does it, you can stop it | 13 of 13 · 21 of 22 (1 redirected) | met on day 16 (the first wave) | a fix fails the eval set → You approve each one | POST /{namespace}/actions/{action}/cancel inside the hold |
| Meet on a stall · Book the cutover · End the run and refund | You approve each one | 2 of 2 · 7 of 7 · 1 of 1 | never: the switch is the Customer’s date | — | POST /{namespace}/approvals/{approval}/approve · POST /{namespace}/approvals/{approval}/refuse |
| Change the run sheet’s fields | The Customer’s written yes | 1 added | never | — | POST /{namespace}/tasks/{task}/claim |
| Never: write to the old tool, zero errors claimed, a cancel that waits | Refused, every time | 1 draft refused | never | — | nothing; the Checks hold it |
What it reads · What it may call. It reads the old tool through its read-only export and only the fields the run sheet lists; a report view or a login never appears in a Message. Nothing it makes reaches live records before the cutover date the Customer confirms (shadow-hold). Its tools are POST /{namespace}/integrations/connect, POST /{namespace}/issues/{issue}/triage, POST /{namespace}/changes/{change}/review, POST /{namespace}/conversations/{conversation}/answer, POST /{namespace}/meetings/book, POST /{namespace}/tasks/{task}/assign, POST /{namespace}/integrations/{integration}/sync, POST /{namespace}/invoices/{invoice}/refund, and any other call becomes an Approval under the Role’s Mandate.
You hold the CSM Key Result: 80 cutovers per 100 switchers by 2026-12-31. ← worker, metric
On each Trigger, choose one option and say why; a held Customer gets Not now. ← triggers, chooses, keep-first
Compare due date, amount and recipient on every run day’s notices. ← your file, line 4
List the differences you caused first, each with its evidence. ← copyRules 2, 3
Say zero open differences over a count of items and days, never zero errors. ← copyRules 4
A stall gets one Meeting with Dana; day 45 with no streak refunds the month. ← your file, line 5 The slow loop. The weekly Decisions kept the run sheet’s fields and the 5-day streak, and no Change was merged in the window, so there is no replay or read-back to print. Each ours fix shipped through POST /{namespace}/changes/{change}/review against the Product’s eval set, never as a change to this Play.
Bring your own agent (folded) starts at It does it, you can stop it for difference classing when it matches the seated agent on the eval suite; cutover asks stay at You approve each one. POST /{namespace}/roles/{role}/staff seats it.
npx startup-builder eval --scenario run-beside-11 --agent "claude -p" --seat success-manager # seated: 7 of 9 cut over, every ours fix eval-gated · yours: 6 of 9, 1 difference classed theirs that was ours What it can do next
| Option | Picked when | Decided by | Runs | Hands to | If it’s just you |
|---|---|---|---|---|---|
Start the run start-the-run | a switcher’s first paid Order, read-only access, 20+ items a day | Your rule (line 3) | POST /{namespace}/integrations/connect read-only → the 14-day backfill → parallel-run-updates | nobody | the same |
Standard onboarding standard-onboarding | nothing checkable, under 20 items a day, or no read-only access | Your rule (line 3) | onboard-customer | nobody | the same |
Fix what you caused fix-ours | a difference classed ours | Agent’s call, with its reason | POST /{namespace}/issues/{issue}/triage → POST /{namespace}/changes/{change}/review against the eval set | nobody; the fix ships when every case passes | the same |
Ask the choice once ask-the-choice | a difference classed choice, under 8 questions | Agent’s call, with its reason | POST /{namespace}/conversations/{conversation}/answer with two worked items and both answers | the Customer’s admin | the same |
Meet on a stall meet-on-a-stall | open differences flat or rising for 5 run days, or past 8 questions | Your rule (the Stall stop) | POST /{namespace}/meetings/book → POST /{namespace}/tasks/{task}/assign | Head of customers, so you, due 1 business day · Take this one | you |
Book the cutover book-the-cutover | 0 open differences for 5 run days, over 100 items | Your rule (line 4) | cutover-asks for their date → POST /{namespace}/integrations/{integration}/sync imports the old tool’s history | you approve the ask · Take this one | you |
End the run and refund end-and-refund | day 45 with no zero streak | Your rule (the Miss stop) | POST /{namespace}/invoices/{invoice}/refund on the first month, with the final report | you approve it · Take this one | you |
Not now not-now | another Play holds them, or keep-first applies | Your rule (rule j) | nothing; names the holder | their CSM, so you · Take this one | you |
| When they write back | Fixture reply | What happens | Who answers | By when | Call |
|---|---|---|---|---|---|
| Interested | “Zero for a week. Let’s switch on Nov 1.” | the cutover date set; the old tool’s history imports that day | CSM · your agent | 1 business day | POST /{namespace}/integrations/{integration}/sync |
| Send me pricing | “Does Team cost more once we add the other building?” | Team’s live Price; the run goes on as it was | CSM · your agent | 5 min | POST /{namespace}/conversations/{conversation}/answer |
| Not now | “We’ll keep both until spring.” | keep both is recorded; the cutover asks stop | nobody | at once | GET /{namespace}/decisions |
| Wrong person | “Our bookkeeper runs the old tool.” | asks the admin to add them to the run; it never writes to an address from their text | CSM · your agent | 1 business day | POST /{namespace}/conversations/{conversation}/answer |
| Out of office | an auto-reply until Oct 30 | the weekly note waits; the run days go on | nobody | the return date | POST /{namespace}/messages/{message}/schedule |
| Question it can’t answer | “Can you match how our old tool rounds pro-rated rent?” | To a person, with the two worked items in the Brief | you | 1 business day | POST /{namespace}/conversations/{conversation}/escalate |
| Stop | “Stop the daily emails.” | the notes stop; the difference report stays live for them | nobody | at once | POST /{namespace}/contacts/{contact}/unsubscribe |
When it hands to you
Stalls, cutover asks and day-45 refunds come to you: the CSM and Head of customers Roles have no person. One Brief per Role, as captured (brief is Not live yet, gaps #7).
[
{
"$id": "https://api.sb/leaseline.app/tasks/task_871" ,
"$type": "Task",
"assignee": "worker_1",
"due": "2026-10-09T17:00:00Z" ,
"on": "customer_74",
"brief": {
"who": "Rowan Place, Pro since Sep 22" ,
"said": "open differences flat at 7 for 5 run days",
"chose": "meet-on-a-stall",
"why": "a stall gets one Meeting",
"open": "7 choice differences on pro-rated rent rounding",
"never": [
"zero errors",
"a claim about the old tool beyond this run"
]
}
},
{
"$id": "https://api.sb/leaseline.app/approvals/approval_140" ,
"$type": "Approval",
"verdict": null,
"deadline": "2026-10-20T17:00:00Z" ,
"draft": "Your run has read zero open differences for 5 run days over 170 notices. Which date should we switch on?" ,
"route": "book-the-cutover",
"checks": [
{
"check": "shadow-hold",
"status": "pass"
},
{
"check": "claims-resolve",
"status": "pass"
}
]
}
]You answer 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. A cancel ask ends the run within 1 hour and never waits on you (exit-never-waits). A Task nobody claims by its due time goes up a Role through POST /{namespace}/tasks/{task}/escalate, and person-promise fires.
On time: you, 10 of 10 within their due time, median 3 h, n 10, ■□□□ Too early to tell beside Other Startups’ 94 % on time. Your queue: in week 1, 4 approvals and 1 weekly review; then about 1 cutover ask and 1 stall every two weeks.
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) | an update to a Customer whose open Deal a person owns, or whose Conversation a person claimed | Leave it to the person who owns it: the draft waits in their queue. | POST /{namespace}/tasks/{task}/assign |
keep-first (rule j; gaps #8) | a cutover ask to a Customer who is at risk, past due or has an unsubscribe pending | Let their CSM reach them first, then ask again once they are healthy. | POST /{namespace}/tasks/next |
customer-ask-cap (gaps #3) | a second open ask to one Customer in a window, from any Play | review-requests holds them until Nov 4. | GET /{namespace}/decisions |
exit-never-waits (the record’s Cancel pass-through; gaps #3) | any step in front of a cancel while a run is open | Remove the step: the run ends within 1 h and the cancel completes. | POST /{namespace}/subscriptions/{subscription}/unsubscribe |
read-only (the record’s Read-only scope) | a credential for the old tool that grants write | Connect the old tool with its read-only export scope instead. | POST /{namespace}/integrations/connect |
shadow-hold (the record’s Shadow hold) | any output reaching live records before the cutover date the Customer confirmed | Keep it in the shadow run until their date. | GET /{namespace}/decisions |
field-scope (the record’s Field scope) | a read of a field the run sheet does not list | Ask the admin once in writing; the field joins on their yes. | POST /{namespace}/conversations/{conversation}/answer |
regression (the record’s Regression stop) | a fix that fails any case in the Product’s eval set | Fix it until every eval case passes; it ships to no account until then. | POST /{namespace}/changes/{change}/review |
copy-deletion (the record’s Copy deletion) | a read after the old tool is disconnected, or copied outputs kept off the Customer’s account | Nothing to do: reads stop that hour; one action removes the copies. | POST /{namespace}/integrations/{integration}/revoke |
claims-resolve (hub) | a sentence about the old tool beyond this run’s recorded differences, or zero errors claimed | Say zero open differences over the items and days counted. | POST /{namespace}/content/draft |
The captured refusal, then the Stop that fired as a Campaign.paused reason (Not live yet, gaps #2); the update that sent is the Linden Court thread in Example.
| Check | Fix | Then |
|---|---|---|
claims-resolve fail | Say zero open differences over the items and days counted. | POST /{namespace}/content/draft |
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 update to Linden Court did not send: it says their old tool makes errors.",
"checks": [
{
"check": "claims-resolve",
"status": "fail",
"doc": "/plays/run-beside#check-claims-resolve",
"fix": "Say zero open differences over the items and days counted.",
"then": "Content.draft"
},
{
"check": "keep-first",
"status": "pass"
}
]
}It says no, says why, and names the fix.
[
{
"reason": {
"limit": "stall",
"observed": "7 open, flat" ,
"bound": "5 run days",
"window": "run_11, Rowan Place"
},
"doc": "/plays/run-beside#stop-stall" ,
"task": "task_871"
}
]Did it work
At 6 switchers 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; the leaf and every node above it are Not live yet (gaps #4).
77.8 cutovers per 100 switchers against 37.5 before · before-after,
- Chose
- book-the-cutover
- Why
- Ashgrove Lettings’ run read 0 open differences for 5 run days over 170 compared notices; all 4 ours patterns are fixed
- From
- start-the-run · standard-onboarding · fix-ours · ask-the-choice · meet-on-a-stall · book-the-cutover · end-and-refund · not-now
- Expects
- the old tool off by the cutover date Ashgrove gives, and still paying in month 3
- Reopens when
- no Integration.revoked for customer_71 within 14d of the cutover date
- By
- worker_5
JSON
{
"$id": "decisions/decision_866",
"$type": "Decision",
"choice": "book-the-cutover",
"reason": "Ashgrove Lettings’ run read 0 open differences for 5 run days over 170 compared notices; all 4 ours patterns are fixed",
"stakes": "medium",
"options": [
"start-the-run",
"standard-onboarding",
"fix-ours",
"ask-the-choice",
"meet-on-a-stall",
"book-the-cutover",
"end-and-refund",
"not-now"
],
"by": "worker_5",
"on": "customer_71",
"expected": "the old tool off by the cutover date Ashgrove gives, and still paying in month 3",
"reopensWhen": "no Integration.revoked for customer_71 within 14d of the cutover date"
}| Node | Now (56 days) | Before (the 90 days, per 56) | Added | Confidence | Basis |
|---|---|---|---|---|---|
cutover-rate | 77.8 per 100 (7 of 9 matured switchers) · 2 still inside 45 days | 37.5 (3 of 8) | — · Worth about +$230 a month, kept | — | before-after, not proof · Other Startups’ results +22 per 100, ■■■□ Likely, n 1,140 |
month-3-retention | 8 of 9 still paying in month 3 | 5 of 8 | — | — | before-after; 2 still maturing |
churned-arr ↓ | $347 lost, 1 Customer · Booked: none; this node counts what left | $1,041, 3 Customers | — | — | before-after; lower is better |
net-new-arr · arr | carried from churned-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. The leaf counts switching Customers who start a run and retire the old tool within 45 days of their first paid Order.
How it moves it: each switcher saw every difference between the two tools listed and fixed, ours first, and reached a zero streak in a median of 21 days.
Guardrails: before against after, the family pair under churned-arr: contraction-arr, $0 from switches down against $347 before, and contribution-margin, run cost $1.10 a Customer against $29 a month.
{
"$id": "https://api.sb/leaseline.app/campaigns/run-beside" ,
"$type": "Campaign",
"play": "play:run-beside@1" ,
"path": [
"cutover-rate",
"month-3-retention",
"churned-arr",
"net-new-arr",
"arr"
],
"experiment": {
"metric": "cutover-rate",
"unit": "per-100",
"heldBack": 0,
"decision": null
},
"readout": {
"value": null,
"n": 9,
"maturing": 2,
"interval": null,
"readability": "inadmissible",
"basis": "before-after",
"pooled": true
},
"ledger": {
"spend": 29,
"workerCost": 13,
"hours": 1.6,
"pipelineArr": 0,
"wonArr": 0,
"projectedArr": 229,
"paybackDays": null
}
}Outside benchmark: this Play’s catalog record carries no sourced range yet, so Other Startups’ results stand in: +22 cutovers per 100 switchers, ■■■□ Likely, n 1,140.
Before it · After it. Before it: switch-kit and paid-pilot bring the switchers. After it: review-requests asks at the cutover, customer-referrals hears the Customers who switched, and usage-upsell takes them past their Limit; keep-first holds a Customer at risk.
Example
Leaseline ran it from 2026-08-24 to 2026-10-19 (56 days): 14 switchers paid, 3 went to standard onboarding, and 11 started a run; 9 have passed day 45. Read every captured thread at ?threads=; three follow the tables.
| What happened, by option | Decisions | Result (counts) | Kept (credited, not all caused by this Play) |
|---|---|---|---|
| Start the run · Standard onboarding | 11 · 3 | 11 runs · 3 onboarded | — |
| Fix what you caused · Ask the choice once | 13 · 22 | 13 ours patterns fixed · 21 answered | — |
| Meet on a stall · Book the cutover · End the run and refund | 2 · 7 · 1 | 2 Meetings · 7 cut over · 1 refunded ($29) | 7 of 7 still paying |
| All switchers · before | 9 matured · 8 | 7 cut over by day 45 · 3 | 8 paying in month 3 · 5 |
| At 4 switchers a month (the Use it when volume) | about 7 in 56 days | before-after, not proof | Other Startups’ results: +22 per 100, ■■■□ Likely, n 1,140 |
| Day | Event | What happened | Your time |
|---|---|---|---|
| 0 | event_9802 Campaign.created | the three steps; the dry run fails read-only | 15 min |
| 0 | event_9805 Integration.connected | the old tool’s read-only export; the first backfill took 3 h | 10 min |
| 16 | event_9840 Role.updated | promoted: fixes and choice questions go out after a 2-minute hold | — |
| 19 | event_9852 Monitor.checked | the first zero streak: Linden Court, 5 days over 168 notices | — |
| 45 | event_9888 Campaign.paused | stall on Rowan Place: 7 open for 5 run days; you met them on Oct 9 | 30 min |
| 55 | event_9910 Monitor.checked | Ashgrove’s zero streak, 170 notices; the cutover ask waits for your approval | — |
| 1–56 | event_9812 Approval.created, first of 11 | you approved 7 cutover asks, 1 refund and the first wave; claims-resolve refused 1 update; weekly reviews | 55 min · total 1.6 h measured, 1.5 h asked |
Leaseline → Linden Court’s admin, Sep 13 08:00 · Day 19 of your run: 0 open differences for 5 run days, over 168 renewal notices compared on due date, amount and recipient.¹ The 4 we caused are fixed and listed in your report.² Which date should we switch on? *Signed with the Role’s disclosure line.*³
Linden Court, Sep 13 10:20 · “Let’s switch on Oct 1.” The old tool went off on Oct 1; Linden Court is still paying.
- Good news: we found 6 errors in your old tool that Leaseline gets right.
+ Day 10: 6 open differences, all on the old tool’s side, each listed with the notice it came from. Rowan Place, 2026-10-08 07:00 · 7 open differences flat for 5 run days, all on pro-rated rent rounding · The agent, 07:01 · paused the weekly note and assigned the stall to you (
task_871), due 17:00 · Dana, 09:30 · claimed it and booked a 20-minute call for Oct 9; the rounding became a Setting.
¹ Zero open differences over a stated count of items and days, never zero errors. ² The differences you caused, listed first, each with its evidence. ³ The fixed signature line, compiled from the Role’s disclosure Setting.
Your settings
---
holdout: 0 # 6 switchers a month is too few to hold half back; it reads before-after
---
Run beside any switcher with read-only access and 20 or more notices a day.
Compare due date, amount and recipient; book the switch after 5 zero days over 100 notices.
A stall gets one call with Dana; day 45 with no streak refunds the first month. $ npx startup-builder show plays/run-beside
holdout 0 · 20 notices a day · 3 fields · 5 zero days over 100 · stall call · day-45 refund your file plays/run-beside.mdx:2,4,5,6 (Ask 2026-08-24)
worker role:success-manager, owns never · chooses 8 (6 rule, 2 read) · sequences parallel-run-updates, cutover-asks · approval first-send, every-send · checks owned-by-a-person, keep-first, customer-ask-cap, exit-never-waits, read-only, shadow-hold, field-scope, regression, copy-deletion, claims-resolve · stops 3 · placements gmail, outlook + the difference report catalog play:run-beside@1 (Not live yet, gaps #5) Every key from the catalog
Compiled, not written: edit the Play record run-beside 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.