URL: /plays/developer-relations · Id: `play:developer-relations`

# Developer relations

> Developer relations fixes what coding agents read, call by call, and ships each fix only when the suite and time to first call agree.

Developer relations fixes what coding agents read, call by call, and ships each fix only when the suite and time to first call agree.

```text title="Set it up · Ask"
Be our Content Lead for developers: watch where coding agents get stuck on our API, fix the errors and docs they read, and test every snippet before it ships. I approve every Change. Show me what’s missing; don’t send.
```

```text title="Run one · Ask"
Hand our latest stalled API key to the Content Lead 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 ("Action.evaluated", "APIKey.stalled", "Question.asked", "Monitor.matched", "Version.deployed", "Version.deprecated", "Monitor.checked", "Gathering.scheduled", "APIKey.activated")', 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_447",
    "$type": "Decision",
    "choice": "fix-what-agents-read",
    "reason": "9 of 14 stalled keys this week failed on POST /notices with a 400 that names no field; the suite fails that call 3 of 5 runs",
    "stakes": "medium",
    "options": [
      "fix-what-agents-read",
      "repair-a-snippet",
      "answer-the-developer",
      "notes-for-agents",
      "host-a-hackathon",
      "to-a-person"
    ],
    "by": "worker_7",
    "on": "key_61",
    "expected": "median time to first call under 15 min for keys on the new error, within 14d",
    "reopensWhen": "Flag.evaluated split shows no drop for change_31 within 14d"
  },
  "actions": [
    {
      "$id": "https://api.sb/leaseline.app/actions/action_1",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_7",
      "verb": "Action.list",
      "checks": []
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_2",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_7",
      "verb": "Change.create",
      "checks": [
        {
          "check": "snippet-pause",
          "status": "pass"
        },
        {
          "check": "security",
          "status": "pass"
        }
      ]
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_3",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_7",
      "verb": "Change.review",
      "checks": []
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_4",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_7",
      "verb": "Flag.create",
      "checks": []
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_5",
      "$type": "Action",
      "status": "pending",
      "reason": "awaitingApproval",
      "mode": "try",
      "by": "worker_7",
      "verb": "Approval.create",
      "checks": []
    }
  ]
}
```

`action_3` replayed the suite on the new error: POST /notices passes 5 of 5 runs, up from 2 of 5. `action_5` is your Approval to merge `change_31` behind a 50/50 Flag split by key.

`11 min median first call against 47 min before · before-after, not proof · n 34 keys · path → arr`

| Try another: the stalled key | Choice | Reason |
| --- | --- | --- |
| key_61, Harbor Row’s developer: 3 failed POST /notices calls in 4 minutes | `fix-what-agents-read` | the same unnamed 400 stalled 9 of 14 keys this week |
| The renewal reminder docs block fails its sandbox run after Tuesday’s release | `repair-a-snippet` | its verification line returns 401, not the stated output |
| Repository issue: “How do I send a notice to every unit in a building?” | `answer-the-developer` | a verified block answers it |
| GET /v1/tenants gets a removal date of 2027-01-15 | `notes-for-agents` | a removal date is set: notes and the 90-day notice |
| Repository issue: “Can we get a higher rate Limit on an annual plan?” | `to-a-person` | terms are never the agent’s call |

| Worth | Your time | Spend | First signal | Use it when | Instead |
| --- | --- | --- | --- | --- | --- |
| ≈ +$330 ARR a month at 20 | 30 min week 1, then 20 a week | $0.40 per suite run, ~$40 a month at 100 runs | Day 9: first fix ships behind its Flag | 20+ new API Keys a month, first call over 15 min | No public API: publish one (<Op verb="Interface.create" />) · under 20 keys a month: `agent-legibility` |

**Where it sits:** Stranger → Lead → **Trialing** → **Activated** → At risk → Churned → Won back · Time to first call → Activation rate → New paying Customers → New Customers ↑ → ARR added each week → ARR

Worth’s formula: about +6 activated per 100 new Customers (Other Startups’ results; no lift of your own yet) × 20 new keys a month × 0.8 paid per activated × $347 first order ARR ≈ +$333. Under First signal: At 34 new keys in 56 days, 90 days can tell only a 20-minute drop in time to first call or more, so it reads before-after.

