Skip to content

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.

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

KeyOn the pageCompiles toIn Leaseline’s file
familyfamilythe hub key, by default from play:<id>@Ndemand, from play:cold-email@1
metricMovesthe 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:cold-email@1
processAutomationthe workflows/ Process that runs the Campaignoutbound, from play:cold-email@1
audienceWho it goes tothe Campaign’s audience: a List id or an inline query Checked by S3.small-property-managers, from plays/cold-email.mdx:2 (Ask, 2026-10-19)
onWhen it startsa 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:cold-email@1, 8 Triggers
holdoutHeld backan 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
workerWho runs itPOST /{namespace}/roles/{role}/staff on the seat that drafts and sendsrole:prospector (SDR), from play:cold-email@1
sequenceFollow-upsone Sequence by id Checked by S2.cold-email-thread-founder, from plays/cold-email.mdx:3 (Ask, 2026-10-19)
heardFromHow they hearda funnels/ key that records where each new Lead heard of yousignup.heardFrom, from funnels/signup.mdx:3
approvalApprovalthe starting rung of the Campaign’s sends; a file tightens it, never loosens itevery-send, from play:cold-email@1
budgetBudget{ amount, currency, per } from the Demand Budget, signed by POST /{namespace}/mandates/authorize Checked by S4.{ amount: 0, currency: USD, per: month }, from the Ask, 2026-10-19 (no budget)
guardsChecksper-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:cold-email@1
stopsStopsmetric-based Stops become the Experiment’s guardrails, count- and time-based ones the seat’s limits; crossing one pauses the Play12: 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

CheckIt holdsWhat dev prints
S1The 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.
S2Every 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].
S3Lists 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.
S4The 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.
S5Every on: names a registered Event.plays/lead-reactivation.mdx:4 S5 on: Lead.lapsed is not a registered Event. Did you mean Offer.trialed?
S7Every body rule compiles to an Evaluator that runs as POST /{namespace}/actions/{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.
S8An 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

CodeFix, then the call that clears it
approved-pagesCut the sentence, or publish the Page that says it. Then POST /{namespace}/sites/{site}/publish.
claimCut the line, or cite the Offer term that grants it. Then POST /{namespace}/conversations/{conversation}/answer.
claims-resolveCut the claim, or name the record that backs it. Then POST /{namespace}/content/draft.
consent-stopAsk once for a yes to the other Startup. Then POST /{namespace}/messages/{message}/schedule.
content-evalsFix the lines the Evaluator lists, then publish again. Then POST /{namespace}/content/draft.
customer-collisionSend it to their CSM instead: the Contact leaves the List. Then POST /{namespace}/leads/{lead}/assign.
domain-authenticatedAdd the 3 DNS records shown; it passes once they resolve, often within an hour. Then POST /{namespace}/domains/{domain}/authenticate.
fact-checkCut the claim, or name the record that backs it. Then POST /{namespace}/content/draft.
flakeFix the profile's tool loading; that night reads unmeasured, never a drop. Then POST /{namespace}/actions/{action}/evaluate.
intakeRead it on saves, clicks or signups, then hand it off again. Then POST /{namespace}/experiments/{experiment}/conclude.
mailbox-warmWait for the warm-up to finish, 14 days from day 0; warm intros keep running. Then POST /{namespace}/mailboxes/{mailbox}/warm.
no-consentRecord the Contact’s yes, with where they gave it, before the first send. Then POST /{namespace}/consents.
owned-by-a-personLeave it to the person who owns it: the draft waits in their queue. Then POST /{namespace}/tasks/{task}/assign.
pre-registrationStart the Experiment first; it locks before the first offer. Then POST /{namespace}/experiments/{experiment}/start.
priceGet the discount approved, or quote at the floor. Then POST /{namespace}/approvals/{approval}/approve.
price-split-by-buyerAssign the Price test by market or by time. Then POST /{namespace}/experiments/{experiment}/start.
provenance-holdCut the line, or cite its timestamp in the transcript. Then POST /{namespace}/content/draft.
readinessGrade the stance in positioning-tournament first, then start the Kit again. Then POST /{namespace}/experiments/{experiment}/conclude.
spam-brakeSend to a smaller List, or wait an hour for the rate to fall. Then POST /{namespace}/messages/{message}/broadcast.
suppressed-contactLeave them out: run the List again, which drops every suppressed Contact. Then POST /{namespace}/collections/query.
spend-limitRaise the Limit, or wait for the month to reset. Then POST /{namespace}/limits/{limit}/raise.

Calls

plays/ compiledMode try
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"
  }
}
Response200application/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
CallWhat it does
POST /{namespace}/play-mixes/compileCompile 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}/startStart opens an Experiment, splits traffic across its variants, and begins measuring its primary and guardrail Metrics. Emits Experiment.started.
POST /{namespace}/experiments/{experiment}/concludeConclude ends an Experiment with a decision to ship, kill, or iterate, recorded as a Decision. Emits Experiment.concluded.
POST /{namespace}/actions/{action}/evaluateEvaluate grades an Action’s output against a rubric and records the verdict as an Event. Emits Action.evaluated.
POST /{namespace}/processes/{process}/pausePause stops a Process from starting new Actions; Actions already in progress finish. Emits Process.paused.
Not live yetUse today
Campaign.startGaps #2. Today: POST /{namespace}/campaigns and POST /{namespace}/processes/{process}/pause run today; a Stop reads as a Process pause.
Campaign.pauseGaps #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.

Contents