URL: /plays/data-stories · Id: `play:data-stories`

# Data stories

> Data stories turns your records into a dated release and briefs you to pitch it to scored reporters; every number links to its Dataset.

Data stories turns your records into a dated release and briefs you to pitch it to scored reporters; every number links to its [Dataset](https://startupbuilder.dev/business/nouns/dataset).

```text title="Set it up · Ask"
Be our Marketing Lead: turn our renewal records into a quarterly report, build the reporter list, and brief me on every pitch and data request. I write every word that goes to a reporter. Show me what’s missing; don’t send.
```

```text title="Run one · Ask"
Hand our latest reporter question to the Marketing 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 ("Dataset.scheduled", "Dataset.shifted", "Dataset.published", "Monitor.matched", "Question.asked", "Dataset.requested", "Interview.requested", "Monitor.checked", "Mention.found")', 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_431",
    "$type": "Decision",
    "choice": "answer-a-data-request",
    "reason": "the reporter asks the renewal rate by notice lead time, which the Q3 release holds at n 1,940 and n 1,110",
    "stakes": "low",
    "options": [
      "pitch-the-release",
      "pitch-a-news-moment",
      "answer-a-data-request",
      "grant-access",
      "ask-for-a-correction",
      "not-now"
    ],
    "by": "worker_6",
    "on": "question_17",
    "expected": "a Mention citing the release within 14d",
    "reopensWhen": "no reply from the reporter on question_17 within 3d"
  },
  "actions": [
    {
      "$id": "https://api.sb/leaseline.app/actions/action_1",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_6",
      "verb": "Dataset.get",
      "checks": []
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_2",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_6",
      "verb": "Content.get",
      "checks": []
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_3",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_6",
      "verb": "Task.assign",
      "checks": []
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_4",
      "$type": "Action",
      "status": "pending",
      "reason": "awaitingAuthor",
      "mode": "try",
      "by": "worker_6",
      "verb": "Message.create",
      "checks": [
        {
          "check": "owned-by-a-person",
          "status": "pass"
        },
        {
          "check": "authorship",
          "status": "held",
          "then": "Task.assign"
        },
        {
          "check": "number-match",
          "status": "pass"
        },
        {
          "check": "claims-resolve",
          "status": "pass"
        }
      ]
    }
  ]
}
```

`action_3` is your Brief: the number, its n, the release link and three fragments. `action_4` is the reply in your Mailbox, which waits for your words.

`3 earned mentions against 0 before · before-after, not proof · n 1 release · path → arr`

| Try another: the reporter’s ask | Choice | Reason |
| --- | --- | --- |
| A housing-desk reporter: “What share of renewals happen when notice goes out 60 days ahead?” | `answer-a-data-request` | the Q3 release holds it: 71 %, n 1,940 |
| Release due: the Q3 Renewal Report, lifting Sep 14 | `pitch-the-release` | all 10 readiness checks pass; 22 tier 2 pitched, 8 held back |
| Story, 6 h old: “Renewals slow as vacancies rise” on a business desk | `pitch-a-news-moment` | the Dataset answers it, inside the 12-hour window |
| A blog post quotes the 71 % as 77 % | `ask-for-a-correction` | the number differs from the release’s Fact |
| Story about a fatal building fire | `not-now` | `tragedy-skip`: dropped, with no override |

| Worth | Your time | Spend | First signal | Use it when | Instead |
| --- | --- | --- | --- | --- | --- |
| ≈ +$580 ARR a month at 1 release a quarter | 2.5 h per release, then 15 min per news moment | $0 per pitch, ~$3 a month Worker cost | Day 24: first earned mention | A number only you hold, 1,000+ rows, 10+ Customers | Under 10 Customers: `free-tools` · no Dataset: freeze one (<Op verb="Dataset.create" />) |

**Where it sits:** **Stranger** → Lead → Trialing → Activated → At risk → Churned → Won back · Earned mentions → People reached → New Customers ↑ → ARR added each week → ARR

Worth’s formula: about +2.1 earned mentions per release (Other Startups’ results; no lift of your own yet) ÷ 3 months × 200 ICP visitors per mention × 1.2 new Customers per 100 × $347 first order ARR ≈ +$580. Under First signal: At 1 release a quarter and 22 reporters pitched, 90 days can tell only +6 mentions per release or more, so it reads before-after.

`sample · api.sb/leaseline.app · 41 Customers at 2026-10-19 · on Question.asked · first brief 6m · 8 keys Not live yet`

## Start it this week
```bash
npx startup-builder add play data-stories   # 1 · writes plays/data-stories.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":"data-stories"}'

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

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

# off: no pitch or brief; the release Page stays up
curl -X PATCH https://api.sb/leaseline.app/campaigns/data-stories \
  -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": "data-stories can’t start yet: 2 checks fail and 1 warns.",
  "checks": [
    {
      "check": "use-when",
      "status": "pass",
      "observed": "3,520 lease renewals at 38 Customers, Jul to Sep",
      "doc": "/plays/data-stories#check-use-when",
      "fix": "None needed: 1,000 rows and 10 Customers are enough.",
      "then": "Metric.query"
    },
    {
      "check": "baseline",
      "status": "pass",
      "observed": "0 earned mentions in 90 days",
      "doc": "/plays/data-stories#check-baseline",
      "fix": "None needed: 0 is your Before.",
      "then": "Metric.query"
    },
    {
      "check": "domain-authenticated",
      "status": "fail",
      "doc": "/plays/data-stories#check-domain-authenticated",
      "fix": "Add the 3 DNS records shown; it passes once they resolve, often within an hour.",
      "then": "Domain.authenticate"
    },
    {
      "check": "mailbox-warm",
      "status": "fail",
      "observed": "dana@leaseline.app has warmed 3 of 14 days",
      "doc": "/plays/data-stories#check-mailbox-warm",
      "fix": "Warm dana@ for 11 more days; pitches wait, the release Page does not.",
      "then": "Mailbox.warm"
    },
    {
      "check": "role-unstaffed",
      "status": "warn",
      "roles": [
        "marketing-lead"
      ],
      "owned": 0,
      "doc": "/plays/data-stories#check-role-unstaffed",
      "fix": "Staff the Marketing Lead, or leave it: every brief 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: 3,520 renewals at 38 Customers; 0 mentions in 90 days (gaps #8) | <Op verb="Metric.query" /> on `earned-mentions`, which fills Before in Your number | 1 min | none |
| <a id="check-domain-authenticated"></a>`domain-authenticated` · fail | <Op verb="Domain.authenticate" /> | 10 min | until DNS resolves, often under 1 h |
| <a id="check-mailbox-warm"></a>`mailbox-warm` · fail (Not live yet, gaps #1) | <Op verb="Mailbox.warm" /> dana@leaseline.app | 2 min | 11 days; the release builds meanwhile |
| <a id="check-role-unstaffed"></a>`role-unstaffed` · warn (gaps #8): no Marketing Lead seat | <Op verb="Role.staff" />, or leave it: every brief comes to you | 0 min | none |

`Your number · Before 0 · Now 3 earned mentions · Target 5 by Dec 31 · before-after, not proof · n 1 release · 34 days still maturing`

Freezing the snapshot, the release Page and the scored list cost about $3 a month in Worker cost; pitches send from your own Mailbox at $0. That comes to about $3 a month at 1 release a quarter.

## When it starts
| When | Trigger id | Starts on | Window | How it gets in |
| --- | --- | --- | --- | --- |
| A release comes due | `release-due` | `at` on `Dataset.scheduled` where 14 days before release.liftsAt | gated 14 days ahead; one per Dataset a quarter | <Op verb="Dataset.update" /> sets the release date |
| A reporter asks a number the release holds | `data-request` | `event` on `Question.asked` where question names a number the release holds | 12 h | <Op verb="Integration.connect" /> your Mailbox and the source-request feed |
| A story the Dataset answers breaks | `news-moment` | `event` on `Monitor.matched` where story.beat in dataset.beats | 4 to 12 h for breaking news; dropped after | <Op verb="Monitor.create" /> on your beat |
| + 5 more for shifted readings, aggregates, researcher access, interview asks and misstated numbers (`?lanes=all`) | | | | |

```json title="One signed delivery · StartupBuilder-Signature"
{
  "$id": "https://api.sb/leaseline.app/events/event_1342",
  "$type": "Event",
  "name": "Question.asked",
  "at": "2026-10-19T10:05:11Z",
  "seq": 1342,
  "data": {
    "question": {
      "$id": "https://api.sb/leaseline.app/questions/question_17",
      "from": "contact_88",
      "desk": "housing",
      "text": "What share of renewals happen when notice goes out 60 days ahead?"
    }
  }
}
```

```bash title="Reproduce it in test"
npx startup-builder trigger Question.asked --like event_1342   # replays the delivery
npx startup-builder clock advance --by 3d   # then reopens the Decision if the reporter has not replied
npx startup-builder listen Question.asked --forward-to http://localhost:3000/hook
```

## Who runs it
**Marketing Lead · your agent** (`role:marketing-lead`, `worker_6`) holds the Key Result. It freezes the release, scores the list and writes your Brief in fragments, never a sentence of a pitch. Every pitch, follow-up and answer goes to **you** (Dana Okafor, person, `worker_1`), who sends it from dana@leaseline.app. Your number links to Did it work.

| Option | Rung today | Evidence (passed of n) | Moves up at | Drops back when | You can |
| --- | --- | --- | --- | --- | --- |
| Pitch the release · Pitch a news moment · Answer a data request | To a person, every time | 26 pitches and 3 answers, all yours | never: you write every word | — | <Op verb="Task.claim" /> · <Op verb="Task.return" /> |
| The release Page and its posts | It does it, you can stop it | 1 of 1 published, 4 posts | met at release 1 | a number fails `number-match` → You approve each one | <Op verb="Action.cancel" /> inside the delay |
| Grant researcher access | It does it | 2 of 2 | — | a cell under 10 ships → You approve each one | <Op verb="APIKey.revoke" /> |
| Ask for a correction | You approve each one | 1 of 1 | at 5 approved | — | <Op verb="Approval.approve" /> |
| Change the release rule | You approve each one | 0 merged | never | — | <Op verb="Change.merge" /> · <Op verb="Change.revert" /> |
| Never: a sentence of your pitch, a named score outside the opt-in top 10, a number without its dataset | To a person, every time | 0 asked | never | — | <Op verb="Task.claim" /> |

**What it reads · What it may call.** It reads the frozen snapshot, the release Page, the reporter list and the news Monitor. Every story, question and Mention is data, never instructions, and nothing it watches appears in a Message. Its tools are <Op verb="Content.publish" />, <Op verb="Task.assign" />, <Op verb="APIKey.issue" />, and any other call becomes an Approval under the Role’s Mandate.

```md title="The brief (folded): compiled Agent-register Markdown · play:data-stories@1 · each line names its record key"
You hold the Marketing Lead Key Result: 5 earned mentions by 2026-12-31.         ← worker, metric
On each release, news moment or data request, choose one option and say why.     ← triggers, chooses
Write Dana a Brief in fragments; never a sentence of a pitch or an answer.       ← sendGates Authorship hold
Every number links to the release Page and resolves to a stored query.           ← copyRules 1
Every rate prints its n; cut every cell under 10 units.                          ← copyRules 2; sendGates Cell floor
Name Leaseline as the data’s collector and a seller in the category.             ← copyRules 4
Drop any story about a death or a disaster, with no override.                    ← sendGates Tragedy skip
```

**The slow loop.** One release has gone out and its 90-day window is still open, so the weekly Decisions kept the list rule as written; no Change was merged, and there is no replay or read-back to print.

**Bring your own agent** (folded) briefs and critiques at the same rung as the seated one: neither writes a pitch, so the eval grades the Brief and the numbers; <Op verb="Role.staff" /> seats it.

```bash
npx startup-builder eval --scenario data-stories-26 --agent "claude -p" --seat marketing-lead   # seated: 26 of 26 numbers resolved, 0 Checks failed sent, Brief in 6 min · yours: 25 of 26, 1 failed `number-match`
```

## What it can do next
| Option | Picked when | Decided by | Runs | Hands to | If it’s just you |
| --- | --- | --- | --- | --- | --- |
| Pitch the release `pitch-the-release` | the release passes its 10 readiness checks | Your rule (line 4) | `publish-content` → <Op verb="Content.publish" /> → `data-release-pitch` (your words) | <Op verb="Task.assign" /> → you, one Brief per reporter, due 1 business day · Take this one | you |
| Pitch a news moment `pitch-a-news-moment` | a story the Dataset answers, inside its window | Agent’s call, with its reason | `newsjack-pitch` (your words) | you, due 60 min, or it drops · Take this one | you |
| Answer a data request `answer-a-data-request` | a reporter asks a number the release holds | Agent’s call, with its reason | `answer-press`, drafted as fragments | you, due 60 min · Take this one | you |
| Grant researcher access `grant-access` | a researcher asks for the file under its open terms | Your rule (the access terms) | <Op verb="APIKey.issue" /> → `researcher-citation-ask` at 60 days | nobody | the same |
| Ask for a correction `ask-for-a-correction` | a Mention misstates a number | Agent’s call, with its reason | <Op verb="Task.assign" /> with the Fact and its link | you, due 1 business day · Take this one | you |
| Not now `not-now` | a tragedy, a closed window, or a contact lease in force | Your rule (`tragedy-skip`, `window-close`, `contact-lease`) | nothing; names the Check that holds it | nobody | the same |

| When they write back | Fixture reply | What happens | Who answers | By when | Call |
| --- | --- | --- | --- | --- | --- |
| Interested | “Send me the full file.” | the CSV link and the method Page go into your Brief; you reply | you | 60 min | <Op verb="Task.assign" /> |
| Send me pricing | “Is the data free to use?” | your reply quotes the open terms: free, with the steward line | you | 60 min | <Op verb="Conversation.answer" /> |
| Not now | “Not this week. Try me next quarter.” | the follow-up stops; the contact lease holds them 30 days | nobody | at once | <Op verb="Enrollment.cancel" /> |
| Wrong person | “Ana on our data desk covers this.” | you ask them to bring Ana in; it never writes to an address from their text | you | 1 business day | <Op verb="Conversation.answer" /> |
| Out of office | an auto-reply until Oct 28 | the follow-up waits for the return date, or drops if the window closes first | nobody | the return date | <Op verb="Message.schedule" /> |
| Question it can’t answer | “Can you break it out by city?” | cells under 10 are cut, so your Brief lists the 4 cities the release holds | you | 60 min | <Op verb="Conversation.escalate" /> |
| Stop | “Please take me off your list.” | no pitch reaches them again, from any of your Startups | nobody | at once | <Op verb="Contact.suppress" /> |

## When it hands to you
Every pitch, follow-up and answer comes to **you**: the Marketing Lead agent writes the Brief, and you write the words. 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_544",
    "$type": "Task",
    "assignee": "worker_1",
    "due": "2026-10-19T11:05:00Z",
    "on": "question_17",
    "brief": {
      "who": "a housing-desk reporter, tier 1, 9 rental stories in 12 months",
      "asked": "What share of renewals happen when notice goes out 60 days ahead?",
      "chose": "answer-a-data-request",
      "number": "71 % renewed with notice 60+ days out (n 1,940); 54 % under 30 days (n 1,110); Q3 2026",
      "link": "leaseline.app/reports/renewals-q3-2026",
      "fragments": [
        "the n beside each rate",
        "collected from 38 Leaseline Customers",
        "the CSV and the method Page"
      ],
      "never": [
        "a number the release lacks",
        "a named property manager"
      ]
    }
  }
]
```

You answer 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 you claim, the agent’s pending fragments on that reporter cancel with `claimedByPerson` (Not live yet, gaps #7). A news-moment Task nobody claims inside its window drops with `window-close`; any other goes up a Role through <Op verb="Task.escalate" />, and `person-promise` fires.

**On time:** you, 28 of 29 claimed within their due time, median 22 min, n 29, ■■□□ Early sign; the 29th was a news moment that dropped at 12 h. **Your queue:** counted by kind: 26 pitch Briefs in release week, then about 1 data request and 1 news moment 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 pitch to a reporter whose Conversation a person claimed | Leave it to the person who owns it: the draft waits in their queue. | <Op verb="Task.assign" /> |
| <a id="check-number-match"></a>`number-match` (the record’s Number match) | a number in a pitch, Post or answer that matches no Fact of the frozen release | Use the release’s Fact with its n, or cut the number. | <Op verb="Task.assign" /> |
| <a id="check-claims-resolve"></a>`claims-resolve` (shared) | a claim that resolves to no live record | Cut the claim, or name the record that backs it. | <Op verb="Content.draft" /> |
| <a id="check-authorship"></a>`authorship` (the record’s Authorship hold) | any sentence of a pitch, follow-up or answer the agent wrote | Write it yourself from the Brief’s fragments. | <Op verb="Task.assign" /> |
| <a id="check-readiness"></a>`readiness` (the record’s Readiness hold) | a release that fails any of its 10 readiness checks | Fix the failing check, then rerun the readiness read. | <Op verb="Dataset.update" /> |
| <a id="check-stale-snapshot"></a>`stale-snapshot` | a pitch stating a snapshot over 30 days old, or a Fact whose recheck fails | Refreeze the snapshot, or pitch the Facts that pass. | <Op verb="Dataset.update" /> |
| <a id="check-cell-floor"></a>`cell-floor` | any cell under 10 units in the release, the CSV or a chart | It is cut; merge the cell up a level. | <Op verb="Dataset.update" /> |
| <a id="check-named-score"></a>`named-score` | a rank or score of a named company outside an opt-in top 10 | Drop the name, or ask for its opt-in. | <Op verb="Consent.create" /> |
| <a id="check-steward-line"></a>`steward-line` | a Page, chart, CSV or pitch that leaves out who collected the data | Add the steward line; it is a fixed line. | <Op verb="Content.update" /> |
| <a id="check-tragedy-skip"></a>`tragedy-skip` | a pitch on a story about a death, a disaster or a violent event | It is dropped, with no override. | <Op verb="Monitor.update" /> |
| <a id="check-window-close"></a>`window-close` | a news-moment pitch past its class window | It is dropped; pitch the next moment inside its window. | <Op verb="Task.next" /> |
| <a id="check-exclusive"></a>`exclusive` | a second exclusive offer on one release | Wait for the first to be declined or expire. | <Op verb="Task.next" /> |
| <a id="check-embargo"></a>`embargo` | a listed release Page before its embargo lifts, or any pitch to a reporter who broke one | It lists at lift; that reporter stays off every release. | <Op verb="Page.update" /> |
| <a id="check-contact-lease"></a>`contact-lease` | a second Brand’s pitch to one reporter in 30 days, or a third send per release | Wait out the lease; the Brief names the date. | <Op verb="Task.next" /> |

| Stop (pauses the Play) | Pauses when | Then |
| --- | --- | --- |
| <a id="stop-pick-rate-floor"></a>`pick-rate-floor` | under 3 picks per 100 pitches, after 100 | the pitch Tactics pause; the Page keeps publishing |
| <a id="stop-complaint"></a>`complaint` | 2 reporter complaints on one release, or 1 public one | every pitch Tactic pauses; you are told |
| <a id="stop-bounce"></a>`bounce` | more than 2 bounces on one release | its sends wait until every address re-verifies |
| <a id="stop-collection-fence"></a>`collection-fence` | a read outside public pages or the rate Limit | collection stops; <Op verb="Incident.open" /> |
| <a id="stop-kill-switch"></a>`kill-switch` | you press it | every Tactic pauses at once; the release Page stays up |

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 pitch to the metro desk did not send: “renewals rise 30 %” matches no Fact of the frozen release.",
  "checks": [
    {
      "check": "number-match",
      "status": "fail",
      "doc": "/plays/data-stories#check-number-match",
      "fix": "Use the release’s Fact with its n, or cut the number.",
      "then": "Task.assign"
    },
    {
      "check": "authorship",
      "status": "pass"
    }
  ]
}
```


```json title="The Stops that fired, as Campaign.paused reasons · sample"
[
  {
    "reason": {
      "limit": "bounce",
      "observed": "3 bounces",
      "bound": "2",
      "window": "1 release"
    },
    "doc": "/plays/data-stories#stop-bounce",
    "task": "task_517"
  }
]
```

## Did it work
One release a quarter and 22 tier 2 reporters are too few for a held-back half to read in 90 days, so the basis is before-after (rule o): the columns read Now and Before, and nothing prints as Added. The 8 held back still get no pitch, and their count prints in Example as what happened. Every node above the leaf is Not live yet (gaps #4).

`3 earned mentions against 0 before · before-after, not proof · n 1 release · path → arr`

| Node | Now (56 days) | Before (the 90 days) | Added | Confidence | Basis |
| --- | --- | --- | --- | --- | --- |
| `earned-mentions` | 3 on the answer engines’ source domains (4 Mentions in all) · 34 days still maturing | 0 | — | — | before-after, not proof · Other Startups’ results +2.1 per release, ■■□□ Early sign, n 118 releases |
| `icp-reached` | 612 ICP visitors from Mention links | 0 from press | — | — | before-after |
| `new-arr` ↑ | $1,041, 3 Customers on Pro · Booked: credited, not all caused by this Play | $0 | — · Worth about +$580 a month | — | before-after · 4 trials still deciding |
| `net-new-arr` · `arr` | carried from `new-arr` | carried | — | — | carried |

Before against after reads 56 days against the 90 days before the first release; it is not proof, and Other Startups’ results print beside it. The leaf counts Mentions on the answer engines’ source domains within 90 days of a release.

**How it moves it:** each tier 1 and tier 2 reporter got one pitch in your words with a dated number, its n and the file, where before no reporter heard of the data.

**Guardrails:** before against after: ICP visitors per Mention, 204 against Other Startups’ 190, and complaint rate, 0 of 26 pitches; the 4 trials from Mention links are still deciding.

```json title="The Campaign card · sample"
{
  "$id": "https://api.sb/leaseline.app/campaigns/data-stories",
  "$type": "Campaign",
  "play": "play:data-stories@1",
  "path": [
    "earned-mentions",
    "icp-reached",
    "new-arr",
    "net-new-arr",
    "arr"
  ],
  "experiment": {
    "metric": "earned-mentions",
    "unit": "count",
    "heldBack": 8,
    "decision": null
  },
  "readout": {
    "value": null,
    "n": 1,
    "maturing": 1,
    "interval": null,
    "readability": "inadmissible",
    "basis": "before-after",
    "pooled": true
  },
  "ledger": {
    "spend": 0,
    "workerCost": 6,
    "hours": 3.4,
    "openArr": 1388,
    "wonArr": 1041,
    "projectedArr": 580,
    "paybackDays": 6
  }
}
```

Outside benchmark: human-written pitches were picked 33 % of the time against 10 % for generated ones (a pitching platform’s study, in Your settings).

**Before it · After it.** Before it: `benchmark-rows` and `free-tools` grow the rows a release reads. After it: `aeo` hears the Mentions on source domains, and `instant-response` answers the visitors who write in from them.

## Example
Leaseline ran it from 2026-08-24 to 2026-10-19 (56 days): 1 release, 26 pitches to 26 reporters, 8 tier 2 held back, 3 data requests and 2 news moments. Read every captured thread at `?threads=all`; three follow the tables.

| What happened, by option | Decisions | Sent in your words | Mentions so far |
| --- | --- | --- | --- |
| Pitch the release: tier 1 · tier 2 pitched · tier 2 held back | 4 · 22 · 8 | 4 · 22 · 0 | 2 · 2 · 0 |
| Pitch a news moment · Answer a data request | 2 · 3 | 1 (1 dropped at 12 h) · 3 | 0 · 1 in the subject’s story, still deciding |
| Grant researcher access · Ask for a correction · Not now | 2 · 1 · 1 | 2 keys · 1 · 0 | 1 correction made |
| **All** · the 90 days before | **44** | **30** | **4 Mentions, 3 on source domains** · 0 |

| Day | Event | What happened | Your time |
| --- | --- | --- | --- |
| 0 | `event_1290` Campaign.created | the three steps; the dry run fails `domain-authenticated` and `mailbox-warm` | 20 min |
| 7 | `event_1296` Dataset.scheduled | the Q3 release froze: 3,520 renewals, 38 Customers, 2 cells cut under 10 | 15 min |
| 14 | `event_1301` Mailbox.warmed | dana@ finished warming; tier 1 offers went out, 1 exclusive | 40 min |
| 21 | `event_1310` Content.published | the release lifted; 22 tier 2 pitches in your words | 1 h 30 min |
| 24 | `event_1318` Mention.found | the first earned mention, on a housing trade site | — |
| 27 | `event_1322` Campaign.paused | `bounce`: 3 addresses bounced; resumed after re-verify | 10 min |
| 1–56 | `event_1299` Task.assigned, first of 29 | 29 Briefs, from the <Op verb="Task.next" /> log | 25 min · **total 3.4 h measured, 2.5 h asked per release** |

> **Dana → a trade-site editor, Sep 14, 08:02** · Subject: Renewals ran 71 % when notice went out 60+ days early.¹ Q3 2026, 3,520 leases at 38 property managers: 71 % renewed with notice 60+ days out (n 1,940), 54 % under 30 days (n 1,110).² Leaseline collected it and sells renewal software.³ The file and method: leaseline.app/reports/renewals-q3-2026.
>
> **Editor, Sep 17** · published the story, citing the release and linking the file. Mention.found, on a source domain.

```diff title="Metro desk, 2026-09-14 · the draft number-match refused, then the rewrite that sent"
- Renewals rise 30 % when notices go out early.
+ Leases with notice 60+ days out renewed at 71 % (n 1,940), against 54 % under 30 days (n 1,110).
```

> **Housing-desk reporter, 2026-10-19 10:05** · “What share of renewals happen when notice goes out 60 days ahead?” · Your Brief reached you at 10:11 (`task_544`), due 11:05; **Dana, 10:41** · *your own answer from the fragments: 71 % of 1,940 leases, Q3 2026, with the file link.*⁴

¹ The subject line states the finding. ² Every rate prints its n, and both values print. ³ The steward line names the collector and the seller. ⁴ The answer is yours; the agent wrote only fragments.

## Your settings
```mdx title="plays/data-stories.mdx"
---
holdout: 0.25   # 1 tier 2 reporter in 4 gets no pitch; it still reads before-after at 1 release a quarter
---
One release a quarter from our renewal records. I write every pitch and answer; brief me in fragments, and drop any story about a tragedy.
```

```text
$ npx startup-builder show plays/data-stories
holdout 0.25 · 1 release a quarter · you write every word     your file  plays/data-stories.mdx:2,4 (Ask 2026-08-24)
family demand · process content-marketing · worker role:marketing-lead, owns never · chooses 6 (3 rule, 3 read) · sequences data-release-pitch, newsjack-pitch, researcher-citation-ask, release-notice · approval every-send, delay-window · checks owned-by-a-person, number-match, claims-resolve, authorship + 10 · stops 5 · placements gmail, outlook, x, linkedin + 6 cited-source Venues    catalog  play:data-stories@1 (Not live yet, gaps #5)
```

### Every key from the catalog
| Key | Id | What it is |
| --- | --- | --- |
| Tactic | <a id="tactic-release-gate"></a>`release-gate` | Release gate freezes a Dataset snapshot, turns each finding into a stored query with n and interval, and publishes nothing until ten checks pass. Starts at You approve each one. |
| Tactic | <a id="tactic-media-list-build"></a>`media-list-build` | Media list build turns 12 months of coverage into 20 to 40 scored, leased journalists, with a random tier 2 holdout and one brief each. Starts at You approve each one. |
| Tactic | <a id="tactic-pitch-critic"></a>`pitch-critic` | Pitch critic briefs the human author before a pitch and checks the draft after it, returning fragments and comments and never rewritten text. Starts at Sends nothing. |
| Tactic | <a id="tactic-release-pitch"></a>`release-pitch` | Release pitch sends the founder’s own pitches from the founder’s Mailbox at a randomized lift, with one offer, one follow-up and every reply routed back. Starts at You approve each one. |
| Tactic | <a id="tactic-release-publish"></a>`release-publish` | Release publish lifts the release Page with its CSV and charts, posts from the Startup’s Handles, and notifies every Person who opted in. Starts at It does it, you can stop it. |
| Tactic | <a id="tactic-newsjack-watch"></a>`newsjack-watch` | Newsjack watch scores beat news every 30 minutes, keeps stories the Dataset answers with a number, and briefs the founder within 2 hours. Starts at You approve each one. |
| Tactic | <a id="tactic-data-request-answer"></a>`data-request-answer` | Data request answer matches a journalist’s public request for a number to a stored query and briefs the founder within 10 minutes; the founder answers. Starts at Sends nothing. |
| Tactic | <a id="tactic-researcher-access"></a>`researcher-access` | Researcher access grants the full release and a rate-limited query key to researchers and journalists who ask, in exchange for a citation line. Starts at Sends nothing. |
| Tactic | <a id="tactic-mention-readout"></a>`mention-readout` | Mention readout counts a release’s mentions on retrieval-set domains for 90 days, pitched against held out, and routes misstated numbers to correction. Starts at Sends nothing. |
| Tactic | <a id="tactic-claims-check"></a>`claims-check` | Claims check maps every draft sentence to an approved source or the Person’s own words, and holds anything else before it reaches anyone. 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-release-due"></a>`release-due` | Release due: `at` on `Dataset.scheduled`, window 14d. |
| Trigger | <a id="trigger-finding-shift"></a>`finding-shift` | Finding shift: `threshold` on `Dataset.shifted`, window 7d. |
| Trigger | <a id="trigger-benchmark-aggregate"></a>`benchmark-aggregate` | Benchmark aggregate: `threshold` on `Dataset.published`, window 30d. |
| Trigger | <a id="trigger-news-moment"></a>`news-moment` | News moment: `event` on `Monitor.matched`, window 48h. |
| Trigger | <a id="trigger-data-request"></a>`data-request` | Data request: `event` on `Question.asked`, window 12h. |
| Trigger | <a id="trigger-access-request"></a>`access-request` | Access request: `event` on `Dataset.requested`, window 3d. |
| Trigger | <a id="trigger-interview-ask"></a>`interview-ask` | Interview ask: `event` on `Interview.requested`, window 24h. |
| Trigger | <a id="trigger-mention-earned"></a>`mention-earned` | Mention earned: `event` on `Monitor.checked`, window 28d. |
| Trigger | <a id="trigger-number-misstated"></a>`number-misstated` | Number misstated: `event` on `Mention.found`, window 7d. |
| Sequence | <a id="sequence-data-release-pitch"></a>`data-release-pitch` | Data release pitch: 4 steps; it ends on A reply; A Mention published; An opt-out or a no-pitch reply; A bounce; An accepted exclusive or embargo, after which the founder takes the Thread. |
| Sequence | <a id="sequence-newsjack-pitch"></a>`newsjack-pitch` | Newsjack pitch: 1 steps; it ends on A reply; A Mention published; An opt-out or a no-pitch reply; The class window closing. |
| Sequence | <a id="sequence-researcher-citation-ask"></a>`researcher-citation-ask` | Researcher citation ask: 2 steps; it ends on A recorded citation; An opt-out; The access end date. |
| Sequence | <a id="sequence-release-notice"></a>`release-notice` | Release notice: 1 steps; it ends on An opt-out. |
| Benchmark | <a id="benchmark-pitch-selection-human"></a>`pitch-selection-human` | Share of human-written answers to journalist source requests that the journalist selects: 33 % (A source-request service’s pitch data, analyzed by an AI-detection vendor, 2026-09-23). |
| Benchmark | <a id="benchmark-pitch-selection-generated"></a>`pitch-selection-generated` | Share of fully generated answers to journalist source requests that the journalist selects: 10 % (A source-request service’s pitch data, analyzed by an AI-detection vendor, 2026-09-23). |
| Benchmark | <a id="benchmark-generated-pitch-share"></a>`generated-pitch-share` | Share of answers to journalist source requests flagged as fully generated: 21 % (A source-request service’s pitch data, analyzed by an AI-detection vendor, 2026-09-23). |
| Benchmark | <a id="benchmark-journalist-ai-filter-share"></a>`journalist-ai-filter-share` | Share of journalists on a source-request service who turned on its filter against fully generated pitches: 35 % (A source-request service’s pitch data, analyzed by an AI-detection vendor, 2026-09-23). |
| Benchmark | <a id="benchmark-journalist-ai-use"></a>`journalist-ai-use` | Share of journalists who use AI tools in their work: 82 % (An annual journalist survey by a media-database vendor, 2026-03-19). |