URL: /plays/objection-watch · Id: `play:objection-watch`

# Objection watch

> Records why each Customer or Deal said no, holds them from other outreach, and sends one Message, with proof, once the reason stops being true.

Records why each [Customer](https://startupbuilder.dev/business/nouns/customer) or [Deal](https://startupbuilder.dev/business/nouns/deal) said no, holds them from other outreach, and sends one [Message](https://startupbuilder.dev/business/nouns/message), with proof, once the reason stops being true.

```text title="Set it up · Ask"
Be our CSM for every no: when a Deal is lost or a Customer cancels, record why in their words and keep everyone else from pitching them. When that reason stops being true, send one note with the proof. Big accounts are mine. Show me what’s missing; don’t send.
```

```text title="Run one · Ask"
Hand our latest objection whose condition came true 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 ("Deal.lost", "Subscription.unsubscribed", "Subscription.switched", "Subscription.lapsed", "Conversation.labeled", "Consent.granted", "Issue.resolved", "Version.deployed", "Offer.published", "Organization.repriced", "Organization.deprecated", "Company.funded", "Opening.published", "Organization.appointed", "Organization.announced", "Objection.dated", "Conversation.answered", "Content.published", "Change.reverted")', 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_502",
    "$type": "Decision",
    "choice": "send-the-one",
    "reason": "Oakridge Flats cancelled Jun 3 saying “owner statements only come out as CSV”; issue_77 shipped statements in the owner portal on Oct 16 and has held all traffic for 3 days, so the proof rule passes",
    "stakes": "low",
    "options": [
      "send-the-one",
      "you-write-it",
      "name-what-it-answers",
      "send-the-correction",
      "keep-holding"
    ],
    "by": "worker_5",
    "on": "customer_38",
    "expected": "Subscription.created by customer_38 within 90d of the fire",
    "reopensWhen": "no inbound Message.created from contact_14 within 30d: the set closes and nothing else sends"
  },
  "actions": [
    {
      "$id": "https://api.sb/leaseline.app/actions/action_1",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_5",
      "verb": "Issue.get",
      "checks": []
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_2",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_5",
      "verb": "Content.draft",
      "checks": [
        {
          "check": "fact-check",
          "status": "pass"
        }
      ]
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_3",
      "$type": "Action",
      "status": "pending",
      "reason": "awaitingTime",
      "mode": "try",
      "by": "worker_5",
      "verb": "Message.schedule",
      "checks": [
        {
          "check": "owned-by-a-person",
          "status": "pass"
        },
        {
          "check": "keep-first",
          "status": "pass"
        },
        {
          "check": "customer-ask-cap",
          "status": "pass"
        },
        {
          "check": "proof-drift",
          "status": "pass"
        }
      ]
    }
  ]
}
```

`action_3` is the one Message to contact_14, in the thread of Oakridge’s cancel, held 30 minutes so you can stop it. The proof is read again just before it sends.

`$694 objection dollars closed against $0 before · before-after, not proof · n 3 · path → arr`

| Try another: the holder | Choice | Reason |
| --- | --- | --- |
| Oakridge Flats: cancelled Jun 3, “owner statements only come out as CSV” | `send-the-one` | issue_77 shipped portal statements and held all traffic 3 days |
| Birchway Properties: lost Deal, “we need it to work for 600 units”, a top-tier holder | `you-write-it` | top tier: the one Message is yours to write |
| Kestrel Rentals: downgraded, “too slow on mobile, and no bulk edits” | `name-what-it-answers` | the mobile fix shipped; bulk edits have not |
| Alder & Co: lost Deal, “no e-signing”; the e-sign Change was reverted Oct 11 | `send-the-correction` | a fired set’s Change came out: one correction within 24 h |

| Worth | Your time | Spend | First signal | Use it when | Instead |
| --- | --- | --- | --- | --- | --- |
| ≈ +$100 ARR a month at 3 sets | 1 h week 1, then 15 min a week | $0.04 per fire, ~$1 a month at 25 nos | Day 52: first former Customer back | 20+ nos a month, each recorded with a reason | Under 20 a month: `lead-reactivation` · nos with no reason: require one (<Op verb="Deal.lose" />) |

**Where it sits:** Stranger → **Lead** → Trialing → Activated → At risk → **Churned** → Won back · Objection dollars closed → Won back ↑ → ARR added each week → ARR

Worth’s formula: about +9 returns per 100 former-Customer sets fired (Other Startups’ results; no lift of your own yet) × 3 sets a month × $347 Pro ARR ≈ +$94; the Prospects’ sets read beside it as Deals opened. Under First signal: At 3 former-Customer sets a month, 90 days can tell only +40 per 100 or more, so your file reads before-after; a set matures 90 days after it fires.

`sample · api.sb/leaseline.app · 41 Customers at 2026-10-19 · on Issue.resolved · first fire 38m after the proof passed · 9 keys Not live yet`

## Start it this week
```bash
npx startup-builder add play objection-watch   # 1 · writes plays/objection-watch.mdx (Not live yet, gaps #5)

# 2 · dry run; below, its 422 on 2026-06-01
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":"objection-watch"}'

# 3 · keep it, at You approve each one
curl -X POST https://api.sb/leaseline.app/campaigns \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"play":"objection-watch"}'

# on
curl -X PATCH https://api.sb/leaseline.app/campaigns/objection-watch \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"endsAt":null}'

# off: no fire sends; every hold stays in force
curl -X PATCH https://api.sb/leaseline.app/campaigns/objection-watch \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"endsAt":"now"}'
```


```json title="The dry run’s 422 · 2026-06-01 · sample"
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "objection-watch can’t start yet: 1 check fails and 2 warn.",
  "checks": [
    {
      "check": "use-when",
      "status": "pass",
      "observed": "26 nos a month: lost Deals, cancels, downgrades and stalled trials",
      "doc": "/plays/objection-watch#check-use-when",
      "fix": "Nothing to do: every no with a reason counts.",
      "then": "Metric.query"
    },
    {
      "check": "billing-connected",
      "status": "fail",
      "doc": "/plays/objection-watch#check-billing-connected",
      "fix": "Connect Stripe so every cancel and downgrade arrives with its reason code.",
      "then": "Integration.connect"
    },
    {
      "check": "baseline",
      "status": "warn",
      "observed": "0 of 9 former Customers back in 90 days, $0",
      "doc": "/plays/objection-watch#check-baseline",
      "fix": "n 9: it reads before-after, not proof, until volume allows a held-back half.",
      "then": "Metric.query"
    },
    {
      "check": "owned-by-a-person",
      "status": "pass",
      "observed": "3 holders have a person owner, you",
      "doc": "/plays/objection-watch#check-owned-by-a-person",
      "fix": "Nothing to do: their fires draft into your queue instead.",
      "then": "Task.assign"
    },
    {
      "check": "keep-first",
      "status": "pass",
      "observed": "0 held",
      "doc": "/plays/objection-watch#check-keep-first",
      "fix": "Nothing to do: a Customer at risk or past due hears from their CSM first.",
      "then": "Task.next"
    },
    {
      "check": "customer-ask-cap",
      "status": "pass",
      "observed": "0 open asks",
      "doc": "/plays/objection-watch#check-customer-ask-cap",
      "fix": "Nothing to do: one open ask per Customer, across every Play.",
      "then": "Decision.list"
    },
    {
      "check": "role-unstaffed",
      "status": "warn",
      "roles": [
        "success-manager",
        "sales-lead"
      ],
      "owned": 3,
      "doc": "/plays/objection-watch#check-role-unstaffed",
      "fix": "Staff the CSM and AE, or leave them: replies and big accounts come to you.",
      "then": "Role.staff"
    }
  ]
}
```

| Check | Fix (one call) | Your time | Wait |
| --- | --- | --- | --- |
| <a id="check-use-when"></a>`use-when` · pass: 26 nos a month (gaps #8) | <Op verb="Metric.query" /> on `objection-dollars-closed` | 1 min | none |
| <a id="check-billing-connected"></a>`billing-connected` · fail | <Op verb="Integration.connect" /> with `system` recurring-billing (Stripe) | 5 min | the first sync, about 10 min |
| <a id="check-baseline"></a>`baseline` · warn: 0 of 9 in 90 days | <Op verb="Metric.query" />; it fills Before in Your number | 0 min | none |
| <a id="check-owned-by-a-person"></a>`owned-by-a-person` · <a id="check-keep-first"></a>`keep-first` · <a id="check-customer-ask-cap"></a>`customer-ask-cap` · pass: 3 owned by you; 0 held; 0 open asks (gaps #3, #8) | nothing; each runs again on every fire | 0 min | none |
| <a id="check-role-unstaffed"></a>`role-unstaffed` · warn: 3 holders have a person owner, you | <Op verb="Role.staff" />, or leave it: replies and top-tier sets come to you | 0 min | none |

`Your number · Before $0 · Now $694 objection dollars closed · Target $1,500 by Dec 31 · before-after, not proof · n 3 matured · Other Startups +9 per 100, ■■■□ Likely`

Capturing and coding a no costs about $0.01, and each fire about $0.04 from your Domain (Worker cost). The condition checks read your own Issues and public pages at no cost. That comes to about $1 a month at 25 nos and 5 fires.

## When it starts
| When | Trigger id | Starts on | Window | How it gets in |
| --- | --- | --- | --- | --- |
| A Customer cancels, with a reason | `subscription-ending` | `event` on `Subscription.unsubscribed` where reason != null | captured within 1 h | <Op verb="Integration.connect" /> (Stripe); your cancel form posts the reason |
| A Deal is lost, with the code and their own words | `deal-lost` | `event` on `Deal.lost` where lossReason != null | captured within 1 h | <Op verb="Deal.lose" /> with `reason` |
| An Issue linked to open objections ships and holds all traffic | `issue-resolved` | `event` on `Issue.resolved` where linkedObjections > 0 and heldAllTraffic = true | 30d | <Op verb="Issue.resolve" /> |
| + 10 more: downgrades, stalled trials and not-now replies that open a set; and rival pages, new buyers, dated conditions and trust items that can close one (`?lanes=all`) | | | | |

```json title="One signed delivery · StartupBuilder-Signature"
{
  "$id": "https://api.sb/leaseline.app/events/event_1290",
  "$type": "Event",
  "name": "Issue.resolved",
  "at": "2026-10-19T06:00:11Z",
  "seq": 1290,
  "data": {
    "issue": {
      "$id": "https://api.sb/leaseline.app/issues/issue_77",
      "title": "Owner statements in the owner portal",
      "resolvedAt": "2026-10-16",
      "heldAllTraffic": true
    },
    "linkedObjections": [
      "objection_12",
      "objection_19"
    ],
    "openDollars": 694
  }
}
```

```bash title="Reproduce it in test"
npx startup-builder trigger Issue.resolved --like event_1290   # replays the delivery in test
npx startup-builder clock advance --by 30m   # sends the one Message (action_3) after its hold
npx startup-builder listen Subscription.created --forward-to http://localhost:3000/hook
```

## Who runs it
**CSM · your agent** (`role:success-manager`, `worker_5`) holds the Key Result. A reply that wants to come back goes to **AE · Priya Shah (person)** (`worker_3`, staffed on day 9); top-tier sets and every set whose proof cites a named rival come to **you** (Dana Okafor, person, `worker_1`), because the CSM Role has no person. Your number links to Did it work.

| Option | Rung today | Evidence (passed of n) | Moves up at | Drops back when | You can |
| --- | --- | --- | --- | --- | --- |
| Send the one Message | It does it, you can stop it | 11 of 11 approved unedited | met on day 71 (the first 10 fires, none not-answered) | a not-answered reply, or the `double-fire` Stop → You approve each one | <Op verb="Action.cancel" /> inside the 30-minute hold |
| Name what it answers · Keep holding | It does it, you can stop it | 3 of 3 · 41 of 41 | the same | the same | the same |
| You write it · Send the correction | You approve each one | 4 of 4 · 1 of 1 | never: top-tier sets and corrections stay yours | — | <Op verb="Approval.approve" /> · <Op verb="Approval.refuse" /> |
| Change a proof rule | You approve each one | 1 merged of 1 | never | — | <Op verb="Change.merge" /> · <Op verb="Change.revert" /> |
| Never: a second Message on one set, a discount, a shipped date for something unshipped | Refused, every time | 0 sent | never | — | none: the Stop holds it |

**What it reads · What it may call.** It reads the holder’s own words, the Issue and the proof source, and never a Visit, an open or a return to the site. A held holder is held for every Play: other Plays’ pool steps refuse a promotional touch until the set closes. Its tools are <Op verb="Message.schedule" />, <Op verb="Task.assign" />, <Op verb="Content.draft" />, and any other call becomes an Approval under the Role’s Mandate.

```md title="The brief (folded): compiled Agent-register Markdown · play:objection-watch@1 · each line names its record key"
You hold the CSM Key Result: $1,500 objection dollars closed by 2026-12-31.      ← worker, metric
On each condition that comes true, choose one option and say why.                ← triggers, chooses
Quote their words with their date; never make them stronger.                     ← copyRules 1
Cite the proof with its date, or do not send.                                    ← copyRules 3
Name only what the proof answers; no date for anything unshipped.                ← copyRules 6–7
Say this is the only Message about it, with one ask.                             ← copyRules 10–11
```

**The slow loop, as a Change.** `decision_288` (2026-08-17, weekly) read 4 mobile-speed sets whose proof was the release note alone, with 2 not-answered replies. `change_19` made the proof rule read the measured page time as well (your file, line 7), and Dana merged it with <Op verb="Change.merge" />. **Replayed on past fires**, <Op verb="Change.review" /> held 2 of the 4 back; read back at +30d, 0 not-answered of 3 fires, n 3, ■□□□ Too early to tell (Example, day 108).

**Bring your own agent** (folded) starts at It does it, you can stop it when it matches or beats the seated agent on the eval suite, and at You approve each one when it does not; <Op verb="Role.staff" /> seats it.

```bash
npx startup-builder eval --scenario objection-watch-118 --agent "claude -p" --seat success-manager   # seated: 21 of 21 sets falsified correctly, 0 double fires, $0.04 a fire · yours: 19 of 21 → You approve each one
```

## What it can do next
| Option | Picked when | Decided by | Runs | Hands to | If it’s just you |
| --- | --- | --- | --- | --- | --- |
| Send the one Message `send-the-one` | the proof rule passes and answers the whole objection | Your rule (line 5) | `objection-fire-one` in the thread of their no → <Op verb="Message.schedule" />, then a 30-day reply window | a reply that wants to come back: <Op verb="Task.assign" /> → AE · Priya Shah, due 1 business day · Take this one | you |
| You write it `you-write-it` | a top-tier holder, or the proof cites a named rival | Your rule (line 6) | `objection-fire-founder` → <Op verb="Task.assign" /> to you with the set and the proof | you write and send it · Take this one | you |
| Name what it answers `name-what-it-answers` | the proof answers part of the objection | Agent’s call, with its reason | `objection-fire-one`, naming only what shipped → <Op verb="Content.draft" /> | the same as Send the one Message | you |
| Send the correction `send-the-correction` | a Change behind a fired set was reverted | Your rule (the Revert stop) | `objection-correction` in the same thread, within 24 h | you approve it · Take this one | you |
| Keep holding `keep-holding` | the proof fails or is stale, the set is held back, or `keep-first` applies | Agent’s call, with its reason | nothing; the hold stays, and names what it waits on | the hold, until the condition is true or it expires | the same |

| When they write back | Fixture reply | What happens | Who answers | By when | Call |
| --- | --- | --- | --- | --- | --- |
| Interested | “Good timing. Can we restart on 160 units?” | over 150 units: a Quote; under it, the Pro restart link | AE · Priya Shah (person) | 1 business day | <Op verb="Quote.send" /> |
| Send me pricing | “What would it cost us now?” | the Pricing Page’s list Price, no private Price | CSM · your agent | 5 min | <Op verb="Conversation.answer" /> |
| Not now | “Maybe after our January budget.” | the set closes; January is recorded as a new condition date | nobody | at once | <Op verb="Decision.list" /> |
| Wrong person | “Our new ops lead, Sam, owns tools now.” | asks them to bring Sam in; it never writes to an address from their text | CSM · your agent | 1 business day | <Op verb="Conversation.answer" /> |
| Out of office | an auto-reply until Oct 28 | the 30-day reply window runs on; nothing else sends | nobody | the return date | <Op verb="Message.schedule" /> |
| Question it can’t answer | “Do statements include the owner’s tax forms?” | To a person, the question in the Brief | AE · Priya Shah, or you | 1 business day | <Op verb="Conversation.escalate" /> |
| Stop | “Please don’t contact us again.” | nothing else sends from any Play, and the set closes | nobody | at once | <Op verb="Contact.suppress" /> |

## When it hands to you
Replies that want to come back go to **AE · Priya Shah (person)**; top-tier sets and sets citing a rival come to **you**, and before day 9 every reply did. 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_391",
    "$type": "Task",
    "assignee": "worker_3",
    "due": "2026-09-23T15:40:00Z",
    "on": "customer_29",
    "brief": {
      "who": "Sable Street Homes, cancelled May 19",
      "said": "Good timing. Can we restart on 160 units?",
      "objection": "no bulk lease edits",
      "proof": "issue_64, shipped Sep 15",
      "chose": "send-the-one",
      "why": "the proof passed; the whole objection is answered",
      "never": [
        "a discount",
        "a second Message on this set"
      ]
    }
  },
  {
    "$id": "https://api.sb/leaseline.app/tasks/task_342",
    "$type": "Task",
    "assignee": "worker_1",
    "due": "2026-09-04T17:00:00Z",
    "on": "lead_47",
    "brief": {
      "who": "Birchway Properties, lost Deal Jul 2",
      "said": "We need it to work for 600 units.",
      "chose": "you-write-it",
      "why": "a top-tier holder",
      "proof": "the 600-unit load test, published Sep 1"
    }
  }
]
```

The receiver answers with <Op verb="Task.claim" /> (Take this one), <Op verb="Task.return" /> with a reason, or <Op verb="Action.redirect" />, each a one-call window. Once a person claims, the agent sends nothing more on that set, and every reply goes to the claimer (`claimedByPerson`, Not live yet, gaps #7). A Task nobody claims by its due time goes up a Role through <Op verb="Task.escalate" />, and `person-promise` fires.

**On time:** AE · Priya Shah, 3 of 3 claimed within 1 business day, median 2 h 05 min, n 3; you, 4 of 4 sent within 3 days, n 4, ■□□□ Too early to tell beside Other Startups’ 91 % on time. **Your queue:** counted by kind: in week 1, 18 labels to confirm and 0 fires; after week 3, about 3 labels and 1 fire a week, plus the Monday Issue ranking.

**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 |
| --- | --- | --- | --- |
| `owned-by-a-person` (rule j, gaps #8) | a fire to a holder 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" /> |
| `keep-first` (rule j, gaps #8) | a fire 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" /> |
| `customer-ask-cap` (gaps #3) | a second open ask to one Customer in a window, from any Play | `next-outcome` holds them until Nov 18. | <Op verb="Decision.list" /> |
| <a id="check-proof-drift"></a>`proof-drift` (the record’s Proof drift stop) | a fire whose proof source changed or vanished since it was captured | Re-read the proof; the fire sends only if it passes again. | <Op verb="Issue.get" /> |
| <a id="check-label-backlog"></a>`label-backlog` (the record’s Label backlog hold) | a fire on a set whose label nobody confirmed | Confirm the label on the lost Deal, then the set can fire. | <Op verb="Deal.update" /> |
| <a id="check-market-hold"></a>`market-hold` (the record’s Market hold) | a fire to a Market that is unresolved or failed its row check | Resolve the holder’s Market; the fire never retries another way. | <Op verb="Organization.update" /> |
| <a id="check-fact-check"></a>`fact-check` (Not live yet) | a line the proof, the claims source or the holder’s own record does not back | Cut the claim, or name the record that backs it. | <Op verb="Content.draft" /> |

| Stop (pauses the Play) | Pauses when | Then |
| --- | --- | --- |
| <a id="stop-hold-breach"></a>`hold-breach` | any Play sends a promotional touch to a held holder; 2 in 30 days pause every Play’s pool step | that Play’s sends pause; an audit opens |
| <a id="stop-double-fire"></a>`double-fire` | a second fire on one set | every fire halts until you review the fire ledger |
| <a id="stop-not-answered"></a>`not-answered` | not-answered replies over 5 % of a kind’s last 40 fires, or 2 in 7 days | fires of that kind halt |
| <a id="stop-revert"></a>`revert` | a Change behind fired sets is reverted | fires on its Issue halt; the correction sends within 24 h |
| <a id="stop-freshness"></a>`freshness` | the Monday canary fails | condition checks pause until it passes |
| <a id="stop-kill-switch"></a>`kill-switch` | you press it | every fire stops; every hold stays in force |
| <a id="stop-complaint"></a>`complaint` | stop and hostile replies over 0.3 % of a stream’s last 1,000, or 3 in 7 days | fires pause on that stream |
| <a id="stop-bounce"></a>`bounce` | hard bounces over 2 % of a day’s fires, at 20 or more | the stream pauses; its queue is re-verified |

The captured refusal, then the Stop that fired as a `Campaign.paused` reason (Not live yet, gaps #2); the rewrite that sent is the Kestrel thread in Example.

```json title="The captured refusal · sample"
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "The fire to Kestrel Rentals did not send: it says both of their reasons are fixed, and the proof shows one.",
  "checks": [
    {
      "check": "fact-check",
      "status": "fail",
      "doc": "/plays/objection-watch#check-fact-check",
      "fix": "Cut the claim, or name the record that backs it.",
      "then": "Content.draft"
    },
    {
      "check": "proof-drift",
      "status": "pass"
    }
  ]
}
```


```json title="The Stops that fired, as Campaign.paused reasons · sample"
[
  {
    "reason": {
      "limit": "hold-breach",
      "observed": "1 promotional touch from lead-nurture to a held holder",
      "bound": "0",
      "window": "30d"
    },
    "doc": "/plays/objection-watch#stop-hold-breach",
    "task": "task_377"
  }
]
```

## Did it work
At 3 former-Customer sets 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. Only sets 90 days past their fire count (rule n). Each figure’s call is <Op verb="Metric.query" /> by node, and every node above the leaf is Not live yet (gaps #4).

`$694 objection dollars closed against $0 before · before-after, not proof · n 3 · path → arr`

| Node | Now (140 days) | Before (the 90 days, per 140) | Added | Confidence | Basis |
| --- | --- | --- | --- | --- | --- |
| `objection-dollars-closed` | $694: 2 of 3 matured former-Customer sets back on Pro · 2 still maturing · Booked $694: credited, not all caused by this Play | $0: 0 of 9 cancels back | — · Worth about +$100 a month | — | before-after, not proof · Other Startups’ results +9 returns per 100, ■■■□ Likely, n 1,460 |
| `reactivated-arr` ↑ | $1,041 | $347 | — | — | before-after; includes 1 return this Play never fired on |
| `net-new-arr` · `arr` | carried from `reactivated-arr` | carried | — | — | carried |

Before against after reads former Customers who came back in this window against the 90 days before it, scaled to 140 days; it is not proof, and Other Startups’ results print beside it. Beside the path, the Prospects’ 14 fired sets opened 4 Deals and won 1 on the $2,400 plan (+$2,393 ARR), read as `opened-deals-and-trials`.

**How it moves it:** each holder heard once, a median 38 minutes after the proof passed, in the thread of their own no, where before nobody wrote back at all (`objection-dollars-closed` by fire latency).

**Guardrails:** before against after: `contraction-arr`, $0 from returners who switched down, against $0, and hold breaches, 1 in 140 days, against a Stop at 2 in 30.

```json title="The Campaign card · sample"
{
  "$id": "https://api.sb/leaseline.app/campaigns/objection-watch",
  "$type": "Campaign",
  "play": "play:objection-watch@1",
  "path": [
    "objection-dollars-closed",
    "reactivated-arr",
    "net-new-arr",
    "arr"
  ],
  "experiment": {
    "metric": "objection-dollars-closed",
    "unit": "usd",
    "heldBack": 2,
    "decision": null
  },
  "readout": {
    "value": null,
    "n": 3,
    "maturing": 2,
    "interval": null,
    "readability": "inadmissible",
    "basis": "before-after",
    "pooled": true
  },
  "ledger": {
    "spend": 2,
    "workerCost": 1,
    "hours": 4.6,
    "pipelineArr": 4786,
    "wonArr": 3087,
    "projectedArr": 94,
    "paybackDays": 4
  }
}
```

Outside benchmark: 5 % to 10 % of churned subscribers come back on their own (a subscription-analytics vendor’s billing data, 2026-09-30; a base rate, not a lift).

**Before it · After it.** Before it: every lost Deal from `instant-response` and `cold-email`, every cancel from `usage-upsell`’s Customers and every stalled trial opens a set here. After it: a reply that wants to come back reaches the AE, Not now feeds `lead-nurture` with a dated condition, and new condition dates feed `moment-outreach`. While a set stands, every other Play’s pool step holds the holder.

## Example
Leaseline ran it from 2026-06-01 to 2026-10-19 (140 days): 118 nos captured, 15 held by former Customers and 103 by Prospects. 21 sets came true, 2 were held back at random, and 19 fired: 5 former Customers’ and 14 Prospects’. Read every captured thread at `?threads=all`; three follow the tables.

| What happened, by option | Sets | Replies | Came back or opened a Deal | Won (credited, not all caused by this Play) |
| --- | --- | --- | --- | --- |
| Send the one Message | 11 | 5 | 2 former Customers back, 3 Deals | 2 on Pro (+$694 ARR) |
| You write it | 4 | 3 | 1 Deal | 1 on the $2,400 plan (+$2,393 ARR) |
| Name what it answers · Send the correction · Keep holding | 3 · 1 · 41 | 1 · 0 · 0 | 0 | 0 |
| **All fires** · held back | **19** · 2 | **9** · — | **2 back, 4 Deals** · 0 | **$3,087** · $0 |
| At 20 nos a month (the Use it when volume) | about 13 fires in 140 days | before-after, not proof | — | Other Startups’ results: +9 returns per 100, ■■■□ Likely, n 1,460 |

| Day | Event | What happened | Your time |
| --- | --- | --- | --- |
| 0 | `event_101` Campaign.created | the three steps; the dry run fails `billing-connected` | 20 min |
| 0–21 | `event_133` Objection.captured, first of 118 | you confirmed 31 labels in the first 3 weeks; every set held its holder | 1 h 50 min |
| 9 | `event_190` Role.staffed | AE · Priya Shah (person) takes `role:sales-lead` | — |
| 52 | `event_611` Subscription.created | the first former Customer back: Sable Street Homes, on Pro | — |
| 71 | `event_788` Role.updated | promoted: fires send after a 30-minute hold; top-tier sets stay yours | — |
| 78 · 108 | `event_802` Change.merged · `event_1004` Readout.created | the mobile proof rule reads page time: read back 0 not-answered of 3 | 5 min |
| 96 | `event_915` Campaign.paused | `hold-breach`: `lead-nurture` touched a held holder; its pool step was fixed the same day | 15 min |
| 1–140 | `event_140` Approval.created, first of 19 | you approved fires and wrote 4; Monday Issue rankings, from the <Op verb="Task.next" /> log | 2 h 30 min · **total 4.6 h measured, 4 h asked** |

> **Leaseline → Sable Street Homes, Sep 22, 09:12, in the thread of their cancel** · On May 19 you wrote: “We cancelled because there are no bulk lease edits.”¹ Bulk edits shipped on Sep 15 and have run on every account since (release note, Sep 15).² This is the only note about it, and nothing else follows unless you reply.³ Want the restart link? *Signed with the Role’s disclosure line and the stop line.*⁴
>
> **Sable Street, Sep 22, 14:40** · “Good timing. Can we restart on 160 units?” The Brief reached Priya at 14:41; she claimed it at 15:02 and sent the Quote the next morning (When it hands to you).

```diff title="Kestrel Rentals, 2026-09-08 · the fire fact-check refused, then the rewrite that sent"
- Good news: mobile is fast now and bulk edits are live, so everything you asked for is fixed.
+ On Jul 2 you named mobile speed and bulk edits. Mobile pages now load in 1.2 s (measured Sep 7); bulk edits have not shipped.
```

> **Birchway Properties, 2026-09-03** · set held since a lost Deal on Jul 2: “We need it to work for 600 units.” · the 600-unit load test published Sep 1 passed the proof rule, and the set went to Dana as a top-tier holder (`task_342`). Dana wrote and sent the one Message on Sep 4 at 11:20, inside the promised time; Birchway booked a call that afternoon.

¹ Their own words, verbatim, with their date. ² The proof, with its date. ³ The only Message about this objection. ⁴ The fixed signature and stop lines, compiled from the Role’s Settings.

## Your settings
```mdx title="plays/objection-watch.mdx"
---
holdout: 0.1      # 1 set in 10 that comes true gets no Message for 90 days
---
Record every no in their own words, and hold them from every other Play.
One Message, with proof, when the reason stops being true.
Accounts over 300 units are mine to write.
A speed fix counts only once the measured page time shows it.
```

```text
$ npx startup-builder show plays/objection-watch
holdout 0.1 · top tier over 300 units · proof reads page time     your file  plays/objection-watch.mdx:2,4,5,6,7 (Ask 2026-06-01; line 7 by change_19, 2026-08-17)
family keep · worker role:success-manager, owns never · chooses 5 (3 rule, 2 read) · sequences objection-fire-one, objection-fire-founder, objection-correction · approval delay-window · checks owned-by-a-person, keep-first, customer-ask-cap, proof-drift, label-backlog, market-hold, fact-check · stops 8 · placements gmail, outlook + the founder’s LinkedIn tray    catalog  play:objection-watch@1 (Not live yet, gaps #5)
```

### Every key from the catalog
| Key | Id | What it is |
| --- | --- | --- |
| Tactic | <a id="tactic-objection-capture"></a>`objection-capture` | Objection capture: Turns every lost Deal, cancel, downgrade, stalled trial and not-now reply into a coded, quoted, priced objection within 1 hour of the no. Starts at Sends nothing. |
| Tactic | <a id="tactic-condition-offer"></a>`condition-offer` | Condition offer: Lets the holder choose, once and after the no completes, the one condition under which the Startup writes again, recorded with Consent. Starts at You approve the first wave. |
| Tactic | <a id="tactic-condition-compile"></a>`condition-compile` | Condition compile: Turns an objection into a checkable condition with a proof rule and an expiry, and writes the hold every other Play reads. Starts at Sends nothing. |
| Tactic | <a id="tactic-objection-ledger"></a>`objection-ledger` | Objection ledger: Ranks the Issue queue by the open objection dollars each Issue would falsify, and shows pricing and the comparison Pages the dollars behind their nos. Starts at Sends nothing. |
| Tactic | <a id="tactic-condition-check"></a>`condition-check` | Condition check: Tests open objection sets each time a condition Trigger fires, and marks a set falsified only when its proof rule passes. Starts at Sends nothing. |
| Tactic | <a id="tactic-send-gate-check"></a>`send-gate-check` | Send gate check: Checks Suppression, a live reason, the Market, the basis stamp, the Person-month grant, the facts and the labels before any cold Message exists. Starts at Sends nothing. |
| Tactic | <a id="tactic-objection-fire"></a>`objection-fire` | Objection fire: Sends exactly one Message per falsified objection set, in the thread of the original no, quoting the holder’s words with the proof that answers them. Starts at It does it, you can stop it. |
| Tactic | <a id="tactic-reply-matrix"></a>`reply-matrix` | Reply matrix: Labels every outreach reply with one of 11 labels, maps it to one of seven reply kinds, and runs that kind’s move by its deadline. Starts at You approve each one. |
| Tactic | <a id="tactic-sending-stop-loss"></a>`sending-stop-loss` | Sending stop loss: Reads the Play’s own bounces, replies and seed tests on every send, and rests, pauses or retires a Mailbox, Domain, cell or the Play. Starts at Sends nothing. |
| Tactic | <a id="tactic-objection-readout"></a>`objection-readout` | Objection readout: Reads objection dollars closed against the holdout by condition kind, with coverage, hold breaches, fire latency and each fix’s gain beside it. Starts at Sends nothing. |
| Trigger | <a id="trigger-deal-lost"></a>`deal-lost` | Deal lost: `event` on `Deal.lost`, window 1d. |
| Trigger | <a id="trigger-subscription-ending"></a>`subscription-ending` | Subscription ending: `event` on `Subscription.unsubscribed`, window 1d. |
| Trigger | <a id="trigger-subscription-downgraded"></a>`subscription-downgraded` | Subscription downgraded: `event` on `Subscription.switched`, window 1d. |
| Trigger | <a id="trigger-trial-stalled"></a>`trial-stalled` | Trial stalled: `state` on `Subscription.lapsed`, window 7d. |
| Trigger | <a id="trigger-objection-replied"></a>`objection-replied` | Objection replied: `event` on `Conversation.labeled`, window 1d. |
| Trigger | <a id="trigger-condition-set"></a>`condition-set` | Condition set: `event` on `Consent.granted`, window 1d. |
| Trigger | <a id="trigger-issue-resolved"></a>`issue-resolved` | Issue resolved: `event` on `Issue.resolved`, window 30d. |
| Trigger | <a id="trigger-loss-reason-resolved"></a>`loss-reason-resolved` | Loss reason resolved: `event` on `Version.deployed`, window 30d. |
| Trigger | <a id="trigger-offer-changed"></a>`offer-changed` | Offer changed: `event` on `Offer.published`, window 30d. |
| Trigger | <a id="trigger-competitor-price"></a>`competitor-price` | Competitor price: `event` on `Organization.repriced`, window 30d. |
| Trigger | <a id="trigger-competitor-changelog"></a>`competitor-changelog` | Competitor changelog: `event` on `Organization.deprecated`, window 30d. |
| Trigger | <a id="trigger-funding"></a>`funding` | Funding: `at` on `Company.funded`, window 45d. |
| Trigger | <a id="trigger-hiring"></a>`hiring` | Opening published: `event` on `Opening.published`, window 30d. |
| Trigger | <a id="trigger-new-executive"></a>`new-executive` | New executive: `event` on `Organization.appointed`, window 45d. |
| Trigger | <a id="trigger-threshold-announced"></a>`threshold-announced` | Threshold announced: `threshold` on `Organization.announced`, window 30d. |
| Trigger | <a id="trigger-condition-date"></a>`condition-date` | Condition date: `at` on `Objection.dated`, window 14d. |
| Trigger | <a id="trigger-not-now-date"></a>`not-now-date` | Not now date: `at` on `Conversation.answered`, window 14d. |
| Trigger | <a id="trigger-outcome-fact"></a>`outcome-fact` | Outcome fact: `event` on `Content.published`, window 30d. |
| Trigger | <a id="trigger-trust-item-published"></a>`trust-item-published` | Trust item published: `event` on `Content.published`, window 30d. |
| Trigger | <a id="trigger-change-reverted"></a>`change-reverted` | Change reverted: `event` on `Change.reverted`, window 1h. |
| Sequence | <a id="sequence-objection-fire-one"></a>`objection-fire-one` | Objection fire one: 2 steps; it ends on Any reply; A return: a restore, a new Deal or a paid Order; A bounce; Suppression at any scope; A never on the condition Page; A revert before send. |
| Sequence | <a id="sequence-objection-fire-founder"></a>`objection-fire-founder` | Objection fire founder: 6 steps; it ends on Any reply; A return: a restore, a new Deal or a paid Order; A bounce; Suppression at any scope; The Mandate paused or revoked. |
| Sequence | <a id="sequence-objection-correction"></a>`objection-correction` | Objection correction: 1 steps; it ends on Any reply; Suppression at any scope. |
| Benchmark | <a id="benchmark-objection-churn-return-base"></a>`objection-churn-return-base` | Share of churned subscribers who come back within three months: 5–10 % (A subscription-analytics vendor’s billing data, published by its affiliated analyst; a base rate, not a lift, 2026-09-30). |