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: the Workflows a Worker may choose between and the Key Result it moves, compiled to one Campaign. SalesBuilder compiles it, and show traces every key.
---
audience: small-property-managers
sequence: cold-email-thread-founder
---
Write as Dana: one fresh reason from a public source, one ask, plain text. $ 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 SalesBuilderleaseline.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.lockThe file
| Key | On the page | Compiles to | In Leaseline’s file |
|---|---|---|---|
family | family | the hub key, by default from play: | demand, from play: |
metric | Moves | the Campaign’s keyResult: one leaf Metric whose parent chain reaches arr Checked by S1. | positive-reply-rate → new-leads → new-arr → net-new-arr → arr, from play: |
process | Automation | the workflows/ Process that runs the Campaign | outbound, from play: |
audience | Who it goes to | the Campaign’s audience: a List id or an inline query Checked by S3. | small-property-managers, from plays/ |
on | When it starts | a Trigger in the hub’s Event-selector grammar, such as Offer.updated where terms changed Checked by S5. | Review.published, Monitor.checked, Conversation.answered +4 unregistered, from play: |
holdout | Held back | an Experiment: unit contact, control 0.25, the leaf’s minN, its window and Metric Checked by S8. | unit contact, control 0.25, from compile default |
worker | Who runs it | POST /{namespace}/roles/{role}/staff on the seat that drafts and sends | role:prospector (SDR), from play: |
sequence | Follow-ups | one Sequence by id Checked by S2. | cold-email-thread-founder, from plays/ |
heardFrom | How they heard | a funnels/ key that records where each new Lead heard of you | signup.heardFrom, from funnels/ |
approval | Approval | the starting rung of the Campaign’s sends; a file tightens it, never loosens it | every-send, from play: |
budget | Budget | { amount, from the Demand Budget, signed by POST /{namespace}/mandates/authorize Checked by S4. | { amount: 0, currency: USD, per: month }, from the Ask, |
guards | Checks | per-send Checks: a failure is a 422 whose checks[] carry a code, doc, fix and then Checked by S2. | 6: use-when, baseline, domain-authenticated +3, from play: |
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: |
play: is the catalog Play this file inherits; versions are Not live yet (gaps #5).
Checks
| Check | It holds | What dev prints |
|---|---|---|
| S1 | The Metric’s parent chain reaches arr: the Result card’s path. | metrics/ |
| S2 | Every send passes a code-tier Check before it goes. | sequences/ |
| S3 | Lists leave out suppressed Contacts and read Consent. | audiences/ |
| S4 | The budget fits the Mandate, or opens an Approval. | plays/ |
| S5 | Every on: names a registered Event. | plays/ |
| S7 | Every body rule compiles to an Evaluator that runs as POST /{namespace}/actions/{action}/evaluate on each draft before its window opens. | plays/ |
| S8 | An Experiment that varies an Offer’s Price assigns by market or by time. | experiments/ |
Refusals
| Code | Fix, then the call that clears it |
|---|---|
approved-pages | Cut the sentence, or publish the Page that says it. Then POST /{namespace}/sites/{site}/publish. |
claim | Cut the line, or cite the Offer term that grants it. Then POST /{namespace}/conversations/{conversation}/answer. |
claims-resolve | Cut the claim, or name the record that backs it. Then POST /{namespace}/content/draft. |
consent-stop | Ask once for a yes to the other Startup. Then POST /{namespace}/messages/{message}/schedule. |
content-evals | Fix the lines the Evaluator lists, then publish again. Then POST /{namespace}/content/draft. |
customer-collision | Send it to their CSM instead: the Contact leaves the List. Then POST /{namespace}/leads/{lead}/assign. |
domain-authenticated | Add the 3 DNS records shown; it passes once they resolve, often within an hour. Then POST /{namespace}/domains/{domain}/authenticate. |
fact-check | Cut the claim, or name the record that backs it. Then POST /{namespace}/content/draft. |
flake | Fix the profile's tool loading; that night reads unmeasured, never a drop. Then POST /{namespace}/actions/{action}/evaluate. |
intake | Read it on saves, clicks or signups, then hand it off again. Then POST /{namespace}/experiments/{experiment}/conclude. |
mailbox-warm | Wait for the warm-up to finish, 14 days from day 0; warm intros keep running. Then POST /{namespace}/mailboxes/{mailbox}/warm. |
no-consent | Record the Contact’s yes, with where they gave it, before the first send. Then POST /{namespace}/consents. |
owned-by-a-person | Leave it to the person who owns it: the draft waits in their queue. Then POST /{namespace}/tasks/{task}/assign. |
pre-registration | Start the Experiment first; it locks before the first offer. Then POST /{namespace}/experiments/{experiment}/start. |
price | Get the discount approved, or quote at the floor. Then POST /{namespace}/approvals/{approval}/approve. |
price-split-by-buyer | Assign the Price test by market or by time. Then POST /{namespace}/experiments/{experiment}/start. |
provenance-hold | Cut the line, or cite its timestamp in the transcript. Then POST /{namespace}/content/draft. |
readiness | Grade the stance in positioning-tournament first, then start the Kit again. Then POST /{namespace}/experiments/{experiment}/conclude. |
spam-brake | Send to a smaller List, or wait an hour for the rate to fall. Then POST /{namespace}/messages/{message}/broadcast. |
suppressed-contact | Leave them out: run the List again, which drops every suppressed Contact. Then POST /{namespace}/collections/query. |
spend-limit | Raise the Limit, or wait for the month to reset. Then POST /{namespace}/limits/{limit}/raise. |
Calls
You, to your agent
Pick our Plays and compile plays/cold-email.mdx to a Campaign, as a dry run.
curl -X POST "https://api.sb/leaseline.app/play-mixes/compile?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"startup":"leaseline.app"}'
# Not live yet (gaps #2)
curl -X POST "https://api.sb/leaseline.app/campaigns?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"play":"cold-email","budget":{"amount":0,"currency":"USD"}}'
# Not live yet (gaps #2)
curl -X POST "https://api.sb/leaseline.app/campaigns/campaign_1/start?mode=try" \
-H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"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
npx startup-builder do PlayMix.compile --json '{"startup":"leaseline.app"}' --try
npx startup-builder do Campaign.create --json '{"play":"cold-email","budget":{"amount":0,"currency":"USD"}}' --try # Not live yet (gaps #2)
npx startup-builder do Campaign.start --json '{"campaign":"campaign_1"}' --try # Not live yet (gaps #2){
"name": "do",
"arguments": {
"code": "await $.PlayMix.compile({ startup: 'leaseline.app' }, { mode: 'try' }) // 0 Customers · 3 h a week · $0 → three Plays\nawait $.Campaign.create({ play: 'cold-email', budget: { amount: 0, currency: 'USD' } }, { mode: 'try' }) // the file, compiled · Not live yet, gaps #2\nreturn await $.Campaign.start({ campaign: 'campaign_1' }, { mode: 'try' }) // stores nothing · Not live yet, gaps #2",
"mode": "try"
}
}Response200
application/json{
"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"
}
}
sample · 2 Not live yet| Call | What it does |
|---|---|
POST /{namespace}/play-mixes/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. |
POST /{namespace}/experiments/{experiment}/start | Start opens an Experiment, splits traffic across its variants, and begins measuring its primary and guardrail Metrics. Emits Experiment.started. |
POST /{namespace}/experiments/{experiment}/conclude | Conclude ends an Experiment with a decision to ship, kill, or iterate, recorded as a Decision. Emits Experiment.concluded. |
POST /{namespace}/actions/{action}/evaluate | Evaluate grades an Action’s output against a rubric and records the verdict as an Event. Emits Action.evaluated. |
POST /{namespace}/processes/{process}/pause | Pause stops a Process from starting new Actions; Actions already in progress finish. Emits Process.paused. |
| Not live yet | Use today |
|---|---|
Campaign.start | Gaps #2. Today: POST /{namespace}/campaigns and POST /{namespace}/processes/{process}/pause run today; a Stop reads as a Process pause. |
Campaign.pause | Gaps #2. Today: POST /{namespace}/campaigns and POST /{namespace}/processes/{process}/pause run today; a Stop reads as a Process pause. |
Compiled, not written: edit the records it reads (compile table; cap), then regenerate. Every step runs from a sentence or a line of code. In your words: the CRM words you know, and where each lives here.