`sample · api.sb/leaseline.app · 41 Customers at 2026-10-19 · on APIKey.stalled · first fix drafted 14m · 9 keys Not live yet`

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

# 2 · dry run; below, its 422 on 2026-08-24
curl -X POST "https://api.sb/leaseline.app/campaigns?mode=try" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"play":"developer-relations"}'

# 3 · keep it; you approve every Change
curl -X POST https://api.sb/leaseline.app/campaigns \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"play":"developer-relations"}'

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

# off: no new fix ships; live Pages and errors keep serving
curl -X PATCH https://api.sb/leaseline.app/campaigns/developer-relations \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"endsAt":"now"}'
```


```json title="The dry run’s 422 · 2026-08-24 · sample"
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "developer-relations can’t start yet: 1 check fails and 1 warns.",
  "checks": [
    {
      "check": "use-when",
      "status": "pass",
      "observed": "22 new keys in 90 days at a 47-minute median first call; 7 a month, under 20",
      "doc": "/plays/developer-relations#check-use-when",
      "fix": "Under 20 a month it runs, but reads before-after only.",
      "then": "Metric.query"
    },
    {
      "check": "baseline",
      "status": "pass",
      "observed": "47 min median first call, n 22 keys in 90 days",
      "doc": "/plays/developer-relations#check-baseline",
      "fix": "None needed: 47 minutes is your Before.",
      "then": "Metric.query"
    },
    {
      "check": "approved-pages",
      "status": "fail",
      "doc": "/plays/developer-relations#check-approved-pages",
      "fix": "Publish the API reference Page; answers quote only verified blocks on it.",
      "then": "Site.publish"
    },
    {
      "check": "role-unstaffed",
      "status": "warn",
      "roles": [
        "content-lead"
      ],
      "owned": 0,
      "doc": "/plays/developer-relations#check-role-unstaffed",
      "fix": "Staff the Content Lead, or leave it: every Change comes to you.",
      "then": "Role.staff"
    }
  ]
}
```

| Check | Fix (one call) | Your time | Wait |
| --- | --- | --- | --- |
| <a id="check-use-when"></a>`use-when` · <a id="check-baseline"></a>`baseline` · pass: 22 keys in 90 days, 47 min median first call (gaps #8) | <Op verb="Metric.query" /> on `time-to-first-call`, which fills Before in Your number | 1 min | none |
| <a id="check-approved-pages"></a>`approved-pages` · fail (Not live yet) | <Op verb="Site.publish" /> the API reference Page | 15 min | none |
| <a id="check-role-unstaffed"></a>`role-unstaffed` · warn (gaps #8): no Content Lead seat | <Op verb="Role.staff" />, or leave it: every Change comes to you | 0 min | none |

`Your number · Before 47 min · Now 11 min median first call · Target under 10 min by Dec 31 · before-after, not proof · n 34 keys`

Each suite run costs about $0.40 in model calls across 5 agent profiles (Worker cost), under a $300-a-month suite Limit; snippet runs in the sandbox cost nothing. That comes to about $40 a month at 100 runs.

## When it starts
| When | Trigger id | Starts on | Window | How it gets in |
| --- | --- | --- | --- | --- |
| A new key fails 3 calls in a row, or has no success in 30 min | `first-call-stall` | `state` on `APIKey.stalled` where 3 failed calls in a row, or no success 30m after the first call | 7d | your API reports each call: <Op verb="Event.capture" /> on each key’s first call and failures |
| The suite fails a call on any agent profile | `case-failure` | `threshold` on `Action.evaluated` where failed runs >= 2 of 5 on any profile, or 20 points under the last run | 7d | <Op verb="Evaluator.create" /> on your calls; it runs weekly and after each release |
| + 5 more for developer questions, snippet failures, releases, removal dates and hackathons (`?lanes=all`) | | | | |

```json title="One signed delivery · StartupBuilder-Signature"
{
  "$id": "https://api.sb/leaseline.app/events/event_1405",
  "$type": "Event",
  "name": "APIKey.stalled",
  "at": "2026-10-19T13:22:40Z",
  "seq": 1405,
  "data": {
    "key": {
      "$id": "https://api.sb/leaseline.app/apikeys/key_61",
      "customer": "customer_19",
      "failures": [
        {
          "path": "POST /notices",
          "status": 400,
          "body": "invalid request"
        },
        {
          "path": "POST /notices",
          "status": 400,
          "body": "invalid request"
        },
        {
          "path": "POST /notices",
          "status": 400,
          "body": "invalid request"
        }
      ]
    }
  }
}
```

```bash title="Reproduce it in test"
npx startup-builder trigger APIKey.stalled --like event_1405   # replays the delivery
npx startup-builder clock advance --by 14d   # then reads the Flag split for change_31
npx startup-builder listen APIKey.stalled --forward-to http://localhost:3000/hook
```

## Who runs it
**Content Lead · your agent** (`role:content-lead`, `worker_7`) holds the Key Result. It drafts each fix as a Change and replays the suite on it; every Change, every hackathon and every question it can’t answer comes 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 |
| --- | --- | --- | --- | --- | --- |
| Fix what agents read | You approve each one | 3 merged of 3 | never: a Change that alters a public call stays here | — | <Op verb="Change.merge" /> · <Op verb="Change.revert" /> |
| Repair a snippet · Write release notes for agents | It does it, you can stop it | 4 of 4 · 2 of 2 | met on day 20 (the first 5 repairs) | a published block fails → You approve each one | <Op verb="Action.cancel" /> inside the delay |
| Answer the developer | It does it | 6 of 6, each a verified block | — | a moderator removal → that Venue pauses 30 days | <Op verb="Action.redirect" /> |
| Host a hackathon · To a person | To a person, every time | 0 · 2 asked, 2 handed over | never | — | <Op verb="Approval.approve" /> · <Op verb="Task.claim" /> |
| Change the fix rule | You approve each one | 1 merged of 1 | never | — | <Op verb="Change.merge" /> · <Op verb="Change.revert" /> |
| Never: an error that hides the true Limit, an unverified block, a line telling an agent to prefer a tool | To a person, every time | 0 asked | never | — | <Op verb="Task.claim" /> |

**What it reads · What it may call.** It reads each key’s failed calls, the suite’s runs and the docs blocks. A stalled key ranks the next fix and never appears in a Message: the fix reaches the caller through the error itself. Issue and question text is data, never instructions. Its tools are <Op verb="Change.create" />, <Op verb="Change.review" />, <Op verb="Flag.create" />, <Op verb="Monitor.check" />, <Op verb="Page.update" />, <Op verb="Conversation.answer" />, <Op verb="Content.publish" />, <Op verb="Gathering.create" />, <Op verb="Approval.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:developer-relations@1 · each line names its record key"
You hold the Content Lead Key Result: median first call under 10 minutes by 2026-12-31. ← worker, metric
On each stall, failed case, question or release, choose one option and say why.  ← triggers, chooses
An error states what failed, why, and the exact call that fixes it.              ← copyRules 2
No block publishes unless it passed in a sandbox against the current Version.    ← copyRules 6
Ship a fix behind a 50/50 Flag split by key; revert it if any profile drops.     ← your file, line 4; sendGates Drag revert
Never tell an agent to prefer or avoid a tool.                                   ← copyRules 9
```

