URL: /plays/run-beside · Id: `play:run-beside`

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

Runs a new [Customer](https://startupbuilder.dev/business/nouns/customer)’s work beside their old tool every day, fixes each difference you caused, and books the switch when the report reads zero.

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

```text title="Run one · Ask"
Hand our latest switching Customer’s run 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 ("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 }
```

```json title="Run one · Response · sample"
{
  "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",
      "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"
  },
  "actions": [
    {
      "$id": "https://api.sb/leaseline.app/actions/action_1",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_5",
      "verb": "Monitor.check",
      "checks": []
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_2",
      "$type": "Action",
      "status": "pending",
      "reason": "awaitingApproval",
      "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": "shadow-hold",
          "status": "pass"
        }
      ]
    }
  ]
}
```

`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, not proof · n 9 · path → arr`

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

| Worth | Your time | Spend | First signal | Use it when | Instead |
| --- | --- | --- | --- | --- | --- |
| ≈ +$230 ARR a month at 6, kept | 40 min week 1, then 10 a week | $1.10 per run, ~$7 a month at 6 | Day 19: the first cutover booked | 4+ switchers a month, 20+ items a day | Under 4: `onboard-customer` · no read-only access: `switch-kit` |

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

`sample · api.sb/leaseline.app · 41 Customers at 2026-10-19 · on Monitor.checked · day-0 difference report 3 h after connect · 8 keys Not live yet`

## Start it this week
```bash
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"}'
```


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

| Check | Fix (one call) | Your time | Wait |
| --- | --- | --- | --- |
| <a id="check-use-when"></a>`use-when` · <a id="check-baseline"></a>`baseline` · pass: 6 switchers a month; warn: 3 of 8 in 90 days (gaps #8) | <Op verb="Metric.query" /> on `cutover-rate`, which fills Before in Your number | 1 min | none |
| <a id="check-read-only"></a>`read-only` · fail: the connection asks for write | <Op verb="Integration.connect" /> with the read-only export scope | 10 min | the first backfill, about 3 h |
| <a id="check-role-unstaffed"></a>`role-unstaffed` · warn (gaps #8): 4 of your Customers have a person owner, Priya | <Op verb="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, not proof · n 9 matured`

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 | <Op verb="Integration.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 <Op verb="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=all`) | | | | |

```json title="One signed delivery · StartupBuilder-Signature"
{
  "$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
  }
}
```

```bash title="Reproduce it in test"
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:success-manager`, `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 | — | — | <Op verb="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 | <Op verb="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 | — | <Op verb="Approval.approve" /> · <Op verb="Approval.refuse" /> |
| Change the run sheet’s fields | The Customer’s written yes | 1 added | never | — | <Op verb="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 <Op verb="Integration.connect" />, <Op verb="Issue.triage" />, <Op verb="Change.review" />, <Op verb="Conversation.answer" />, <Op verb="Meeting.book" />, <Op verb="Task.assign" />, <Op verb="Integration.sync" />, <Op verb="Invoice.refund" />, and any other call becomes an Approval under the Role’s Mandate.

```md title="The brief (folded): compiled Agent-register Markdown · play:run-beside@1 · each line names its record key"
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 <Op verb="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. <Op verb="Role.staff" /> seats it.

```bash
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) | <Op verb="Integration.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 | <Op verb="Issue.triage" /> → <Op verb="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 | <Op verb="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) | <Op verb="Meeting.book" /> → <Op verb="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 → <Op verb="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) | <Op verb="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 | <Op verb="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 | <Op verb="Conversation.answer" /> |
| Not now | “We’ll keep both until spring.” | keep both is recorded; the cutover asks stop | nobody | at once | <Op verb="Decision.list" /> |
| 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 | <Op verb="Conversation.answer" /> |
| Out of office | an auto-reply until Oct 30 | the weekly note waits; the run days go on | nobody | the return date | <Op verb="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 | <Op verb="Conversation.escalate" /> |
| Stop | “Stop the daily emails.” | the notes stop; the difference report stays live for them | nobody | at once | <Op verb="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).

```json title="What lands in each queue · sample"
[
  {
    "$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 <Op verb="Task.claim" /> (Take this one), <Op verb="Task.return" /> with a reason, <Op verb="Action.redirect" />, <Op verb="Approval.approve" /> or <Op verb="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 <Op verb="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:** <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) | 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. | <Op verb="Task.assign" /> |
| <a id="check-keep-first"></a>`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. | <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 | `review-requests` holds them until Nov 4. | <Op verb="Decision.list" /> |
| <a id="check-exit-never-waits"></a>`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. | <Op verb="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. | <Op verb="Integration.connect" /> |
| <a id="check-shadow-hold"></a>`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. | <Op verb="Decision.list" /> |
| <a id="check-field-scope"></a>`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. | <Op verb="Conversation.answer" /> |
| <a id="check-regression"></a>`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. | <Op verb="Change.review" /> |
| <a id="check-copy-deletion"></a>`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. | <Op verb="Integration.revoke" /> |
| <a id="check-claims-resolve"></a>`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. | <Op verb="Content.draft" /> |

| Stop (pauses the Play) | Pauses when | Then |
| --- | --- | --- |
| <a id="stop-stall"></a>`stall` | open differences flat or rising for 5 run days | the weekly note pauses; a Task to you and a Meeting proposed |
| <a id="stop-miss"></a>`miss` | no zero streak by day 45 | the run ends with its final report; the first month is refunded |
| <a id="stop-kill-switch"></a>`kill-switch` | you press it | every Tactic pauses; open runs keep their report |
| + 1 more for the email lane: the question cap (`?lanes=all`) | | |

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.

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


```json title="The Stops that fired, as Campaign.paused reasons · sample"
[
  {
    "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 <Op verb="Metric.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, not proof · n 9 · path → arr`

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

```json title="The Campaign card · sample"
{
  "$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=all`; 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.

```diff title="Linden Court, 2026-09-04 · the update claims-resolve refused, then the update that sent"
- 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
```mdx title="plays/run-beside.mdx"
---
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.
```

```text
$ 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
| Key | Id | What it is |
| --- | --- | --- |
| Tactic | <a id="tactic-parallel-run-setup"></a>`parallel-run-setup` | Parallel run setup: Scopes one parallel run on a signed run sheet, connects the old tool read-only, and backfills 14 days into a day-0 difference report. Starts at You approve the first wave. |
| Tactic | <a id="tactic-daily-difference"></a>`daily-difference` | Daily difference: Compares each run day’s items from both tools field by field, classes every difference as ours, theirs, choice or mapping, and refreshes the difference report. Starts at You approve the first wave. |
| Tactic | <a id="tactic-difference-burn-down"></a>`difference-burn-down` | Difference burn-down: Closes open differences: an eval-gated fix per ours pattern, a Setting per choice answer, a Mapping fix per gap, and one Meeting for a stall. Starts at You approve each one. |
| Tactic | <a id="tactic-cutover-handoff"></a>`cutover-handoff` | Cutover handoff: Turns a zero streak into a dated cutover with the old tool’s history imported, or ends a missed run on day 45 with a refund. Starts at You approve each one. |
| Tactic | <a id="tactic-claims-check"></a>`claims-check` | Claims check maps every draft sentence to an approved source or the Person’s own words, and holds anything else before it reaches anyone. Starts at Sends nothing. |
| Trigger | <a id="trigger-switcher-first-order"></a>`switcher-first-order` | Switcher first Order: `event` on `Order.paid`, window 7d. |
| Trigger | <a id="trigger-old-tool-connected"></a>`old-tool-connected` | Old tool connected: `event` on `Integration.connected`, window 14d. |
| Trigger | <a id="trigger-run-day-synced"></a>`run-day-synced` | Run day synced: `state` on `Integration.synced`, window 1d. |
| Trigger | <a id="trigger-difference-stalled"></a>`difference-stalled` | Difference stalled: `threshold` on `Monitor.checked`, window 3d. |
| Trigger | <a id="trigger-zero-streak"></a>`zero-streak` | Zero streak: `threshold` on `Monitor.checked`, window 2d. |
| Trigger | <a id="trigger-run-deadline"></a>`run-deadline` | Run deadline: `state` on `Monitor.checked`, window 1d. |
| Trigger | <a id="trigger-old-tool-retired"></a>`old-tool-retired` | Old tool retired: `event` on `Integration.revoked`, window 14d. |
| Trigger | <a id="trigger-run-cancel-ask"></a>`run-cancel-ask` | Run cancel ask: `event` on `Subscription.unsubscribed`, window 1d. |
| Sequence | <a id="sequence-parallel-run-updates"></a>`parallel-run-updates` | Parallel run updates: 8 steps; it ends on The zero streak holds, and the cutover sequence starts.; The Customer cancels.; The Customer asks to stop the notes; the report stays live.. |
| Sequence | <a id="sequence-cutover-asks"></a>`cutover-asks` | Cutover asks: 5 steps; it ends on The old tool is disconnected or confirmed off.; The Customer answers keep both, and the answer is recorded.; The Customer cancels.. |