URL: /plays/agent-to-agent · Id: `play:agent-to-agent`

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

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.

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

```text title="Run one · Ask"
Hand the latest account an agent made to the SDR 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 ("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 }
```

```json title="Run one · Response · sample"
{
  "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"
    ],
    "by": "worker_2",
    "on": "tenant_77",
    "expected": "tenant_77 claimed within 72h and paying within 30d",
    "reopensWhen": "tenant_77 unclaimed 24h before expiresAt"
  },
  "actions": [
    {
      "$id": "https://api.sb/leaseline.app/actions/action_1",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_2",
      "verb": "Tenant.provision",
      "checks": [
        {
          "check": "provision-throttle",
          "status": "pass"
        }
      ]
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_2",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_2",
      "verb": "Entitlement.check",
      "checks": [
        {
          "check": "limit-truth",
          "status": "pass"
        }
      ]
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_3",
      "$type": "Action",
      "status": "pending",
      "reason": "awaitingTime",
      "mode": "try",
      "by": "worker_2",
      "verb": "Message.schedule",
      "checks": [
        {
          "check": "owned-by-a-person",
          "status": "pass"
        },
        {
          "check": "customer-collision",
          "status": "pass"
        },
        {
          "check": "address-cap",
          "status": "pass"
        },
        {
          "check": "dormant-address",
          "status": "pass"
        }
      ]
    }
  ]
}
```

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

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

| Worth | Your time | Spend | First signal | Use it when | Instead |
| --- | --- | --- | --- | --- | --- |
| ≈ +$600 ARR a month at 22 | 25 min week 1, then 5 a week | $0.05 per agent-made account, ~$1 a month | Day 3: first account claimed | an Interface agents call, 5+ keyless calls a month | Under 5 a month: `agent-registries` · no Price in Stripe: publish the Offer (<Op verb="Offer.publish" />) |

**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/leaseline.app · 41 Customers at 2026-10-19 · on Tenant.provisioned · claim note 40 s after the first call · 4 keys Not live yet`

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


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

| Check | Fix (one call) | Your time | Wait |
| --- | --- | --- | --- |
| <a id="check-owned-by-a-person"></a>`owned-by-a-person` · <a id="check-use-when"></a>`use-when` · pass: 0 owned; 19 keyless calls a month (gaps #8) | <Op verb="Metric.query" /> on `claimed-paying-rate` | 1 min | none |
| <a id="check-baseline"></a>`baseline` · warn: 2 of 38 in 90 days | <Op verb="Metric.query" />; it fills Before in Your number | 0 min | none |
| <a id="check-billing-connected"></a>`billing-connected` · fail | <Op verb="Integration.connect" /> with `system` recurring-billing (Stripe) | 5 min | the first Price sync, about 10 min |
| <a id="check-domain-authenticated"></a>`domain-authenticated` · <a id="check-role-unstaffed"></a>`role-unstaffed` · pass · warn (gaps #8): no AE, so terms come to you | <Op verb="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, not proof · n 29 matured`

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

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

