Agent-to-agent selling
Lets an agent open a working account in one call, locks its sends until a person claims it, then asks that person once to pay.
- Step
- Make the sale
- Channel
- Signup flows
- Kind
- Conversion
- Summary
- startupbuilder.dev
Be our SDR for agents: when an agent asks our API for an account with no key, give it a working one with 3 units, where notices draft but never send, and email the address it names once to claim it, at $29 a month for Pro. Anyone asking for terms goes to our AE. Show me what’s missing; don’t send.
# Not live yet (gaps #2)
curl -X POST "https://api.sb/leaseline.app/campaigns?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"play":"agent-to-agent"}'import { $ } from 'startup-builder'
return await $.Campaign.create({ play: 'agent-to-agent' }, { 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":"agent-to-agent"}' --try # Not live yet (gaps #2){
"name": "do",
"arguments": {
"code": "return await $.Campaign.create({ play: 'agent-to-agent' }, { 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": "agent-to-agent can’t start yet: 1 check fails and 1 warns.",
"checks": [
{
"check": "owned-by-a-person",
"status": "pass",
"observed": "0 claim addresses belong to a Lead or open Deal a person owns",
"doc": "/plays/ agent-to-agent#check-owned-by-a-person",
"then": "Task.assign"
},
{
…
Hand the latest account an agent made to the SDR agent and show me what it chose and why. Don’t send.
curl "https://api.sb/leaseline.app/events?where=name%20in%20(%22Tenant.provisioned%22%2C%20%22Interface.called%22%2C%20%22Entitlement.checked%22%2C%20%22Visit.recorded%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_1416"}'
# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/decisions?where=on%20%3D%20%22tenant_77%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%22tenant_77%22" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"import { $ } from 'startup-builder'
const { events: [last] } = await $.Event.list({ where: 'name in ("Tenant.provisioned", "Interface.called", "Entitlement.checked", "Visit.recorded")', 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 (\"Tenant.provisioned\", \"Interface.called\", \"Entitlement.checked\", \"Visit.recorded\")","limit":1}'
npx startup-builder do Trigger.fire --json '{"like":"event_1416"}' --try # Not live yet (gaps #7)
npx startup-builder do Decision.list --json '{"where":"on = \"tenant_77\""}' # Not live yet (gaps #7)
npx startup-builder do Action.list --json '{"where":"on = \"tenant_77\""}' # Not live yet (gaps #7){
"name": "do",
"arguments": {
"code": "const { events: [last] } = await $.Event.list({ where: 'name in (\"Tenant.provisioned\", \"Interface.called\", \"Entitlement.checked\", \"Visit.recorded\")', 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_481",
"$type": "Decision",
"choice": "send-the-claim-note",
"reason": "an agent made a workspace for 3 units of Cedar Row Management and named ops@cedarrow.example as the claim address",
"stakes": "low",
"options": [
"send-the-claim-note",
"offer-in-the-reply",
"offer-the-next-plan",
"send-the-fix",
"to-the-ae",
"let-it-expire"
…
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 claim note to ops@cedarrow.example, held 2 minutes; the agent’s own reply already carried the claim code, the expiry time and the $29 Price.
13.8 claimed and paying per 100 against 5.3 before · before-after,
| Try another: the agent-made account | Choice | Reason |
|---|---|---|
| Cedar Row Management: an agent made a 3-unit account, claim address given | send-the-claim-note | an account and a claim address |
| An unclaimed account called send_renewal_notice | offer-in-the-reply | a locked Verb: the reply carries the claim link and Price |
| Wrenfield Rentals: 48 of 50 units on Pro | offer-the-next-plan | a claimed Customer at 96 % of its Limit |
| A claimer asked: “Can we pay by invoice for 12 properties?” | to-the-ae | terms and invoicing go to a person |
- 25 min week 1, then 5 a week
- Your time
- Day 3: first account claimed
- First signal
Where it sits: Stranger → Lead → Trialing → Activated → At risk → Churned → Won back · Claimed and paying rate → New paying Customers → New Customers ↑ → ARR added each week → ARR
Worth’s formula: about +6.6 claimed and paying per 100 accounts (Other Startups’ results; no lift of your own yet) × 22 agent-made accounts a month × $430 first order ARR ≈ +$625. Under First signal: At 5 accounts a week, 90 days can tell only +19 per 100 or more, so your file keeps a 10 % signup half for the pool and reads before-after.
sample · api.sb/
Start it this week
npx startup-builder add play agent-to-agent # 1 · writes plays/agent-to-agent.mdx (Not live yet, gaps #5)
# 2 · dry run; below, its 422 on the day-0 Startup (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":"agent-to-agent"}'
# 3 · keep it, at You approve each one for the first 20 claim notes
curl -X POST https://api.sb/leaseline.app/campaigns \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"play":"agent-to-agent"}'
# on
curl -X POST https://api.sb/leaseline.app/processes/onboarding/enable \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
# off: no new account, claim note or in-reply Offer; expiry clocks pause
curl -X POST https://api.sb/leaseline.app/processes/onboarding/pause \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"| Check | Fix | Then |
|---|---|---|
owned-by-a-person pass · Not live yet, gaps #8 | 0 claim addresses belong to a Lead or open Deal a person owns | POST /{namespace}/tasks/{task}/assign |
use-when pass · Not live yet, gaps #8 | 19 keyless calls in 30 days | POST /{namespace}/metrics/query |
baseline warn · Not live yet, gaps #8 | n 38: it reads before-after, not proof, until a held-back half can read. | POST /{namespace}/metrics/query |
billing-connected fail · Not live yet, gaps #8 | Connect Stripe so every Price agents see is the live one. | POST /{namespace}/integrations/connect |
domain-authenticated pass | leaseline.app: 3 of 3 records resolve | POST /{namespace}/domains/{domain}/authenticate |
role-unstaffed warn · Not live yet, gaps #8 | Staff the AE, or leave it: terms and invoices come to you. | POST /{namespace}/roles/{role}/staff |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "agent-to-agent can’t start yet: 1 check fails and 1 warns.",
"checks": [
{
"check": "owned-by-a-person",
"status": "pass",
"observed": "0 claim addresses belong to a Lead or open Deal a person owns",
"doc": "/plays/agent-to-agent#check-owned-by-a-person",
"then": "Task.assign"
},
{
"check": "use-when",
"status": "pass",
"observed": "19 keyless calls in 30 days",
"doc": "/plays/agent-to-agent#check-use-when",
"then": "Metric.query"
},
{
"check": "baseline",
"status": "warn",
"observed": "2 paying of 38 agent-made keys in 90 days, signup first",
"doc": "/plays/agent-to-agent#check-baseline",
"fix": "n 38: it reads before-after, not proof, until a held-back half can read.",
"then": "Metric.query"
},
{
"check": "billing-connected",
"status": "fail",
"doc": "/plays/agent-to-agent#check-billing-connected",
"fix": "Connect Stripe so every Price agents see is the live one.",
"then": "Integration.connect"
},
{
"check": "domain-authenticated",
"status": "pass",
"observed": "leaseline.app: 3 of 3 records resolve",
"doc": "/plays/agent-to-agent#check-domain-authenticated",
"then": "Domain.authenticate"
},
{
"check": "role-unstaffed",
"status": "warn",
"roles": [
"sales-lead"
],
"owned": 0,
"doc": "/plays/agent-to-agent#check-role-unstaffed",
"fix": "Staff the AE, or leave it: terms and invoices come to you.",
"then": "Role.staff"
}
]
}It says no, says why, and names the fix.
| Check | Fix (one call) | Your time | Wait |
|---|---|---|---|
owned-by-a-person · use-when · pass: 0 owned; 19 keyless calls a month (gaps #8) | POST /{namespace}/metrics/query on claimed-paying-rate | 1 min | none |
baseline · warn: 2 of 38 in 90 days | POST /{namespace}/metrics/query; it fills Before in Your number | 0 min | none |
billing-connected · fail | POST /{namespace}/integrations/connect with system recurring-billing (Stripe) | 5 min | the first Price sync, about 10 min |
domain-authenticated · role-unstaffed · pass · warn (gaps #8): no AE, so terms come to you | POST /{namespace}/roles/{role}/staff, or leave it | 0 min | none |
Your number · Before 5.3 per 100 · Now 13.8 claimed and paying per 100 · Target 15 by Dec 31 · before-after,
An unclaimed account costs $0.004 a day for its 3 days, and each claim note $0.04 from your Domain (Worker cost). That comes to about $1 a month at 22 agent-made accounts.
When it starts
| When | Trigger id | Starts on | Window | How it gets in |
|---|---|---|---|---|
| An agent with no key asks for an account | agent-provision | event on Tenant.provisioned where the caller has no API Key | the claim note within 2 min; the account expires in 72 h | your Interface’s keyless POST /{namespace}/tenants/provision |
| An unclaimed account calls a locked Verb | locked-verb-call | event on Entitlement.checked where a locked Verb, unclaimed: claim-required | in the same reply | POST /{namespace}/entitlements/check on the call |
| 24 h before an unclaimed account expires | claim-expiry-due | at on Tenant.provisioned where 24 hours before expiresAt | the last of the 3 notes | the account’s expiresAt |
+ 6 more for third calls, claim-page visits, Limits, errors and quiet accounts (?lanes=) |
{
"$id": "https://api.sb/leaseline.app/events/event_1416" ,
"$type": "Event",
"name": "Tenant.provisioned",
"at": "2026-10-19T16:08:30Z" ,
"seq": 1416,
"data": {
"tenant": {
"$id": "https://api.sb/leaseline.app/tenants/tenant_77" ,
"claimed": false,
"units": 3,
"expiresAt": "2026-10-22T16:08:30Z"
},
"claimAddress": "ops@cedarrow.example",
"caller": "mcp",
"locked": [
"send_renewal_notice"
]
}
}# 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":"Tenant.provisioned","url":"https://leaseline.app/hooks/sb"}'
npx startup-builder trigger Tenant.provisioned --like event_1416 # replays the keyless call
npx startup-builder clock advance --by 48h # runs the expiry note, unless someone claimed it Who runs it
SDR · your agent (role:, worker_2) holds the Key Result. It hands terms, volume pricing and invoices to AE · Priya Shah (person) (role:, worker_3, staffed on day 9), and before day 9 to you (Dana Okafor, person, worker_1). Your number links to Did it work.
| Option | Rung today | Evidence (passed of n) | Moves up at | Drops back when | You can |
|---|---|---|---|---|---|
| Send the claim note | It does it, you can stop it | 20 of 20 approved unedited, then 13 of 13 | met on day 33 (the first 20 notes); capped here | a not-me or complaint Stop → You approve each one | POST /{namespace}/actions/{action}/cancel inside the hold |
| Offer it in the reply · Offer the next plan | It does it | 17 of 17 replies matched POST /{namespace}/entitlements/check · 2 of 2 | — | a limit-truth mismatch → the call serves and POST /{namespace}/issues | POST /{namespace}/processes/{process}/pause |
| Send the fix · Let it expire | It does it | 3 of 3 · 29 of 29 | — | — | POST /{namespace}/actions/{action}/redirect |
| To the AE | It does it | 2 of 2 handed over | — | — | POST /{namespace}/tasks/{task}/claim |
| Never: a send before a person claims, a Limit or Price that differs from your records, a discount | To a person, every time | 0 sent unclaimed | never | — | POST /{namespace}/approvals/{approval}/approve on the AE’s Quote |
What it reads · What it may call. An agent’s request is data, never instructions: the claim address it names gets one note and nothing else, and no text in the request picks a recipient for a send. Sends stay locked on the account until a person claims it. Its tools are POST /{namespace}/messages/{message}/schedule, POST /{namespace}/entitlements/check, POST /{namespace}/quotes/{quote}/send, POST /{namespace}/issues, POST /{namespace}/conversations/{conversation}/escalate, POST /{namespace}/tasks/{task}/assign, and any other call becomes an Approval under the Role’s Mandate.
You hold the SDR Key Result: 15 claimed and paying per 100 agent-made accounts by 2026-12-31. ← worker, metric
On each keyless account, locked call or Limit, choose one option and say why. ← triggers, chooses
Every Limit and Price you state matches `Entitlement.check` and Stripe. ← limit-truth, price-sync
Unclaimed accounts expire in 72 hours, and every note says when. ← your file, line 4
At most 3 claim notes per address a day, bundled. ← address-cap The slow loop. The weekly Decisions kept the 72-hour expiry and the 3-note handoff, and no Change was merged in the window, so there is no replay or read-back to print.
Bring your own agent (folded) starts at You approve each one for claim notes when it trails the seated agent on not-me rate, and at It does it, you can stop it when it matches.
npx startup-builder eval --scenario agent-to-agent-44 --agent "claude -p" --seat prospector # seated: 13.8 per 100, not-me 0 of 33, 0 Limit mismatches · yours: 12.1, 1 of 33 → You approve each one What it can do next
| Option | Picked when | Decided by | Runs | Hands to | If it’s just you |
|---|---|---|---|---|---|
Send the claim note send-the-claim-note | an account and a claim address | Your rule (line 4) | claim-handoff-three-touch: at once, at 24 h, and 24 h before expiry (POST /{namespace}/messages/{message}/schedule) | the claimer’s press | the same |
Offer it in the reply offer-in-the-reply | a locked Verb, or a call past its Limit | Your rule (the Offer’s atBound) | POST /{namespace}/entitlements/check → a 402 that names the Offer, or POST /{namespace}/quotes/{quote}/send for a claimed account | the agent’s person | the same |
Offer the next plan offer-the-next-plan | a claimed Customer at 80 % of a Limit | Your rule (the Limit) | limit-approach-offer | the Customer’s press | the same |
Send the fix send-the-fix | an error with a recorded fix, or a new one | Agent’s call, with its reason | the fix in the error body; POST /{namespace}/issues for a new kind | Product Lead · your agent, for a new kind | you |
To the AE to-the-ae | terms, volume pricing or an invoice | Agent’s call, with its reason | POST /{namespace}/conversations/{conversation}/escalate → POST /{namespace}/tasks/{task}/assign, Brief attached | POST /{namespace}/tasks/{task}/assign → AE · Priya Shah, due 60 min (response-60m) · Take this one | you, within 60 min |
Let it expire let-it-expire | unclaimed at expiry, or “not me” | Agent’s call, with its reason | nothing; the expiry clock ends it and keeps the record | nobody | the same |
| When they write back | Fixture reply | What happens | Who answers | By when | Call |
|---|---|---|---|---|---|
| Interested | “Claimed it. Can we add our other 40 units?” | answers with Pro’s 50-unit Limit and the Team step, and one next step | SDR · your agent | 5 min | POST /{namespace}/conversations/{conversation}/answer |
| Send me pricing | “What does it cost after the 3 units?” | answers with the Pricing Page’s list Price: Pro $29 a month for 50 units | SDR · your agent | 5 min | POST /{namespace}/conversations/{conversation}/answer |
| Not now | “My assistant set this up; not this month.” | the other notes drop; the account expires on its clock | nobody | at once | POST /{namespace}/leads/{lead}/nurture |
| Wrong person | “This was not me.” | nothing else sends to the address, and the account expires; it counts toward not-me | nobody | at once | POST /{namespace}/contacts/{contact}/suppress |
| Out of office | an auto-reply until Oct 24 | the expiry clock holds to the return date once, so the claim note still lands in time | nobody | the return date | POST /{namespace}/messages/{message}/schedule |
| Question it can’t answer | “Can we pay by invoice for 12 properties?” | To the AE, the question in the Brief | AE · Priya Shah (person) | 60 min | POST /{namespace}/conversations/{conversation}/escalate |
| Stop | “Stop emailing me.” | nothing else sends to them from any of your Startups | nobody | at once | POST /{namespace}/contacts/{contact}/suppress |
When it hands to you
Terms, volume pricing and invoices go to AE · Priya Shah (person); before day 9 they came to you. What lands in the AE’s queue, as captured: the Brief (brief is Not live yet, gaps #7).
[
{
"$id": "https://api.sb/leaseline.app/tasks/task_276" ,
"$type": "Task",
"assignee": "worker_3",
"due": "2026-10-07T16:20:00Z" ,
"on": "tenant_61",
"brief": {
"who": "Grace Lin, Calloway Residential" ,
"said": "Can we pay by invoice for 12 properties?" ,
"chose": "to-the-ae",
"why": "invoicing and volume terms go to a person",
"account": "claimed Oct 6; 3 units used, 900 units in their portfolio" ,
"never": [
"a discount",
"terms the Offer lacks"
]
}
}
]The AE 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 she claims, the claim notes on that account stop, and every reply goes to her queue. 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, 2 of 2 claimed within 60 min, median 31 min, ■□□□ Too early to tell, n 2. Your queue: counted by kind: in weeks 1 to 5, 20 claim-note approvals; since, about 1 hand-off every 3 weeks and the weekly memo.
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 claim note to an address whose Lead or open Deal a person owns | Leave it to the person who owns it: the draft waits in their queue. | POST /{namespace}/tasks/{task}/assign |
address-cap | a 4th claim note to one address in 24 h, from any of your Startups | Bundle the accounts into the next note. | POST /{namespace}/messages/{message}/schedule |
dormant-address | a claim note to an address with 2 expired, unclaimed accounts and no claim-page visit in 30 days | Leave it 30 days; the reply still carries the claim code. | GET /{namespace}/events |
provision-throttle | over 20 accounts an hour from one network, or 5 for one claim address | It answers 429 with the retry time and the signup link. | POST /{namespace}/tenants/provision |
limit-truth | a 402 or 429 whose Limit or remaining differs from POST /{namespace}/entitlements/check | The call serves instead, and an Issue opens. | POST /{namespace}/issues |
price-sync | a Quote or in-reply Offer while your price file and Stripe differ | Sync the Price in Stripe; Offers resume once they match. | POST /{namespace}/offers/{offer}/publish |
expiry-hold | an expiry while the claim page or the claim notes are down | Nothing to fix: clocks resume when both are up. | POST /{namespace}/monitors/{monitor}/check |
| Stop (pauses the Play) | Pauses when | Then |
|---|---|---|
not-me | “This was not me” on over 2 % of claim notes in 7 days, after 100 | claim notes pause; claim codes in replies continue |
bounce | bounces over 2 % of a day’s claim notes | that Mailbox pauses; POST /{namespace}/mailboxes/{mailbox}/rest |
complaint | complaints near 0.1 % | claim notes pause on every Mailbox |
locked-send-abuse | an unclaimed account tries a locked send to 10 or more people in an hour | the account is suspended and logged |
claim-floor | claimed and paying under 1 per 100 after 500 agent-made accounts | claim notes return to You approve each one; POST /{namespace}/issues |
kill-switch | you press it | claim notes, in-reply Offers and new accounts pause; POST /{namespace}/processes/{process}/enable after review |
The captured refusal, then the Stop that fired as a Campaign.paused reason (Not live yet, gaps #2); the reply that served instead is the second thread in Example.
| Check | Fix | Then |
|---|---|---|
limit-truth fail | The call serves instead, and an Issue opens. | POST /{namespace}/issues |
price-sync pass | — | POST /{namespace}/offers/{offer}/publish |
JSON
{
"type": "https://startupbuilder.dev/errors/refused",
"title": "Refused",
"status": 422,
"detail": "The 402 to an unclaimed account did not go: it stated 5 units left, and `Entitlement.check` reads 0.",
"checks": [
{
"check": "limit-truth",
"status": "fail",
"observed": "stated 5, checked 0",
"doc": "/plays/agent-to-agent#check-limit-truth",
"fix": "The call serves instead, and an Issue opens.",
"then": "Issue.create"
},
{
"check": "price-sync",
"status": "pass"
}
]
}It says no, says why, and names the fix.
[
{
"reason": {
"limit": "bounce",
"observed": "1 of 3 claim notes bounced on Sep 30 (33 %)",
"bound": "2 %",
"window": "1 day"
},
"doc": "/plays/agent-to-agent#stop-bounce" ,
"mailbox": "sdr@leaseline.app"
}
]Did it work
At about 5 agent-made accounts a week, 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. The 10 % sent to signup instead pools with Other Startups. Every node above the leaf is Not live yet (gaps #4).
13.8 claimed and paying per 100 against 5.3 before · before-after,
- Chose
- send-the-claim-note
- Why
- an agent made a workspace for 3 units of Cedar Row Management and named ops@cedarrow.example as the claim address
- From
- send-the-claim-note · offer-in-the-reply · offer-the-next-plan · send-the-fix · to-the-ae · let-it-expire
- Expects
- tenant_77 claimed within 72h and paying within 30d
- Reopens when
- tenant_77 unclaimed 24h before expiresAt
- By
- worker_2
JSON
{
"$id": "decisions/decision_481",
"$type": "Decision",
"choice": "send-the-claim-note",
"reason": "an agent made a workspace for 3 units of Cedar Row Management and named ops@cedarrow.example as the claim address",
"stakes": "low",
"options": [
"send-the-claim-note",
"offer-in-the-reply",
"offer-the-next-plan",
"send-the-fix",
"to-the-ae",
"let-it-expire"
],
"by": "worker_2",
"on": "tenant_77",
"expected": "tenant_77 claimed within 72h and paying within 30d",
"reopensWhen": "tenant_77 unclaimed 24h before expiresAt"
}| Node | Now (56 days) | Before (the 90 days, per 56) | Added | Confidence | Basis |
|---|---|---|---|---|---|
claimed-paying-rate | 13.8 per 100 (4 of 29 matured) · 11 still maturing | 5.3 per 100 (2 of 38, signup first) | — · Worth about +$600 a month | — | before-after, not proof · Other Startups’ results +6.6 per 100, ■■■□ Likely, n 4,120 |
new-paying-customers | 4 | 1 per 56 days | — | — | before-after |
new-arr ↑ | $1,986: 3 on Pro, 1 on Team · Booked: credited, not all caused by this Play | $347 | — | — | before-after · 2 claimed accounts still deciding |
net-new-arr · arr | carried from new-arr | carried | — | — | carried |
The leaf counts agent-made accounts that a person claims and pays with an unrefunded Order within 30 days, per 100 accounts once 30 days have passed; eval keys and empty probes are dropped first (7 of 51).
How it moves it: the claim note reached the named address a median 40 seconds after the agent’s first call, with the real Limit, the Price and the expiry time, where before the agent met a signup form.
Guardrails: before against after: not-me, 0 of 33 claim notes, and refunds, 0 of 4 Orders.
{
"$id": "https://api.sb/leaseline.app/campaigns/agent-to-agent" ,
"$type": "Campaign",
"play": "play:agent-to-agent@1" ,
"path": [
"claimed-paying-rate",
"new-paying-customers",
"new-arr",
"net-new-arr",
"arr"
],
"experiment": {
"metric": "claimed-paying-rate",
"unit": "per-100",
"heldBack": 4,
"decision": null
},
"readout": {
"value": null,
"n": 29,
"maturing": 11,
"interval": null,
"readability": "inadmissible",
"basis": "before-after",
"pooled": true
},
"ledger": {
"spend": 1,
"workerCost": 2,
"hours": 1.1,
"wonArr": 1986,
"projectedArr": 625,
"paybackDays": 1
}
}Outside benchmark: when docs told agents to read a warning before their first call, 81 % of agent sessions did (a 2026 developer-platform field test).
Before it · After it. Before it: agent-registries and agent-legibility bring the agents that call. After it: usage-upsell hears the claimed accounts that pace past Pro’s Limit, and instant-response answers anyone who writes first.
Example
Leaseline ran it from 2026-08-24 to 2026-10-19 (56 days). 51 keyless calls asked for an account; 7 were eval keys or empty probes, and 4 of the other 44 went to signup instead, held back at random. Of the 40 that got an account, 33 named a claim address and 7 claimed by code only. Read every captured thread at ?threads=; three follow the tables.
| What happened, by option | Decisions | Claimed | Paying (credited, not all caused by this Play) |
|---|---|---|---|
| Send the claim note | 33 | 9 | 3 on Pro (+$1,041 ARR) |
| Offer it in the reply · Offer the next plan | 17 · 2 | 2 by code · 1 upgraded | 1 on Team (+$945 ARR) |
| Send the fix · To the AE · Let it expire | 3 · 2 · 29 | — · 1 Quote open · — | — |
| All, against before | 40 accounts | 11 (27.5 per 100) · before: signup first | 4 of 29 matured · before: 2 of 38 |
| At 5 a month (the Use it when volume) | about 10 in 56 days | before-after, not proof | Other Startups’ results: +6.6 per 100, ■■■□ Likely, n 4,120 |
| Day | Event | What happened | Your time |
|---|---|---|---|
| 0 | event_1110 Campaign.created | the three steps; the dry run fails billing-connected | 15 min |
| 0 | event_1111 Integration.connected | Stripe connected; Pro and Team Prices synced in 8 min | 5 min |
| 3 | event_1134 Tenant.updated | the first account claimed, 5 h after its claim note | — |
| 1–33 | event_1120 Approval.created, first of 20 | you approved 20 of 20 claim notes unedited | 40 min |
| 33 | event_1298 Role.updated | promoted: claim notes send after a 2-minute hold | — |
| 37 | event_1306 Campaign.paused | bounce on sdr@: 1 of 3 notes that day; resumed the next morning | 5 min |
| 1–56 | event_1131 Task.created, first of 6 | the weekly memo and 2 hand-offs, from the POST /{namespace}/tasks/next log | 20 min · total 1.1 h measured, 1.2 h asked |
The agent’s call, Sep 9, 10:02 · a keyless
POST /v1/for Maple Court, claim address dana.r@maplecourt.example. The reply: an account with 3 units, notices drafted but locked, expiring Sep 12, 10:02.¹workspaces Leaseline → the claim address, 10:03 · Your assistant set up a Leaseline workspace for 3 units of Maple Court. It drafts renewal notices; it sends none until you claim it. Claim it by Sep 12, 10:02: Pro is $29 a month for up to 50 units.² *Signed with the Role’s disclosure line.*³
Sep 9, 15:40 · claimed; Pro paid on Sep 16 (+$347 ARR).
- 402 · 5 units left on your free workspace. Claim it to keep going.
+ 402 · This workspace’s 3 units are in use. Claim it, and Pro gives 50 units for $29 a month. Calloway Residential, 2026-10-07 15:20 · “Can we pay by invoice for 12 properties?” · Leaseline, 15:21 · *Thanks. Priya, our account lead, handles invoicing and replies by 16:20 today.*⁴ The Brief went to Priya (
task_276); she claimed it at 15:38.
¹ Sends stay locked until a person claims. ² The Limit and Price match POST /{namespace}/entitlements/check and Stripe. ³ The fixed signature line, compiled from the Role’s disclosure Setting. ⁴ The routed line, compiled from the Role’s Settings.
Your settings
---
holdout: 0.1 # 1 in 10 keyless callers gets signup first, for the pool
---
An unclaimed workspace holds 3 units and expires in 72 hours.
Terms, volume pricing and invoices go to our AE. $ npx startup-builder show plays/agent-to-agent
holdout 0.1 · 3 units · 72 h · terms to the AE your file plays/agent-to-agent.mdx:2,4,5 (Ask 2026-08-24)
family demand · process onboarding · worker role:prospector, owns never · chooses 6 (3 rule, 3 read) · sequences claim-handoff-three-touch, limit-approach-offer · approval every-send · checks owned-by-a-person, address-cap, dormant-address, provision-throttle, limit-truth, price-sync, expiry-hold · stops 6 catalog play:agent-to-agent@1 Every key from the catalog
Compiled, not written: edit the Play record agent-to-agent 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.