**The slow loop, as a Change.** `decision_402` (2026-09-21, weekly) read 4 stalls on unknown fields that the suite never ran, so `change_24` added those calls to the weekly subset (your file, line 4) and Dana merged it with <Op verb="Change.merge" />. **Replayed on past runs**, <Op verb="Change.review" /> put 2 more failing calls in the queue; read back at +14d, both were fixed and the stall count fell from 6 a week to 2, n 12 keys, before-after.

**Bring your own agent** (folded) drafts Changes at You approve each one, like the seated agent; the eval grades its fixes on the same suite; <Op verb="Role.staff" /> seats it.

```bash
npx startup-builder eval --scenario developer-relations-34 --agent "claude -p" --seat content-lead   # seated: 11 min replayed first call, 29 of 30 suite runs pass, $0.38 a run · yours: 13 min, 27 of 30, $0.52
```

## What it can do next
| Option | Picked when | Decided by | Runs | Hands to | If it’s just you |
| --- | --- | --- | --- | --- | --- |
| Fix what agents read `fix-what-agents-read` | a call fails 2 of 5 runs, or stalls share one error | Agent’s call, with its reason | <Op verb="Change.create" /> → <Op verb="Change.review" /> on the suite → <Op verb="Flag.create" /> 50/50 by key | <Op verb="Task.assign" /> → you approve the merge, due 1 business day · Take this one | you |
| Repair a snippet `repair-a-snippet` | a published block fails its sandbox run | Your rule (copy rule 6) | <Op verb="Monitor.check" /> → <Op verb="Page.update" />; the block shows failing within 10 min | nobody | the same |
| Answer the developer `answer-the-developer` | asks how to make a call, and a verified block answers it | Agent’s call, with its reason | `answer-message` → <Op verb="Conversation.answer" /> with the block and its check line | nobody | the same |
| Write release notes for agents `notes-for-agents` | a release changes a public call, or a removal date is set | Your rule (the release rule) | `launch-announcement` → <Op verb="Content.publish" /> → `deprecation-notice` at 90, 30 and 7 days | nobody | the same |
| Host a hackathon `host-a-hackathon` | 45 days before a scheduled event | Agent’s call, with its reason | <Op verb="Gathering.create" /> → <Op verb="Approval.create" /> → `hosted-hackathon-builder` | you approve the $1,000 Limit, due 1 business day · Take this one | you |
| To a person `to-a-person` | a question no verified block answers, or terms | Agent’s call, with its reason | <Op verb="Conversation.escalate" /> → <Op verb="Task.assign" /> | you, due 1 business day · Take this one | you |

