Outbound calling
A disclosed voice agent calls back anyone who asks within 60 seconds and answers your number; a person dials every cold call.
- Channel
- Phone
- Kind
- Outreach
- Summary
- startupbuilder.dev
Be our SDR on the phone: when someone ticks “call me” on the demo form, call back within a minute in a disclosed AI voice, and answer our number at any hour. Over 120 units, book our AE. I dial every cold call myself from your prep. Show me what’s missing; don’t call.
# 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":"outbound-calling"}'import { $ } from 'startup-builder'
return await $.Campaign.create({ play: 'outbound-calling' }, { 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":"outbound-calling"}' --try # Not live yet (gaps #2){
"name": "do",
"arguments": {
"code": "return await $.Campaign.create({ play: 'outbound-calling' }, { 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": "outbound-calling can’t start yet: 1 check fails and 2 warn.",
"checks": [
{
"check": "use-when",
"status": "pass",
"observed": "135 forms a month; 0 offer a callback yet",
"doc": "/plays/ outbound-calling#check-use-when",
"fix": "Add the callback box; about 1 in 3 forms tick it at Other Startups.",
"then": "Metric.query"
},
…
Hand our latest callback request to the SDR agent and show me what it chose and why. Don’t call.
curl "https://api.sb/leaseline.app/events?where=name%20in%20(%22Consent.granted%22%2C%20%22Channel.called%22%2C%20%22Channel.missed%22%2C%20%22Conversation.labeled%22%2C%20%22Lead.qualified%22%2C%20%22Prospect.created%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_1366"}'
# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/decisions?where=on%20%3D%20%22lead_47%22" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/actions?where=on%20%3D%20%22lead_47%22" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"import { $ } from 'startup-builder'
const { events: [last] } = await $.Event.list({ where: 'name in ("Consent.granted", "Channel.called", "Channel.missed", "Conversation.labeled", "Lead.qualified", "Prospect.created")', 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 (\"Consent.granted\", \"Channel.called\", \"Channel.missed\", \"Conversation.labeled\", \"Lead.qualified\", \"Prospect.created\")","limit":1}'
npx startup-builder do Trigger.fire --json '{"like":"event_1366"}' --try # Not live yet (gaps #7)
npx startup-builder do Decision.list --json '{"where":"on = \"lead_47\""}' # Not live yet (gaps #7)
npx startup-builder do Action.list --json '{"where":"on = \"lead_47\""}' # Not live yet (gaps #7){
"name": "do",
"arguments": {
"code": "const { events: [last] } = await $.Event.list({ where: 'name in (\"Consent.granted\", \"Channel.called\", \"Channel.missed\", \"Conversation.labeled\", \"Lead.qualified\", \"Prospect.created\")', 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_547",
"$type": "Decision",
"choice": "call-back-now",
"reason": "the yes names AI-voice calls from Leaseline to the number on the form; 14: 06 Central is inside their calling window; 220 units is over the 120-unit line",
"stakes": "low",
"options": [
"call-back-now",
"a-person-calls",
"answer-the-call",
"cold-list",
"answer-in-writing"
],
…
sample · 4 Not live yetPreviewOn POST /{namespace}/processes/{process}/enableOff POST /{namespace}/processes/{process}/pauseTake this one POST /{namespace}/tasks/{task}/claim
action_3 is the first dial, 41 seconds after the form, opening with the fixed clip. action_4 books on the AE’s calendar only if the caller says yes on the call.
+11.7 per 100 · ■□□□ Too early to tell · n 46 · path → arr
| Try another: the callback | Choice | Reason |
|---|---|---|
| Willow & Main: ticked “call me”, 220 units, 14:06 their time | call-back-now | a yes to AI-voice calls, inside their calling window |
| Birchway Properties: ticked “call me”, 600 units, on your top-25 List | a-person-calls | a top-tier company gets a person on every rung |
| A call to +1 512 555 0100 at 21:40: “Do you handle HOA renewals?” | answer-the-call | an inbound call, answered at any hour from your own pages |
| Ridgeline Rentals: a listed office number, and a leasing job reposted 64 days | cold-list | a local company with a live reason: on Dana’s list to dial |
| Kestrel Rentals: “Can someone call me?” by email, no box ticked | answer-in-writing | no yes to an AI-voice call: it answers by email and offers a person |
- 2 h week 1, then 1 h 40 min a week
- Your time
- Day 2: first Meeting booked on a callback
- First signal
Where it sits: Stranger → Lead → Trialing → Activated → At risk → Churned → Won back · Callback Meeting rate → Deals and trials opened → New Customers ↑ → ARR added each week → ARR
Worth’s formula: about +7.5 Meetings per 100 callbacks (Other Startups’ results; your own read is too early) × 47 a month × 0.80 Meetings to opens × 0.30 opens to paid × $800 first order ARR ≈ +$846. Under First signal: With 1 callback in 10 held back at 47 a month, about 30 weeks to tell a +7.5 lift; 90 days can tell only +22 or more.
sample · api.sb/
Start it this week
npx startup-builder add play outbound-calling # 1 · writes plays/outbound-calling.mdx (Not live yet, gaps #5)
# 2 · dry run; below, its 422 on the day-0 Startup (--env test, clock at 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":"outbound-calling"}'
# 3 · keep it: people make the first 50 calls from the brief
curl -X POST https://api.sb/leaseline.app/campaigns \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"play":"outbound-calling"}'
# on
curl -X POST https://api.sb/leaseline.app/processes/call-routing/enable \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
# off: every call goes to the person line
curl -X POST https://api.sb/leaseline.app/processes/call-routing/pause \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"| Check | Fix | Then |
|---|---|---|
use-when pass · Not live yet, gaps #8 | Add the callback box; about 1 in 3 forms tick it at Other Startups. | POST /{namespace}/metrics/query |
baseline warn · Not live yet, gaps #8 | n 41: your half becomes the baseline as it runs. | POST /{namespace}/metrics/query |
inbound-source fail · Not live yet, gaps #8 | Connect your number and add the callback box to the demo form. | POST /{namespace}/integrations/connect |
owned-by-a-person pass · Not live yet, gaps #8 | Nothing to do: their callbacks come to your queue instead. | POST /{namespace}/tasks/{task}/assign |
customer-collision pass · Not live yet, gaps #1 | Nothing to do: a Customer’s call goes to their CSM. | POST /{namespace}/leads/{lead}/assign |
claim pass | Nothing to do: the agent says only what an Offer term grants. | POST /{namespace}/conversations/{conversation}/answer |
role-unstaffed warn · Not live yet, gaps #8 | Staff the AE, or leave it: booked calls and transfers come to you. | POST /{namespace}/roles/{role}/staff |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "outbound-calling can’t start yet: 1 check fails and 2 warn.",
"checks": [
{
"check": "use-when",
"status": "pass",
"observed": "135 forms a month; 0 offer a callback yet",
"doc": "/plays/outbound-calling#check-use-when",
"fix": "Add the callback box; about 1 in 3 forms tick it at Other Startups.",
"then": "Metric.query"
},
{
"check": "baseline",
"status": "warn",
"observed": "callbacks by a person: 9 Meetings from 41, n 41 in 90 days",
"doc": "/plays/outbound-calling#check-baseline",
"fix": "n 41: your half becomes the baseline as it runs.",
"then": "Metric.query"
},
{
"check": "inbound-source",
"status": "fail",
"doc": "/plays/outbound-calling#check-inbound-source",
"fix": "Connect your number and add the callback box to the demo form.",
"then": "Integration.connect"
},
{
"check": "owned-by-a-person",
"status": "pass",
"observed": "31 open Leads have a person owner, you",
"doc": "/plays/outbound-calling#check-owned-by-a-person",
"fix": "Nothing to do: their callbacks come to your queue instead.",
"then": "Task.assign"
},
{
"check": "customer-collision",
"status": "pass",
"observed": "6 numbers belong to Customers",
"doc": "/plays/outbound-calling#check-customer-collision",
"fix": "Nothing to do: a Customer’s call goes to their CSM.",
"then": "Lead.assign"
},
{
"check": "claim",
"status": "pass",
"observed": "the claims source holds 24 approved answers",
"doc": "/plays/outbound-calling#check-claim",
"fix": "Nothing to do: the agent says only what an Offer term grants.",
"then": "Conversation.answer"
},
{
"check": "role-unstaffed",
"status": "warn",
"roles": [
"sales-lead"
],
"owned": 31,
"doc": "/plays/outbound-calling#check-role-unstaffed",
"fix": "Staff the AE, or leave it: booked calls and transfers come to you.",
"then": "Role.staff"
}
]
}It says no, says why, and names the fix.
| Check | Fix (one call) | Your time | Wait |
|---|---|---|---|
use-when · baseline · pass: 135 forms a month; warn: n 41 person callbacks (gaps #8) | POST /{namespace}/metrics/query on callback-meeting-rate, which fills Before in Your number | 1 min | none |
inbound-source · fail (gaps #8) | POST /{namespace}/integrations/connect the number, then PATCH /{namespace}/forms/{id} with the callback box | 15 min | the number’s registration, often 1 to 3 days |
owned-by-a-person · customer-collision · claim · pass: 31 owned by you; 6 Customer numbers; 24 answers (gaps #1, #8) | nothing; each runs again before every dial | 0 min | none |
role-unstaffed · warn (gaps #8): 31 of your open Leads have a person owner, you | POST /{namespace}/roles/{role}/staff, or leave it: bookings and transfers come to you | 0 min | none |
Your number · Before 22.0 · Now 31.7 per 100 · Target 30 by Dec 31 · ■□□□ Too early to tell · n 46 matured · Other Startups +7.5,
One local number and voice minutes for about 100 callbacks cost $100 a month; each callback averages 3 minutes, about $1. Contact data costs $0.06 a lookup (POST /{namespace}/contacts/{contact}/enrich). A cold call costs only your time. That comes to $100 a month up to 100 callbacks.
When it starts
| When | Trigger id | Starts on | Window | How it gets in |
|---|---|---|---|---|
| Someone ticks “call me” on your form, naming AI-voice calls | voice-consent-form | event on Consent.granted where purpose = 'ai-voice-call' | first ring within 60 s; ignored after 1d | your form posts to POST /{namespace}/consents, then POST /{namespace}/leads |
| Someone calls your number | inbound-call | event on Channel.called where direction = 'inbound' | answered on the first ring | POST /{namespace}/integrations/connect with your number |
| A call to you ends unanswered, or they ask to be called back | dropped-call | event on Channel.missed where answered = false or callbackAsked = true | 1d | the same number |
+ 3 more: a written ask for a call, a strong-fit Lead with no yes to a call, and a cold-call reason (?lanes=) |
{
"$id": "https://api.sb/leaseline.app/events/event_1366" ,
"$type": "Event",
"name": "Consent.granted",
"at": "2026-10-19T19:06:12Z" ,
"seq": 1366,
"data": {
"consent": {
"$id": "https://api.sb/leaseline.app/consents/consent_201" ,
"purpose": "ai-voice-call",
"startup": "leaseline.app",
"number": "+1 512 555 0148"
},
"lead": {
"$id": "https://api.sb/leaseline.app/leads/lead_47" ,
"company": "Willow & Main",
"units": 220
}
}
}# each delivery is signed with the Watch’s secret
curl -X POST https://api.sb/leaseline.app/watches \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"selector":"Consent.granted","url":"https://leaseline.app/hooks/sb"}'
npx startup-builder trigger Consent.granted --like event_1366 # replays the delivery in test; the dial is simulated
npx startup-builder clock advance --by 1h # runs the third dial if the first two went unansweredWho runs it
SDR · your agent (role:, worker_2) holds the Key Result and speaks in a designed voice that sounds like no real person. It hands booked calls and transfers to AE · Priya Shah (person) (role:, worker_3, staffed on day 9), and top-tier callbacks and every cold call to you (Dana Okafor, person, worker_1): a person dials every cold number. Your number links to Did it work.
| Option | Rung today | Evidence (passed of n) | Moves up at | Drops back when | You can |
|---|---|---|---|---|---|
| Call back now | It does it, you can stop it | 52 calls; 50 person calls from its brief first | started on day 21, after the 50; spot-check after 500 calls with no Stop in 30 days | any voice Stop (clip, ai-identity, claims) → You hear each transcript first | POST /{namespace}/actions/{action}/cancel before the dial · POST /{namespace}/conversations/{conversation}/escalate on the call |
| Answer the call · Answer in writing | It does it, you can stop it | 38 calls · 9 of 9 | the same | the same | the same |
| A person calls back · Put it on your cold list | A person, every time | 6 held back, 5 top tier · 34 dials from 12 briefs | never: a person dials | — | POST /{namespace}/tasks/{task}/claim · POST /{namespace}/tasks/{task}/return |
| Change the calling rule | You approve each one | 1 merged of 1 | never | — | POST /{namespace}/changes/{change}/merge · POST /{namespace}/changes/{change}/revert |
| Never: a call with no yes to AI voice, a cloned voice, a discount | Refused, every time | 3 strong-fit Leads with no yes: written answers | never | — | none: the Check holds it |
What it reads · What it may call. It reads the form, the yes and its scope, the claims source and the calendar. A pricing-page visit may order the cold list and is never spoken on a call; nothing a caller says picks a number to dial. Its tools are POST /{namespace}/conversations, POST /{namespace}/meetings/book, POST /{namespace}/tasks/{task}/assign, POST /{namespace}/conversations/{conversation}/answer, POST /{namespace}/messages/{message}/schedule, and any other call becomes an Approval under the Role’s Mandate.
You hold the SDR Key Result: 30 Meetings held per 100 callbacks by 2026-12-31. ← worker, metric
On each callback ask or call, choose one option and say why. ← triggers, chooses
Play the opening clip first and in full; say you are an agent if asked. ← copyRules 1, 3
One ask per call; the list Price only as the Pricing Page shows it. ← copyRules 6, 9
Over 120 units, book the AE. ← your file, line 5
“Person” at any point reaches a person, live or booked. ← copyRules 12 The slow loop, as a Change. decision_366 (2026-09-28, weekly) read 11 callbacks that rang once and went to voicemail, with 1 Meeting, and 9 that rang twice in a row, with 4. change_27 made the double ring the first attempt for every callback (your file, line 6), and Dana merged it with POST /{namespace}/changes/{change}/merge. Replayed on past callbacks, POST /{namespace}/changes/{change}/review put the agent half at 34 per 100; read back at +14d it is 33.3, n 18, ■□□□ Too early to tell (Example, day 50).
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; POST /{namespace}/roles/{role}/staff seats it.
npx startup-builder eval --scenario outbound-calling-58 --agent "claude -p" --seat prospector # seated: 31.7 per 100, 0 clip misses, $1.02 a call · yours: 28.1, 0 misses → You approve each one What it can do next
| Option | Picked when | Decided by | Runs | Hands to | If it’s just you |
|---|---|---|---|---|---|
Call back now call-back-now | a yes to AI-voice calls, inside their calling window | Your rule (line 5) | callback-attempts: double ring at once, then 1 h, then 1 day → POST /{namespace}/conversations → on yes POST /{namespace}/meetings/book | over 120 units: POST /{namespace}/tasks/{task}/assign → AE · Priya Shah, due 60 min, Brief attached · Take this one | you, with the Brief, within 60 min |
A person calls back a-person-calls | a top-tier company, a strong fit with no yes to AI voice, or held back | Your rule (line 7) | human-callback: a person calls from the brief within 5 min in staffed hours, twice at most → POST /{namespace}/tasks/{task}/assign | you · Take this one | you |
Answer the call answer-the-call | someone calls your number | Agent’s call, with its reason | route-call → POST /{namespace}/conversations/{conversation}/answer from the claims source | a transfer: AE · Priya Shah, live, or booked within 60 min | you |
Put it on your cold list cold-list | a local company with a live reason and a listed number | Agent’s call, with its reason | cold-call-touches: the brief and talk track → POST /{namespace}/tasks/{task}/assign; a person dials 3 times in 7 days | you dial · Take this one | you |
Answer in writing answer-in-writing | outside the calling window, or a written ask with no yes to a call | Agent’s call, with its reason | answer-inbound → POST /{namespace}/messages/{message}/schedule, offering a person’s call | nobody | the same |
| When they write back | Fixture reply | What happens | Who answers | By when | Call |
|---|---|---|---|---|---|
| Interested | “Yes, Tuesday at 3 works.” (on the call) | books it on the AE’s calendar while they listen; the Brief goes to the AE (When it hands to you) | AE · Priya Shah (person) | 60 min | POST /{namespace}/meetings/book |
| Send me pricing | “Just email me the pricing.” | the call ends; the Pricing Page’s list Price arrives by email with one next step | SDR · your agent | 5 min | POST /{namespace}/messages/{message}/schedule |
| Not now | “Call me after the holidays.” | no more dials; it moves to Nurture with January noted | SDR · your agent | at once | POST /{namespace}/leads/{lead}/nurture |
| Wrong person | “You want our ops manager, Sam.” | it asks for Sam’s email to send the summary; it never dials a number it heard on the call | SDR · your agent | 1 business day | POST /{namespace}/conversations/{conversation}/answer |
| Out of office | no answer twice; voicemail on the second ring | one fixed voicemail clip, then the 1 h and 1-day attempts | nobody | 1 day | POST /{namespace}/messages/{message}/schedule |
| Question it can’t answer | “Can you migrate our 6 years of lease history?” | a live transfer to the AE, or a booked call with the question in the Brief | AE · Priya Shah (person) | 60 min | POST /{namespace}/conversations/{conversation}/escalate |
| Stop | “Don’t call this number again.” | the number goes on your do-not-call list; nothing dials it on any lane | nobody | at once | POST /{namespace}/contacts/{contact}/suppress |
When it hands to you
Booked calls and transfers go to AE · Priya Shah (person); top-tier callbacks and every cold call come to you, and before day 9 every hand-off did. One Brief per Role, as captured (brief is Not live yet, gaps #7).
[
{
"$id": "https://api.sb/leaseline.app/tasks/task_455" ,
"$type": "Task",
"assignee": "worker_3",
"due": "2026-10-19T20:07:00Z" ,
"on": "lead_47",
"brief": {
"who": "Jordan, Willow & Main (220 units)" ,
"said": "Renewals across 3 buildings, and owners want monthly statements." ,
"chose": "call-back-now",
"why": "a yes to AI voice; over the 120-unit line" ,
"booked": "Tue 15:00 Central" ,
"call": "transcript and recording, 3 min 40 s" ,
"never": [
"a price beyond the list",
"a discount",
"terms"
]
}
},
{
"$id": "https://api.sb/leaseline.app/tasks/task_418" ,
"$type": "Task",
"assignee": "worker_1",
"due": "2026-10-06T14:00:00Z" ,
"on": "prospect_44",
"brief": {
"who": "Ridgeline Rentals, office line +1 512 555 0172" ,
"reason": "leasing coordinator job reposted after 64 days",
"chose": "cold-list",
"why": "a local company with a live reason",
"track": "name, Startup, reason in the first 15 seconds; one ask: a 20-minute call"
}
}
]Each receiver answers with POST /{namespace}/tasks/{task}/claim (Take this one), POST /{namespace}/tasks/{task}/return with a reason, or POST /{namespace}/actions/{action}/redirect, each a one-call window. Once a person claims, the agent dials that Lead no more and every call or reply goes to the claimer (claimedByPerson, Not live yet, gaps #7). A Task nobody claims by its due time goes up a Role through POST /{namespace}/tasks/{task}/escalate (AE to you), and person-promise fires.
On time: AE · Priya Shah, 9 of 9 booked calls claimed within 60 min, median 12 min, n 9, ■□□□ Too early to tell beside Other Startups’ 95 % on time; you, 12 cold briefs dialed within their day, n 12. Your queue: counted by kind: in weeks 1 to 3, 50 person callbacks from the brief; after day 21, about 2 transfers, 3 cold briefs and 5 transcripts to spot-check a week.
The fixed line: POST /{namespace}/deals/{deal}/assign sets owner to the receiving person, the agent is never a Deal’s owner, and Meetings land on the owner’s calendar under the owner’s name.
When it won’t send
| Check (refuses one send) | Refuses | Fix | Then |
|---|---|---|---|
owned-by-a-person (rule j; Not live yet, gaps #8) | a dial to a Lead or open Deal a person owns, or a Conversation a person claimed | Leave it to the person who owns it: the draft waits in their queue. | POST /{namespace}/tasks/{task}/assign |
voice-consent (the record’s Consent gate) | any AI-voice dial with no recorded yes naming AI-voice calls, the Startup and the number | Call back in person, or answer in writing; no override exists. | POST /{namespace}/tasks/{task}/assign |
do-not-call (the record’s Do-not-call gate) | a number on Suppression, your list, or a Market list older than 31 days | Leave the number off; refresh the Market list if it is over 31 days old. | GET /{namespace}/suppressions |
quiet-hours (the record’s Quiet hours gate) | a dial outside the calling window in the called party’s local time | Wait for their window; the narrower of two time zones wins. | POST /{namespace}/messages/{message}/schedule |
customer-collision (gaps #1) | a new-lead callback to a number on an active Subscription | Send it to their CSM instead: the Contact leaves the List. | POST /{namespace}/leads/{lead}/assign |
claim (hub, on POST /{namespace}/conversations/{conversation}/answer) | a term with no accepted terms Version, or money back no Action keeps | Cut the line, or cite the Offer term that grants it. | POST /{namespace}/conversations/{conversation}/answer |
| Stop (pauses the Play) | Pauses when | Then |
|---|---|---|
clip | one call log lacks the opening clip’s hash | AI calls pause on every number until you review it |
ai-identity | one transcript says or implies the agent is a person | every voice Tactic pauses; the Marketing Lead is told |
claims | one transcript sentence states a price, policy or promise outside the claims source | callbacks drop to You hear each transcript first |
complaint | one “I never asked for this call” opens an audit of that yes; two in 200 calls | AI callbacks pause on that form |
spam-label | a carrier labels the number as spam | outbound calls stop; an Issue opens; calls resume on a clean read |
early-hang-up | over 40 % of answered callbacks end within 10 s of the clip, after 50 | the Tactic pauses for review |
meeting-floor | the agent half books fewer Meetings per 100 than people, after 100 calls each | callbacks return to people, with the agent’s brief |
kill-switch | you press it | POST /{namespace}/processes/{process}/pause on call-routing; every call goes to the person line |
cold-refusal | do-not-call asks over 5 % of cold connects, after 100; one dial to a suppressed number | that cold list pauses; a suppressed dial trips kill-switch |
cold-floor | zero Meetings after 100 cold connects | cold calls end for that List until the Offer or the reason changes |
The captured refusal, then the Stop that fired as a Campaign.paused reason (Not live yet, gaps #2); the rewrite that sent is the Harbor Row follow-up in Example.
| Check | Fix | Then |
|---|---|---|
claim fail | Cut the line, or cite the Offer term that grants it. | POST /{namespace}/conversations/{conversation}/answer |
owned-by-a-person pass · Not live yet, gaps #8 | — | POST /{namespace}/tasks/{task}/assign |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "The follow-up email to Harbor Row did not send: it promises a free month no Offer term grants.",
"checks": [
{
"check": "claim",
"status": "fail",
"doc": "/plays/outbound-calling#check-claim",
"fix": "Cut the line, or cite the Offer term that grants it.",
"then": "Conversation.answer"
},
{
"check": "owned-by-a-person",
"status": "pass"
}
]
}It says no, says why, and names the fix.
[
{
"reason": {
"limit": "spam-label",
"observed": "“Spam risk” on 1 carrier",
"bound": "0",
"window": "the daily number read, Oct 3"
},
"doc": "/plays/outbound-calling#stop-spam-label" ,
"task": "task_431"
}
]Did it work
One callback in 10, drawn when the form lands, is held back at random and gets a person’s callback in staffed hours; both halves are read from that assignment (rules m–o), counting from day 21, when the agent started dialing. Each figure’s call is POST /{namespace}/metrics/query by node and by arm; the parent field and every node above the leaf are Not live yet (gaps #4).
+11.7 per 100 · ■□□□ Too early to tell · n 46 · path → arr
- Chose
- call-back-now
- Why
- the yes names AI-voice calls from Leaseline to the number on the form; 14:06 Central is inside their calling window; 220 units is over the 120-unit line
- From
- call-back-now · a-person-calls · answer-the-call · cold-list · answer-in-writing
- Expects
- a Meeting held by day 14
- Reopens when
- no Meeting.booked for lead_47 after the 4th dial, or within 14d
- By
- worker_2
JSON
{
"$id": "decisions/decision_547",
"$type": "Decision",
"choice": "call-back-now",
"reason": "the yes names AI-voice calls from Leaseline to the number on the form; 14:06 Central is inside their calling window; 220 units is over the 120-unit line",
"stakes": "low",
"options": [
"call-back-now",
"a-person-calls",
"answer-the-call",
"cold-list",
"answer-in-writing"
],
"by": "worker_2",
"on": "lead_47",
"expected": "a Meeting held by day 14",
"reopensWhen": "no Meeting.booked for lead_47 after the 4th dial, or within 14d"
}| Node | Agent half | Your half (a person calls back) | Added | Confidence | Basis |
|---|---|---|---|---|---|
callback-meeting-rate | 31.7 per 100 (13 of 41) | 20.0 (1 of 5) | +11.7 [−25.4, +48.8] | ■□□□ Too early to tell | held back at random, 1 in 10 · n 46 · 12 still maturing · Other Startups +7.5, ■■■□ Likely, n 6,900 |
opened-deals-and-trials | 26.8 per 100 (11 of 41) | 20.0 (1 of 5) | +6.8 [−29.9, +43.5] · Not carrying up yet | ■□□□ Too early to tell | the same assignment |
new-arr ↑ | $3,087, 3 Customers · Booked: credited, not all caused by this Play | $347, 1 Customer | +$2,740 [−$1,600, +$7,000] · Worth about +$850 a month | ■□□□ Too early to tell (3 vs 1 Customers) | the same · 5 trials still deciding |
net-new-arr · arr | +$3,087 | +$347 | +$2,740, carried: it moves no other bridge term | ■□□□ Too early to tell | carried from new-arr |
The leaf counts Meetings held within 14 days per 100 callbacks placed, wrong numbers kept in the denominator (rule m), to 100 matured calls in each half; at 41 and 5 the Experiment runs on.
How it moves it: a median first ring 41 seconds after the form for the agent half, against 2 h 50 min for a person’s callback (time-to-first-call on callbacks).
Guardrails: treated against held back: Deals or trials opened per Meeting held, 0.85 against 1.00 (meeting-to-open-rate, Not live yet), and “I never asked for this call”, 0 of 52 against a Stop at 2 in 200.
{
"$id": "https://api.sb/leaseline.app/campaigns/outbound-calling" ,
"$type": "Campaign",
"play": "play:outbound-calling@1" ,
"path": [
"callback-meeting-rate",
"opened-deals-and-trials",
"new-arr",
"net-new-arr",
"arr"
],
"experiment": {
"metric": "callback-meeting-rate",
"unit": "per-100",
"heldBack": 6,
"decision": null
},
"readout": {
"value": 11.7,
"n": 46,
"maturing": 12,
"interval": [
-25.4,
48.8
],
"readability": "inadmissible",
"basis": "held-back",
"pooled": false
},
"ledger": {
"spend": 186,
"workerCost": 6,
"hours": 9.4,
"pipelineArr": 4800,
"wonArr": 3087,
"projectedArr": 846,
"paybackDays": 22
}
}Outside benchmark: 4 % of business demo requests got a phone call at all, after a median 34.5 hours (a vendor-run speed-to-lead field experiment, 2026-09-22).
Before it · After it. Before it: paid-search, paid-social, directory-listings, free-tools and lead-nurture end on a form, a number or a call-me reply. After it: booked calls reach the AE as instant-response hand-offs do, partial fits enter lead-nurture, and every call outcome maps to a reply kind.
Example
Leaseline ran it from 2026-08-24 to 2026-10-19 (56 days). 88 forms ticked “call me”: people made the first 50 callbacks from the brief, then from day 21, 58 were assigned at random, 52 to the agent and 6 held back; 46 have matured. The agent also answered 38 calls to the number. Read every captured thread at ?threads=; three follow the tables.
| What happened, by option | Calls | Meetings held | Won (credited, not all caused by this Play) |
|---|---|---|---|
| Call back now | 52 (148 dials) | 16, 13 of the 41 matured | 1 on the $2,400 plan (+$2,393 ARR), 1 on Pro (+$347 ARR) |
| Answer the call | 38 | 4 | 1 on Pro (+$347 ARR) |
| A person calls back · Put it on your cold list · Answer in writing | 11 · 34 dials from 12 briefs · 9 | 2 · 2 · 1 | 1 on Pro (+$347 ARR) · 0 · 0 |
| Agent half · held back | 52 · 6 | 16 · 1 | $3,087 · $347 |
| At 20 callback asks a month (the Use it when volume) | about 37 in 56 days | held back 1 in 10: Too early to tell at 90 days | Other Startups’ results: +7.5 per 100, ■■■□ Likely, n 6,900 |
| Day | Event | What happened | Your time |
|---|---|---|---|
| 0 | event_118 Campaign.created | the three steps; the dry run fails inbound-source | 25 min |
| 2 | event_131 Integration.connected | the number registered; the callback box went live on the demo form | 15 min |
| 2 | event_140 Meeting.booked | the first Meeting on a person’s callback from the brief | — |
| 9 | event_301 Role.staffed | AE · Priya Shah (person) takes role: | — |
| 0–20 | event_133 Task.claimed, first of 50 | you and Priya made the first 50 callbacks from the agent’s brief | 4 h 10 min |
| 21 | event_455 Role.updated | the voice agent starts dialing consented callbacks | — |
| 36 · 50 | event_655 Change.merged · event_861 Readout.created | double ring first: replayed at 34 per 100, read back at 33.3, n 18 | 5 min |
| 40 | event_702 Campaign.paused | spam-label on 1 carrier; calls resumed on a clean read 19 h later | 20 min |
| 21–56 | event_480 Conversation.created, first of 90 | you spot-checked transcripts and dialed 34 cold calls; weekly reviews, from the POST /{namespace}/tasks/next log | 5 h · total 9.4 h measured, 9 h asked |
Willow & Main, form at 14:05:31 Central · ticked “call me”, 220 units.
Leaseline (voice agent), 14:06:12 · *The fixed opening clip, naming Leaseline, the AI voice and the recording.*¹ “You asked about renewals across your buildings. Is now still a good time?”² Jordan named 3 buildings and monthly owner statements; the agent offered the AE’s times³ and booked Tue 15:00 with
POST /{namespace}/meetings/bookat 14:09:52. The Brief reached Priya at 14:10.
- As promised on the call, your first month is free.
+ As we said on the call, Pro is $29 a month, and the trial is free for 14 days. A call to +1 512 555 0100, 2026-10-08 10:12 · “Can you migrate our 6 years of lease history?” · Leaseline (voice agent) · *Priya, our account lead, calls you back by 11:12.*⁴ The Brief went to Priya (
task_437); she claimed it at 10:20 and called back at 10:48.
¹ The fixed opening clip, compiled from the Role’s Settings and never typed. ² It quotes the form; nothing they did not give is spoken. ³ One ask, with times the calendar shows. ⁴ The routed line, compiled from the Role’s Settings.
Your settings
---
holdout: 0.1 # 1 callback in 10 gets a person instead
number: +1 512 555 0100
---
Over 120 units, book our AE.
Every callback starts with the double ring.
Our top 25 get a person on every rung, and I dial every cold call. $ npx startup-builder show plays/outbound-calling
holdout 0.1 · number +1 512 555 0100 · size line 120 units · double ring · top 25 to a person your file plays/outbound-calling.mdx:2,3,5,6,7 (Ask 2026-08-24; line 6 by change_27, 2026-09-28)
family outreach · process call-routing · worker role:prospector, owns never · chooses 5 (2 rule, 3 read) · sequences callback-attempts, human-callback, cold-call-touches · approval every-send · checks owned-by-a-person, voice-consent, do-not-call, quiet-hours, customer-collision, claim · stops 10 catalog play:outbound-calling@1 (Not live yet, gaps #5) Every key from the catalog
Compiled, not written: edit the Play record outbound-calling 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.