```bash title="Reproduce it in test"
# 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:prospector`, `worker_2`) holds the Key Result. It hands terms, volume pricing and invoices to **AE · Priya Shah (person)** (`role:sales-lead`, `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 | <Op verb="Action.cancel" /> inside the hold |
| Offer it in the reply · Offer the next plan | It does it | 17 of 17 replies matched <Op verb="Entitlement.check" /> · 2 of 2 | — | a `limit-truth` mismatch → the call serves and <Op verb="Issue.create" /> | <Op verb="Process.pause" /> |
| Send the fix · Let it expire | It does it | 3 of 3 · 29 of 29 | — | — | <Op verb="Action.redirect" /> |
| To the AE | It does it | 2 of 2 handed over | — | — | <Op verb="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 | — | <Op verb="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 <Op verb="Message.schedule" />, <Op verb="Entitlement.check" />, <Op verb="Quote.send" />, <Op verb="Issue.create" />, <Op verb="Conversation.escalate" />, <Op verb="Task.assign" />, and any other call becomes an Approval under the Role’s Mandate.

```md title="The brief (folded): compiled Agent-register Markdown · play:agent-to-agent@1 · each line names its record key"
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.

```bash
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 (<Op verb="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) | <Op verb="Entitlement.check" /> → a 402 that names the Offer, or <Op verb="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; <Op verb="Issue.create" /> 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 | <Op verb="Conversation.escalate" /> → <Op verb="Task.assign" />, Brief attached | <Op verb="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 | <Op verb="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 | <Op verb="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 | <Op verb="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 | <Op verb="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 | <Op verb="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 | <Op verb="Conversation.escalate" /> |
| Stop | “Stop emailing me.” | nothing else sends to them from any of your Startups | nobody | at once | <Op verb="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).

```json title="What lands in each queue · sample"
[
  {
    "$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 <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 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 <Op verb="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:** <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; 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. | <Op verb="Task.assign" /> |
| <a id="check-address-cap"></a>`address-cap` | a 4th claim note to one address in 24 h, from any of your Startups | Bundle the accounts into the next note. | <Op verb="Message.schedule" /> |
| <a id="check-dormant-address"></a>`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. | <Op verb="Event.list" /> |
| <a id="check-provision-throttle"></a>`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. | <Op verb="Tenant.provision" /> |
| <a id="check-limit-truth"></a>`limit-truth` | a 402 or 429 whose Limit or remaining differs from <Op verb="Entitlement.check" /> | The call serves instead, and an Issue opens. | <Op verb="Issue.create" /> |
| <a id="check-price-sync"></a>`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. | <Op verb="Offer.publish" /> |
| <a id="check-expiry-hold"></a>`expiry-hold` | an expiry while the claim page or the claim notes are down | Nothing to fix: clocks resume when both are up. | <Op verb="Monitor.check" /> |

| Stop (pauses the Play) | Pauses when | Then |
| --- | --- | --- |
| <a id="stop-not-me"></a>`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 |
| <a id="stop-bounce"></a>`bounce` | bounces over 2 % of a day’s claim notes | that Mailbox pauses; <Op verb="Mailbox.rest" /> |
| <a id="stop-complaint"></a>`complaint` | complaints near 0.1 % | claim notes pause on every Mailbox |
| <a id="stop-locked-send-abuse"></a>`locked-send-abuse` | an unclaimed account tries a locked send to 10 or more people in an hour | the account is suspended and logged |
| <a id="stop-claim-floor"></a>`claim-floor` | claimed and paying under 1 per 100 after 500 agent-made accounts | claim notes return to You approve each one; <Op verb="Issue.create" /> |
| <a id="stop-kill-switch"></a>`kill-switch` | you press it | claim notes, in-reply Offers and new accounts pause; <Op verb="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.

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


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

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

```json title="The Campaign card · sample"
{
  "$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=all`; 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 <Op verb="Task.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/workspaces` 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.¹
>
> **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).

```diff title="An unclaimed account, 2026-09-21 · limit-truth refused the 402, then the reply that served"
- 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 <Op verb="Entitlement.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
```mdx title="plays/agent-to-agent.mdx"
---
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.
```

```text
$ 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
| Key | Id | What it is |
| --- | --- | --- |
| Tactic | <a id="tactic-claimable-provision"></a>`claimable-provision` | Claimable provision answers one keyless call with a working Tenant whose side effects stay locked until a human claims it, and expires it unclaimed. Starts at Sends nothing. |
| Tactic | <a id="tactic-claim-handoff"></a>`claim-handoff` | Claim handoff sends the human a claim message built from the Tenant’s own records, runs the one-screen claim, and unlocks side effects after screening. Starts at You approve the first wave. |
| Tactic | <a id="tactic-in-response-lifecycle"></a>`in-response-lifecycle` | In-response lifecycle carries Limit headroom, fixes, honest over-Limit Offers, signed receipts and deprecations inside every reply to an agent Customer. Starts at Sends nothing. |
| Tactic | <a id="tactic-price-file-and-quote"></a>`price-file-and-quote` | Price file and quote publishes each Offer’s price as data, synced with Stripe Prices, and quotes a job’s exact cost before it runs. Starts at Sends nothing. |
| Tactic | <a id="tactic-inline-payment"></a>`inline-payment` | Inline payment answers a keyless priced call with payment terms and settles it per call inside the paying Agent’s own stated Limit. Starts at Sends nothing. |
| Tactic | <a id="tactic-claim-readout"></a>`claim-readout` | Claim readout computes the claimed-paying rate by weekly cohort, reads the signup holdout, and tests claim-message Variants. Starts at Sends nothing. |
| Tactic | <a id="tactic-registry-attribution"></a>`registry-attribution` | Registry attribution counts a registry’s real first calls by its install tag, excluding probes, indexers and eval traffic. Starts at Sends nothing. |
| Trigger | <a id="trigger-agent-provision"></a>`agent-provision` | Agent provision: `event` on `Tenant.provisioned`, window 72h. |
| Trigger | <a id="trigger-unclaimed-use"></a>`unclaimed-use` | Unclaimed use: `threshold` on `Interface.called`, window 48h. |
| Trigger | <a id="trigger-locked-verb-call"></a>`locked-verb-call` | Locked verb call: `event` on `Entitlement.checked`, window 24h. |
| Trigger | <a id="trigger-claim-page-open"></a>`claim-page-open` | Claim page open: `event` on `Visit.recorded`, window 24h. |
| Trigger | <a id="trigger-claim-expiry-due"></a>`claim-expiry-due` | Claim expiry due: `at` on `Tenant.provisioned`, window 24h. |
| Trigger | <a id="trigger-limit-approach"></a>`limit-approach` | Limit approach: `threshold` on `Entitlement.checked`, window 7d. |
| Trigger | <a id="trigger-limit-reached"></a>`limit-reached` | Limit reached: `threshold` on `Entitlement.checked`, window 1d. |
| Trigger | <a id="trigger-fixable-error"></a>`fixable-error` | Fixable error: `event` on `Interface.called`, window 7d. |
| Trigger | <a id="trigger-principal-quiet"></a>`principal-quiet` | Principal quiet: `state` on `Interface.called`, window 30d. |
| Sequence | <a id="sequence-claim-handoff-three-touch"></a>`claim-handoff-three-touch` | Claim handoff three touch: 4 steps; it ends on The Tenant is claimed or restored.; The Person clicks This was not me.; The address is suppressed.; The message bounces.. |
| Sequence | <a id="sequence-limit-approach-offer"></a>`limit-approach-offer` | Limit approach offer: 2 steps; it ends on The Offer is accepted.; The Limit period resets.; The Offer reaches its end time.. |
| Benchmark | <a id="benchmark-docs-warning-adoption"></a>`docs-warning-adoption` | Runs where an agent used the right approach after a docs warning named the failing one, against 1 of 5 after a tip: 100 % (An agent-experience experiment series., 2026-10-06). |