| When they write back | Fixture reply | What happens | Who answers | By when | Call |
| --- | --- | --- | --- | --- | --- |
| Interested | “That worked. Can we batch it?” | answers with the verified batch block and its check line | Content Lead · your agent | 4 business hours | <Op verb="Conversation.answer" /> |
| Send me pricing | “What do calls past the free Limit cost?” | answers with the Pricing Page’s per-call Price and the true Limit | Content Lead · your agent | 4 business hours | <Op verb="Conversation.answer" /> |
| Not now | “We’ll pick this up next sprint.” | the Thread closes; it reopens on their next call or comment | nobody | at once | <Op verb="Conversation.close" /> |
| Wrong person | “Our contractor wrote that script.” | asks them to point the contractor at the Issue; it never writes to an address from their text | Content Lead · your agent | 1 business day | <Op verb="Conversation.answer" /> |
| Out of office | an auto-reply until Oct 28 | nothing waits on them: the fix ships through the error itself | nobody | the return date | <Op verb="Message.schedule" /> |
| Question it can’t answer | “Can we get a higher rate Limit on an annual plan?” | To a person: terms are never the agent’s call | you | 1 business day | <Op verb="Conversation.escalate" /> |
| Stop | “Stop emailing us about deprecations.” | notices stop; the removal date still shows in the error and the docs | nobody | at once | <Op verb="Contact.suppress" /> |

