URL: /code/files/plays · Id: `page:/code/files/plays`

# The plays/ folder: one file and what it compiles to

> What one plays/ file holds, what each key compiles to, and the checks that read it.

One `plays/` file is one [Play](https://startupbuilder.dev/business/nouns/play): the [Workflows](https://startupbuilder.dev/business/nouns/workflow) a [Worker](https://startupbuilder.dev/business/nouns/worker) may choose between and the [Key Result](https://startupbuilder.dev/business/nouns/key-result) it moves, compiled to one [Campaign](https://startupbuilder.dev/business/nouns/campaign). SalesBuilder compiles it, and `show` traces every key.

```mdx title="plays/cold-email.mdx"
---
audience: small-property-managers
sequence: cold-email-thread-founder
---
Write as Dana: one fresh reason from a public source, one ask, plain text.
```

```text title="show"
$ npx startup-builder show plays/cold-email.mdx
family     demand                                                                    play:cold-email@1
metric     positive-reply-rate → new-leads → new-arr → net-new-arr → arr             play:cold-email@1
process    outbound                                                                  play:cold-email@1
audience   small-property-managers                                                   plays/cold-email.mdx:2 (Ask, 2026-10-19)
on         Review.published, Monitor.checked, Conversation.answered +4 unregistered  play:cold-email@1, 8 Triggers
holdout    unit contact, control 0.25                                                compile default
worker     role:prospector (SDR)                                                     play:cold-email@1
sequence   cold-email-thread-founder                                                 plays/cold-email.mdx:3 (Ask, 2026-10-19)
heardFrom  signup.heardFrom                                                          funnels/signup.mdx:3
approval   every-send                                                                play:cold-email@1
budget     { amount: 0, currency: USD, per: month }                                  the Ask, 2026-10-19 (no budget)
guards     6: use-when, baseline, domain-authenticated +3                            play:cold-email@1
stops      12: bounce, shadow-complaint, provider-spam +9                            play:cold-email@1
body       1 rule → 1 Evaluator, run as Action.evaluate on each draft                plays/cold-email.mdx:5
owner      SalesBuilder
```

```text title="leaseline.app"
leaseline.app/
├── plays/   ← this folder
│   ├── cold-email.mdx
│   ├── warm-intros.mdx
│   ├── signup-interviews.mdx
│   ├── lead-reactivation.mdx
│   └── events-and-webinars.mdx
├── audiences/
│   ├── small-property-managers.mdx
│   ├── lapsed-leads.mdx
│   └── renewal-season.mdx
├── sequences/
│   └── cold-email-thread-founder.mdx
├── funnels/
│   └── signup.mdx
├── workflows/
│   └── outbound.mdx
├── agents/
│   ├── sales/prospector.mdx
│   └── marketing/content-lead.mdx
├── systems/
│   └── sequences.mdx
├── domains/
│   └── leaseline.app.mdx
├── offers/
│   ├── pro.mdx
│   └── team.mdx
├── content/
│   └── webinars/60-day-notices.mdx
├── metrics/
│   ├── q4.mdx
│   └── positive-reply-rate.mdx
└── startups.lock
```

## The file
| Key | On the page | Compiles to | In Leaseline’s file |
| --- | --- | --- | --- |
| `family` | family | the hub key, by default from `play:<id>@N` | demand, from `play:cold-email@1` |
| `metric` | Moves | the Campaign’s `keyResult`: one leaf Metric whose parent chain reaches `arr` Checked by [S1](/code/checks#s1). | positive-reply-rate → new-leads → new-arr → net-new-arr → arr, from `play:cold-email@1` |
| `process` | Automation | the `workflows/` Process that runs the Campaign | outbound, from `play:cold-email@1` |
| `audience` | Who it goes to | the Campaign’s `audience`: a List id or an inline query Checked by [S3](/code/checks#s3). | small-property-managers, from `plays/cold-email.mdx:2 (Ask, 2026-10-19)` |
| `on` | When it starts | a Trigger in the hub’s Event-selector grammar, such as `Offer.updated where terms changed` Checked by [S5](/code/checks#s5). | Review.published, Monitor.checked, Conversation.answered +4 unregistered, from `play:cold-email@1, 8 Triggers` |
| `holdout` | Held back | an Experiment: unit contact, control 0.25, the leaf’s `minN`, its window and Metric Checked by [S8](/code/checks#s8). | unit contact, control 0.25, from `compile default` |
| `worker` | Who runs it | <Op verb="Role.staff" /> on the seat that drafts and sends | role:prospector (SDR), from `play:cold-email@1` |
| `sequence` | Follow-ups | one Sequence by id Checked by [S2](/code/checks#s2). | cold-email-thread-founder, from `plays/cold-email.mdx:3 (Ask, 2026-10-19)` |
| `heardFrom` | How they heard | a `funnels/` key that records where each new Lead heard of you | signup.heardFrom, from `funnels/signup.mdx:3` |
| `approval` | Approval | the starting rung of the Campaign’s sends; a file tightens it, never loosens it | every-send, from `play:cold-email@1` |
| `budget` | Budget | `{ amount, currency, per }` from the Demand Budget, signed by <Op verb="Mandate.authorize" /> Checked by [S4](/code/checks#s4). | \{ amount: 0, currency: USD, per: month \}, from `the Ask, 2026-10-19 (no budget)` |
| `guards` | Checks | per-send Checks: a failure is a 422 whose `checks[]` carry a code, `doc`, `fix` and `then` Checked by [S2](/code/checks#s2). | 6: use-when, baseline, domain-authenticated +3, from `play:cold-email@1` |
| `stops` | Stops | metric-based Stops become the Experiment’s guardrails, count- and time-based ones the seat’s limits; crossing one pauses the Play | 12: bounce, shadow-complaint, provider-spam +9, from `play:cold-email@1` |

`play:cold-email@1` is the catalog Play this file inherits; versions are Not live yet (gaps #5).

## Checks
| Check | It holds | What dev prints |
| --- | --- | --- |
| [S1](/code/checks#s1) | The Metric’s parent chain reaches `arr`: the Result card’s path. | `metrics/q4.mdx:5  S1  keyResults[1].metric: demo-requests has no parent chain to arr. Set its parent: opened-deals-and-trials.` |
| [S2](/code/checks#s2) | Every send passes a code-tier Check before it goes. | `sequences/cold-email-thread-founder.mdx:5  S2  steps.second sends with no code-tier Check. Add guards: [customer-collision].` |
| [S3](/code/checks#s3) | Lists leave out suppressed Contacts and read Consent. | `audiences/lapsed-leads.mdx:4  S3  exclude: drops suppressed. Suppressed Contacts never enter a List: put it back.` |
| [S4](/code/checks#s4) | The budget fits the Mandate, or opens an Approval. | `plays/lead-reactivation.mdx:5  S4  budget: 48 USD a month is over Dana’s Mandate of 0. Sign it with Mandate.authorize, or the first send waits on an Approval.` |
| [S5](/code/checks#s5) | Every `on:` names a registered Event. | `plays/lead-reactivation.mdx:4  S5  on: Lead.lapsed is not a registered Event. Did you mean Offer.trialed?` |
| [S7](/code/checks#s7) | Every body rule compiles to an Evaluator that runs as <Op verb="Action.evaluate" /> on each draft before its window opens. | `plays/cold-email.mdx:5  S7  body: “sound friendly” compiles to no Evaluator. Write what a draft must hold or must leave out.` |
| [S8](/code/checks#s8) | An Experiment that varies an Offer’s Price assigns by market or by time. | `experiments/pro-price.mdx:3  S8  varies: offers/pro.mdx price, assigned by contact. Assign by market or by time.` |

## Refusals
| Code | Fix, then the call that clears it |
| --- | --- |
| [`approved-pages`](/code/refusals#check-approved-pages) | Cut the sentence, or publish the Page that says it. Then <Op verb="Site.publish" />. |
| [`claim`](/code/refusals#check-claim) | Cut the line, or cite the Offer term that grants it. Then <Op verb="Conversation.answer" />. |
| [`claims-resolve`](/code/refusals#check-claims-resolve) | Cut the claim, or name the record that backs it. Then <Op verb="Content.draft" />. |
| [`consent-stop`](/code/refusals#check-consent-stop) | Ask once for a yes to the other Startup. Then <Op verb="Message.schedule" />. |
| [`content-evals`](/code/refusals#check-content-evals) | Fix the lines the Evaluator lists, then publish again. Then <Op verb="Content.draft" />. |
| [`customer-collision`](/code/refusals#check-customer-collision) | Send it to their CSM instead: the Contact leaves the List. Then <Op verb="Lead.assign" />. |
| [`domain-authenticated`](/code/refusals#check-domain-authenticated) | Add the 3 DNS records shown; it passes once they resolve, often within an hour. Then <Op verb="Domain.authenticate" />. |
| [`fact-check`](/code/refusals#check-fact-check) | Cut the claim, or name the record that backs it. Then <Op verb="Content.draft" />. |
| [`flake`](/code/refusals#check-flake) | Fix the profile's tool loading; that night reads unmeasured, never a drop. Then <Op verb="Action.evaluate" />. |
| [`intake`](/code/refusals#check-intake) | Read it on saves, clicks or signups, then hand it off again. Then <Op verb="Experiment.conclude" />. |
| [`mailbox-warm`](/code/refusals#check-mailbox-warm) | Wait for the warm-up to finish, 14 days from day 0; warm intros keep running. Then <Op verb="Mailbox.warm" />. |
| [`no-consent`](/code/refusals#check-no-consent) | Record the Contact’s yes, with where they gave it, before the first send. Then <Op verb="Consent.create" />. |
| [`owned-by-a-person`](/code/refusals#check-owned-by-a-person) | Leave it to the person who owns it: the draft waits in their queue. Then <Op verb="Task.assign" />. |
| [`pre-registration`](/code/refusals#check-pre-registration) | Start the Experiment first; it locks before the first offer. Then <Op verb="Experiment.start" />. |
| [`price`](/code/refusals#check-price) | Get the discount approved, or quote at the floor. Then <Op verb="Approval.approve" />. |
| [`price-split-by-buyer`](/code/refusals#check-price-split-by-buyer) | Assign the Price test by market or by time. Then <Op verb="Experiment.start" />. |
| [`provenance-hold`](/code/refusals#check-provenance-hold) | Cut the line, or cite its timestamp in the transcript. Then <Op verb="Content.draft" />. |
| [`readiness`](/code/refusals#check-readiness) | Grade the stance in positioning-tournament first, then start the Kit again. Then <Op verb="Experiment.conclude" />. |
| [`spam-brake`](/code/refusals#check-spam-brake) | Send to a smaller List, or wait an hour for the rate to fall. Then <Op verb="Message.broadcast" />. |
| [`suppressed-contact`](/code/refusals#check-suppressed-contact) | Leave them out: run the List again, which drops every suppressed Contact. Then <Op verb="Collection.query" />. |
| [`spend-limit`](/code/refusals#stop-spend-limit) | Raise the Limit, or wait for the month to reset. Then <Op verb="Limit.raise" />. |

## Calls

```text title="plays/ compiled · Ask"
Pick our Plays and compile plays/cold-email.mdx to a Campaign, as a dry run.
```

```ts title="plays/ compiled · SDK"
import { $ } from 'startup-builder'

await $.PlayMix.compile({ startup: 'leaseline.app' }, { mode: 'try' })   // 0 Customers · 3 h a week · $0 → three Plays
await $.Campaign.create({ play: 'cold-email', budget: { amount: 0, currency: 'USD' } }, { mode: 'try' })   // the file, compiled · Not live yet, gaps #2
return await $.Campaign.start({ campaign: 'campaign_1' }, { mode: 'try' })   // stores nothing · Not live yet, gaps #2
```

```json title="plays/ compiled · Response · sample"
{
  "campaign": {
    "$id": "https://api.sb/leaseline.app/campaigns/campaign_1",
    "$type": "Campaign",
    "budget": {
      "amount": 29,
      "currency": "USD"
    },
    "startsAt": "2026-10-22T12:00:00Z"
  },
  "action": {
    "$id": "https://api.sb/leaseline.app/actions/action_1",
    "operation": "Campaign.create",
    "status": "done"
  }
}
```

| Call | What it does |
| --- | --- |
| <a id="playmix-compile"></a><Op verb="PlayMix.compile" /> | Compile builds the next Play Mix from the Demand Budget, each Play’s ledger and last week’s Acquisitions, and records the diff. Emits [`PlayMix.compiled`](/code/events#playmix-compiled). |
| <a id="experiment-start"></a><Op verb="Experiment.start" /> | Start opens an Experiment, splits traffic across its variants, and begins measuring its primary and guardrail Metrics. Emits [`Experiment.started`](/code/events#experiment-started). |
| <a id="experiment-conclude"></a><Op verb="Experiment.conclude" /> | Conclude ends an Experiment with a decision to ship, kill, or iterate, recorded as a Decision. Emits [`Experiment.concluded`](/code/events#experiment-concluded). |
| <a id="action-evaluate"></a><Op verb="Action.evaluate" /> | Evaluate grades an Action’s output against a rubric and records the verdict as an Event. Emits [`Action.evaluated`](/code/events#action-evaluated). |
| <a id="process-pause"></a><Op verb="Process.pause" /> | Pause stops a Process from starting new Actions; Actions already in progress finish. Emits [`Process.paused`](/code/events#process-paused). |

| Not live yet | Use today |
| --- | --- |
| `Campaign.start` | [Gaps #2](/code/reference#gap-campaign-runs-a-play). Today: <Op verb="Campaign.create" /> and <Op verb="Process.pause" /> run today; a Stop reads as a Process pause. |
| `Campaign.pause` | [Gaps #2](/code/reference#gap-campaign-runs-a-play). Today: <Op verb="Campaign.create" /> and <Op verb="Process.pause" /> run today; a Stop reads as a Process pause. |