## When it hands to you
Every Change, every hackathon and every question no verified block answers comes to **you**. One Brief per Role, as captured (`brief` is Not live yet, gaps #7).

```json title="What lands in each queue · sample"
[
  {
    "$id": "https://api.sb/leaseline.app/tasks/task_552",
    "$type": "Task",
    "assignee": "worker_1",
    "due": "2026-10-20T13:22:00Z",
    "on": "key_61",
    "brief": {
      "what": "change_31: the 400 on POST /notices now names the missing field, unit_id, and the call that fixes it",
      "why": "9 of 14 stalled keys this week; suite 2 of 5 → 5 of 5 replayed",
      "ships": "behind a 50/50 Flag split by key, 14 days",
      "reverts": "if any agent profile drops on the next run",
      "never": [
        "a lower Limit than the true one",
        "an unverified block"
      ]
    }
  },
  {
    "$id": "https://api.sb/leaseline.app/approvals/approval_63",
    "$type": "Approval",
    "verdict": null,
    "deadline": "2026-10-20T13:22:00Z",
    "draft": "change_31: 400 invalid request → 400 unit_id is required; send POST /notices with {\"unit_id\": \"unit_…\", \"kind\": \"renewal\"}",
    "route": "fix-what-agents-read",
    "checks": [
      {
        "check": "snippet-pause",
        "status": "pass"
      },
      {
        "check": "security",
        "status": "pass"
      }
    ]
  }
]
```

You answer with <Op verb="Task.claim" /> (Take this one), <Op verb="Task.return" /> with a reason, <Op verb="Approval.approve" /> or <Op verb="Approval.refuse" />, each a one-call window; an Approval unanswered by its deadline expires and the Change stays unmerged. Once you claim a question, the agent’s pending reply on that Thread cancels with `claimedByPerson` (Not live yet, gaps #7). A Task nobody claims by its due time goes up through <Op verb="Task.escalate" />, and `person-promise` fires.

**On time:** you, 5 of 5 claimed within their due time, median 3 h 05 min, n 5, ■□□□ Too early to tell beside Other Startups’ 88 % on time. **Your queue:** counted by kind: in week 1, 2 Changes and 1 question; since then about 1 Change and 1 weekly review a week.

**The fixed line:** <Op verb="Deal.assign" /> sets `owner` to the receiving person, the agent is never a Deal’s owner, and Meetings land on the owner’s calendar under the owner’s name.

## When it won’t send
| Check (refuses one send) | Refuses | Fix | Then |
| --- | --- | --- | --- |
| <a id="check-owned-by-a-person"></a>`owned-by-a-person` (rule j; Not live yet, gaps #8) | a reply in a Thread a person claimed, or to a Contact whose 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-claims-resolve"></a>`claims-resolve` (shared) | a pass rate, usage count or claim that resolves to no measured, dated run | Cut the claim, or name the run that backs it. | <Op verb="Content.draft" /> |
| <a id="check-snippet-pause"></a>`snippet-pause` (the record’s Snippet pause) | a block that has not passed in a sandbox against the current Version, or a Change that breaks one | Run the block in the sandbox; it publishes once its check line matches. | <Op verb="Monitor.check" /> |
| <a id="check-credit"></a>`credit` (the record’s Credit stop) | an event credit past its Limit, or a key drawing credits from 2 accounts | Raise the event Limit, or block the second account. | <Op verb="Limit.raise" /> |

| Stop (pauses the Play) | Pauses when | Then |
| --- | --- | --- |
| <a id="stop-drag-revert"></a>`drag-revert` | a merged fix loses its Flag split, or any profile drops on the next run | the Change reverts with <Op verb="Change.revert" /> |
| <a id="stop-security"></a>`security` | a suite run grants more access than its task needs | that call fails and its Pages pin at You approve each one |
| <a id="stop-first-call-regression"></a>`first-call-regression` | median first call up 25 % week over week, at 50+ new keys | new fixes freeze; <Op verb="Issue.create" /> |
| <a id="stop-cost"></a>`cost` | suite runs reach their token Limit | runs drop to the regression subset; raising it is an Approval |
| <a id="stop-hackathon-floor"></a>`hackathon-floor` | an event under 5 kept event keys per $1,000 at day 30 | the series stops until a new Decision |
| <a id="stop-venue-notice"></a>`venue-notice` | a moderator removes an answer, or a Venue sends a notice | that Venue’s answers pause for 30 days |
| <a id="stop-kill-switch"></a>`kill-switch` | you press it | every Tactic pauses; live Pages and errors keep serving |

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

```json title="The captured refusal · sample"
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "The answer on the repository Issue did not send: its block never passed in the sandbox against the current Version.",
  "checks": [
    {
      "check": "snippet-pause",
      "status": "fail",
      "doc": "/plays/developer-relations#check-snippet-pause",
      "fix": "Run the block in the sandbox; it publishes once its check line matches.",
      "then": "Monitor.check"
    },
    {
      "check": "claims-resolve",
      "status": "pass"
    }
  ]
}
```


```json title="The Stops that fired, as Campaign.paused reasons · sample"
[
  {
    "reason": {
      "limit": "drag-revert",
      "observed": "4 of 5 → 3 of 5 on one agent profile",
      "bound": "no drop",
      "window": "1 suite run"
    },
    "doc": "/plays/developer-relations#stop-drag-revert",
    "task": "task_538"
  }
]
```

## Did it work
At 34 new keys in 56 days the Flag splits read each fix, but the leaf has too few keys a week for a held-back half, so its basis is before-after (rule o): the columns read Now and Before, and nothing prints as Added. Every node above the leaf is Not live yet (gaps #4).

`11 min median first call against 47 min before · before-after, not proof · n 34 keys · path → arr`

| Node | Now (56 days) | Before (the 90 days, per 56) | Added | Confidence | Basis |
| --- | --- | --- | --- | --- | --- |
| `time-to-first-call` ↓ | 11 min median, n 34 keys; 29 of 34 succeed within 30 min | 47 min, n 22 keys; 12 of 22 | — | — | before-after, not proof · Other Startups’ results −21 min, ■■■□ Likely, n 3,410 keys |
| `activation-rate` | 75 % (9 of 12 new Customers) | 54 % (7 of 13) | — | — | before-after |
| `new-paying-customers` · `new-arr` ↑ | $1,735, 5 Customers whose first call came through a key · Booked: credited, not all caused by this Play | $1,388, 4 Customers | — · Worth about +$330 a month | — | before-after · 6 trials still deciding |
| `net-new-arr` · `arr` | carried from `new-arr` | carried | — | — | carried |

Before against after reads 56 days against the 90 days before, scaled to 56; it is not proof, and Other Startups’ results print beside it. Each fix’s own Flag split prints in Example as what happened, never as the Play’s result.

**How it moves it:** the 3 merged errors now name the missing field and the call that fixes it, and 9 of 11 docs blocks carry a passing check line.

**Guardrails:** before against after: paid per activated, 0.56 against 0.57, and support Threads per new key, 0.21 against 0.45; 6 trials are still deciding.

```json title="The Campaign card · sample"
{
  "$id": "https://api.sb/leaseline.app/campaigns/developer-relations",
  "$type": "Campaign",
  "play": "play:developer-relations@1",
  "path": [
    "time-to-first-call",
    "activation-rate",
    "new-paying-customers",
    "new-arr",
    "net-new-arr",
    "arr"
  ],
  "experiment": {
    "metric": "time-to-first-call",
    "unit": "minutes",
    "heldBack": 0,
    "decision": null
  },
  "readout": {
    "value": null,
    "n": 34,
    "maturing": 6,
    "interval": null,
    "readability": "inadmissible",
    "basis": "before-after",
    "pooled": true
  },
  "ledger": {
    "spend": 0,
    "workerCost": 74,
    "hours": 3.1,
    "openArr": 2082,
    "wonArr": 1735,
    "projectedArr": 333,
    "paybackDays": 16
  }
}
```

Outside benchmark: a warning that names the wrong call first raised agent pass rates 20 to 100 points over a tip (a platform vendor’s agent-experience experiments, in Your settings).

**Before it · After it.** Before it: `agent-legibility` and `agent-registries` bring the agents that make first calls. After it: `usage-upsell` hears the keys whose use climbs past their Limit, and `instant-response` answers the developers who write in.

## Example
Leaseline ran it from 2026-08-24 to 2026-10-19 (56 days): 34 new keys, 14 stalls, 3 merged fixes, 4 snippet repairs and 8 developer questions. Read every captured thread at `?threads=all`; three follow the tables.

| What happened, by option | Decisions | Shipped | Its own Flag split, 14 days |
| --- | --- | --- | --- |
| Fix what agents read | 4 | 3 merged, 1 reverted | POST /notices: new error 9 min median, old 41 min, n 18 keys |
| Repair a snippet · Write release notes for agents | 4 · 2 | 4 · 2 | — |
| Answer the developer · To a person | 6 · 2 | 6 · 2 by you | — |
| **All new keys** · the 90 days before, per 56 | **34** | **11 min median first call** | 47 min, n 22 |
| At 20 a month (the Use it when volume) | about 37 in 56 days | before-after, not proof | Other Startups’ results: −21 min, ■■■□ Likely, n 3,410 |

| Day | Event | What happened | Your time |
| --- | --- | --- | --- |
| 0 | `event_1371` Campaign.created | the three steps; the dry run fails `approved-pages` | 20 min |
| 0 | `event_1372` Site.published | the API reference Page goes live with check lines | 15 min |
| 9 | `event_1380` Change.merged | the first fix ships behind its Flag split: GET /leases names its date format | 10 min |
| 20 | `event_1388` Role.updated | promoted: snippet repairs and release notes run after a delay | — |
| 28 | `event_1394` Change.merged · `event_1397` Campaign.paused | the second fix lost a point on one profile; `drag-revert` reverted it | 10 min |
| 56 | `event_1405` APIKey.stalled | key_61 stalls on POST /notices; `change_31` goes to you | 5 min |
| 1–56 | `event_1374` Approval.created, first of 5 | you approved 4 Changes and refused 1; weekly reviews, from the <Op verb="Task.next" /> log | 1 h 40 min · **total 3.1 h measured, 3 h asked** |

> **Repository issue, Oct 6, 09:14** · “How do I send a notice to every unit in a building?”
>
> **Leaseline, 09:31** · Send one POST /notices per unit with `"building": "bld_12"` and `"kind": "renewal"`, or one POST /notices/batch for up to 500 units.¹ The block below is verified: it prints “42 notices queued” on the sandbox building.² *Signed with the Role’s disclosure line.*³
>
> **Developer, 09:52** · “Worked first try.” Their key made its first successful batch call at 09:49.

```diff title="Repository issue, 2026-09-30 · the draft snippet-pause refused, then the rewrite that sent"
- leaseline notices send --all
+ leaseline notices send --building bld_12   # verified 2026-10-02: prints "42 notices queued"
```

> **Repository issue, 2026-10-14 15:02** · “Can we get a higher rate Limit on an annual plan?” · **Leaseline, 15:04** · *Thanks. Dana, our founder, handles plans and replies here by 15:02 tomorrow.*⁴ The Brief went to Dana (`task_549`); she replied at 10:30.

¹ It names the right call first. ² The verification line sits under the block. ³ 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/developer-relations.mdx"
---
holdout: 0.5   # each fix ships to half the new keys for 14 days, behind its Flag
---
Fix the errors and docs agents read on our API, and test every block in the sandbox. I approve every Change.
```

```text
$ npx startup-builder show plays/developer-relations
holdout 0.5 per fix · you approve every Change     your file  plays/developer-relations.mdx:2,4 (Ask 2026-08-24; suite subset widened by change_24, 2026-09-21)
family demand · process content-marketing · worker role:content-lead, owns never · chooses 6 (2 rule, 4 read) · sequences deprecation-notice, hosted-hackathon-builder · checks owned-by-a-person, claims-resolve, snippet-pause, credit · stops 7 · placements npm, mcp-registry, github, stack-overflow, youtube, x, linkedin, gmail, outlook, yahoo-mail    catalog  play:developer-relations@1 (Not live yet, gaps #5)
```

### Every key from the catalog
| Key | Id | What it is |
| --- | --- | --- |
| Tactic | <a id="tactic-verb-case-intake"></a>`verb-case-intake` | Verb Case intake: Verb case intake turns stalled first calls, developer questions and agent requests into efficacy cases per Verb, with checklists written from outside. Starts at Sends nothing. |
| Tactic | <a id="tactic-agent-read-fix"></a>`agent-read-fix` | Fix what agents read repairs error text, warnings, release notes and starter Pages behind a failing case, gated by the suite and a Flag split. Starts at You approve each one. |
| Tactic | <a id="tactic-snippet-verification"></a>`snippet-verification` | Snippet verification runs every copyable docs block and its verification line on each candidate Version and nightly, and pauses any block that fails. Starts at You approve each one. |
| Tactic | <a id="tactic-release-notes-for-agents"></a>`release-notes-for-agents` | Release notes for agents writes one line per Change, a warning for each changed default, and each deprecation in the response and in notices. Starts at You approve the first wave. |
| Tactic | <a id="tactic-instruction-file-test"></a>`instruction-file-test` | Instruction file test gates each challenger description on load rate and task success in the suite, splits it per install, and ships the winner. Starts at It does it, you can stop it. |
| Tactic | <a id="tactic-hosted-hackathon"></a>`hosted-hackathon` | Hosted hackathon runs one online build event a quarter with prizes for multi-Verb builds, measured by keys issued under the event prefix. Starts at You approve each one. |
| Tactic | <a id="tactic-first-call-readout"></a>`first-call-readout` | First-call Readout reads median time to first call and first-call success weekly, split by caller kind and Interface, beside the suite’s pass rate per Verb. Starts at Sends nothing. |
| Tactic | <a id="tactic-tool-description-lint"></a>`tool-description-lint` | Tool description lint blocks any description that holds hidden text, addresses the reading agent, names a rival, or states a fact the claims source lacks. Starts at Sends nothing. |
| Tactic | <a id="tactic-description-variant-test"></a>`description-variant-test` | Description variant test gates each challenger description on the pick-rate suite, then splits it per caller through a Flag and ships the winner everywhere. Starts at Sends nothing. |
| Tactic | <a id="tactic-placement-code"></a>`placement-code` | Placement code gives each placement a vanity Page, a Stripe promotion code and, for events, an API Key prefix, each tested before it runs. Starts at Sends nothing. |
| Tactic | <a id="tactic-send-gate-check"></a>`send-gate-check` | Send gate check: Checks Suppression, a live reason, the Market, the basis stamp, the Person-month grant, the facts and the labels before any cold Message exists. Starts at Sends nothing. |
| Trigger | <a id="trigger-case-failure"></a>`case-failure` | Case failure: `threshold` on `Action.evaluated`, window 7d. |
| Trigger | <a id="trigger-first-call-stall"></a>`first-call-stall` | First call stall: `state` on `APIKey.stalled`, window 7d. |
| Trigger | <a id="trigger-developer-question"></a>`developer-question` | Developer question: `event` on `Question.asked`, window 2d. |
| Trigger | <a id="trigger-snippet-failure"></a>`snippet-failure` | Snippet failure: `event` on `Monitor.matched`, window 1d. |
| Trigger | <a id="trigger-release-shipped"></a>`release-shipped` | Version shipped: `event` on `Version.deployed`, window 2d. |
| Trigger | <a id="trigger-deprecation-scheduled"></a>`deprecation-scheduled` | Deprecation scheduled: `event` on `Version.deprecated`, window 30d. |
| Trigger | <a id="trigger-model-release"></a>`model-release` | Model release: `event` on `Monitor.checked`, window 7d. |
| Trigger | <a id="trigger-hackathon-due"></a>`hackathon-due` | Hackathon due: `at` on `Gathering.scheduled`, window 14d. |
| Trigger | <a id="trigger-event-key-activated"></a>`event-key-activated` | Event key activated: `threshold` on `APIKey.activated`, window 14d. |
| Sequence | <a id="sequence-deprecation-notice"></a>`deprecation-notice` | Deprecation notice: 3 steps; it ends on The key makes no call to the old version for 14 days; The old version is removed. |
| Sequence | <a id="sequence-hosted-hackathon-builder"></a>`hosted-hackathon-builder` | Hosted hackathon builder: 4 steps; it ends on Reply; Booked Meeting; First paid Transaction; Opt-out. |
| Benchmark | <a id="benchmark-devrel-warning-vs-tip"></a>`devrel-warning-vs-tip` | Share of agent runs that adopted the documented approach after a docs tip, against a warning naming the failing approach.: 20–100 % (A platform vendor’s agent-experience experiments., 2026-10-06). |
| Benchmark | <a id="benchmark-devrel-json-input-mode"></a>`devrel-json-input-mode` | Task success of a small model with a JSON input mode, against plain arguments.: 40–100 % (A platform vendor’s agent-experience experiments., 2026-10-06). |
| Benchmark | <a id="benchmark-devrel-named-interface-instruction"></a>`devrel-named-interface-instruction` | Setup checks passed when the task told the agent which Interface to use.: 37.5–93.75 % (A platform vendor’s agent-experience experiments., 2026-10-06). |