Skip to content

Build in public

Publishes a weekly build log compiled from your own records: what you decided, tested, killed and earned, with no typed number, after your OK.

Channel
Social
Run oneMode try
You, to your agent

Hand this week’s records to the Content Lead agent and show me what it chose and why. Don’t publish.

curl "https://api.sb/leaseline.app/events?where=name%20in%20(%22Decision.recorded%22%2C%20%22Experiment.concluded%22%2C%20%22Hypothesis.dropped%22%2C%20%22Invoice.collected%22%2C%20%22Decision.superseded%22%2C%20%22Incident.resolved%22%2C%20%22Consent.granted%22%2C%20%22Metric.queried%22%2C%20%22Prospect.engaged%22)&limit=1" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"

# Not live yet (gaps #7)
curl -X POST "https://api.sb/leaseline.app/triggers/fire?mode=try" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"like":"event_1388"}'

# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/decisions?where=on%20%3D%20%22content_88%22" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"

# Not live yet (gaps #7)
curl "https://api.sb/leaseline.app/actions?where=on%20%3D%20%22content_88%22" \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
import { $ } from 'startup-builder'

const { events: [last] } = await $.Event.list({ where: 'name in ("Decision.recorded", "Experiment.concluded", "Hypothesis.dropped", "Invoice.collected", "Decision.superseded", "Incident.resolved", "Consent.granted", "Metric.queried", "Prospect.engaged")', 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 }
npx startup-builder do Event.list --json '{"where":"name in (\"Decision.recorded\", \"Experiment.concluded\", \"Hypothesis.dropped\", \"Invoice.collected\", \"Decision.superseded\", \"Incident.resolved\", \"Consent.granted\", \"Metric.queried\", \"Prospect.engaged\")","limit":1}'
npx startup-builder do Trigger.fire --json '{"like":"event_1388"}' --try   # Not live yet (gaps #7)
npx startup-builder do Decision.list --json '{"where":"on = \"content_88\""}'   # Not live yet (gaps #7)
npx startup-builder do Action.list --json '{"where":"on = \"content_88\""}'   # Not live yet (gaps #7)
{
  "name": "do",
  "arguments": {
    "code": "const { events: [last] } = await $.Event.list({ where: 'name in (\"Decision.recorded\", \"Experiment.concluded\", \"Hypothesis.dropped\", \"Invoice.collected\", \"Decision.superseded\", \"Incident.resolved\", \"Consent.granted\", \"Metric.queried\", \"Prospect.engaged\")', limit: 1 })   // { play } compiled to this Play’s Trigger Events\nconst { subject } = await $.Trigger.fire({ like: last.$id }, { mode: 'try' })   // the same Event again, in try mode · Not live yet, gaps #7\nconst { decisions: [decision] } = await $.Decision.list({ where: `on = \"${subject}\"` })   // what it chose, and why · Not live yet, gaps #7\nconst { actions } = await $.Action.list({ where: `on = \"${subject}\"` })   // what it did, with its checks · Not live yet, gaps #7\nreturn { decision, actions }",
    "mode": "try"
  }
}
Response200application/json
{
  "decision": {
    "$id": "https://api.sb/leaseline.app/decisions/decision_499",
    "$type": "Decision",
    "choice": "lead-with-an-outcome",
    "reason": "logs 5 to 7 drew 7, 6 and 5 contacts against a peak of 14, under half, and Maple Court said yes to showing its renewal result",
    "stakes": "low",
    "options": [
      "publish-the-log",
      "lead-with-an-outcome",
      "ask-for-a-result",
      "correct-the-number",
      "welcome-the-reader",
      "not-now"
  …
sample · 4 Not live yetPreview

On POST /{namespace}/processes/{process}/enableOff POST /{namespace}/processes/{process}/pauseTake this one POST /{namespace}/tasks/{task}/claim

action_3 is log 8, waiting for your redactions and OK (You approve each one); the issue to subscribers and the posts on your own accounts wait for your press.

67 contacts in 8 logs against 11 before · before-after, not proof · n 8 logs · path → arr

Try another: the weekChoiceReason
Week of Oct 12: contacts fell to 5, Maple Court’s result consentedlead-with-an-outcome3 logs under half the peak, and a result you may show
Week of Sep 7: Decision “iterate”, the reminder test readpublish-the-loga Decision and a test read to report
Sep 21: a Stripe refund changed log 4’s 9 to 8correct-the-numbera recompute differs from the published number
Wrenfield: 14 renewals sent on time in September, no yes to show itask-for-a-resulta verified result with no display Consent

Where it sits: Stranger → Lead → Trialing → Activated → At risk → Churned → Won back · Build log contacts → New Leads → New Customers ↑ → ARR added each week → ARR

Worth’s formula: about +4.1 contacts per log (Other Startups’ results; no lift of your own yet) × 4.3 logs a month × 0.21 contacts to Leads × 0.14 Leads to paid × $347 first order ARR ≈ +$180. Under First signal: One log a week can’t be split at random, so it reads before-after, and against its own first 4 logs for decay; 8 logs tell a fall to half the early rate.

sample · api.sb/leaseline.app · 41 Customers at 2026-10-19 · on Decision.recorded · first draft 22m after office hours · 4 keys Not live yet

Start it this week

Terminal
npx startup-builder add play build-in-public   # 1 · writes plays/build-in-public.mdx (Not live yet, gaps #5)

# 2 · dry run; below, its 422 on the day-0 Startup (clock at 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":"build-in-public"}'

# 3 · keep it, at You approve each one
curl -X POST https://api.sb/leaseline.app/campaigns \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"play":"build-in-public"}'

# on
curl -X POST https://api.sb/leaseline.app/processes/content-marketing/enable \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"

# off: no log publishes; corrections keep running
curl -X POST https://api.sb/leaseline.app/processes/content-marketing/pause \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY"
422Refusedbuild-in-public can’t start yet: 1 check fails and 1 warns.
CheckFixThen
owned-by-a-person pass · Not live yet, gaps #80 subscribers are Leads a person ownsPOST /{namespace}/tasks/{task}/assign
keep-first pass · Not live yet, gaps #80 Customers with a result to ask about are at riskPOST /{namespace}/tasks/next
customer-ask-cap pass · Not live yet, gaps #30 open asks to those CustomersGET /{namespace}/decisions
use-when pass · Not live yet, gaps #8Friday office hours recorded 6 Decisions in 6 weeks; a weekly test each weekPOST /{namespace}/metrics/query
billing-connected fail · Not live yet, gaps #8Connect Stripe in live mode; revenue shows only from it.POST /{namespace}/integrations/connect
baseline warn · Not live yet, gaps #8n 11: it reads before-after, not proof, beside Other Startups’ results.POST /{namespace}/metrics/query
role-unstaffed pass · Not live yet, gaps #8Content Lead is your agent; each log comes to youPOST /{namespace}/roles/{role}/staff
JSON
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "build-in-public can’t start yet: 1 check fails and 1 warns.",
  "checks": [
    {
      "check": "owned-by-a-person",
      "status": "pass",
      "observed": "0 subscribers are Leads a person owns",
      "doc": "/plays/build-in-public#check-owned-by-a-person",
      "then": "Task.assign"
    },
    {
      "check": "keep-first",
      "status": "pass",
      "observed": "0 Customers with a result to ask about are at risk",
      "doc": "/plays/build-in-public#check-keep-first",
      "then": "Task.next"
    },
    {
      "check": "customer-ask-cap",
      "status": "pass",
      "observed": "0 open asks to those Customers",
      "doc": "/plays/build-in-public#check-customer-ask-cap",
      "then": "Decision.list"
    },
    {
      "check": "use-when",
      "status": "pass",
      "observed": "Friday office hours recorded 6 Decisions in 6 weeks; a weekly test each week",
      "doc": "/plays/build-in-public#check-use-when",
      "then": "Metric.query"
    },
    {
      "check": "billing-connected",
      "status": "fail",
      "doc": "/plays/build-in-public#check-billing-connected",
      "fix": "Connect Stripe in live mode; revenue shows only from it.",
      "then": "Integration.connect"
    },
    {
      "check": "baseline",
      "status": "warn",
      "observed": "11 contacts from your own posts in 90 days",
      "doc": "/plays/build-in-public#check-baseline",
      "fix": "n 11: it reads before-after, not proof, beside Other Startups’ results.",
      "then": "Metric.query"
    },
    {
      "check": "role-unstaffed",
      "status": "pass",
      "observed": "Content Lead is your agent; each log comes to you",
      "doc": "/plays/build-in-public#check-role-unstaffed",
      "then": "Role.staff"
    }
  ]
}

It says no, says why, and names the fix.

CheckFix (one call)Your timeWait
owned-by-a-person · keep-first · customer-ask-cap · pass · pass · pass (gaps #3, #8)—0 minnone
use-when · billing-connected · pass: 6 Decisions in 6 weeks · failPOST /{namespace}/integrations/connect with system recurring-billing (Stripe), live mode5 minthe first sync, about 10 min
baseline · warn: 11 in 90 daysPOST /{namespace}/metrics/query; it fills Before in Your number0 minnone
role-unstaffed · pass (gaps #8)—0 minnone

Your number · Before 11 per 90 days · Now 67 contacts in 8 logs · Target 120 by Dec 31 · before-after, not proof · n 8 logs

Each log costs about $0.40 to compile and check from your Domain (Worker cost), and the issue to about 150 subscribers sends from your Domain at no extra charge. That comes to about $2 a month at 4 logs.

When it starts

WhenTrigger idStarts onWindowHow it gets in
Friday office hours record the week’s Decisionfriday-decisionevent on Decision.recorded where the weekly office hours' Decision: ship, kill, iterate, persevere or pivotthe draft within 1 h; expires after 3 dPOST /{namespace}/decisions in office hours
The weekly test reaches its readweekly-test-readevent on Experiment.concluded where the weekly test's Readout recorded, or its read date reached7 dPOST /{namespace}/experiments/{experiment}/conclude
A recompute changes a published numbernumber-restatedthreshold on Metric.queried where a recompute differs from the published valuethe correction within 1 hthe nightly POST /{namespace}/metrics/query recompute
+ 6 more for kills, Stripe milestones, overrules, incidents, consented results and replies (?lanes=all)
One signed delivery · StartupBuilder-Signature
{
  "$id": "https://api.sb/leaseline.app/events/event_1388",
  "$type": "Event",
  "name": "Decision.recorded",
  "at": "2026-10-16T16:40:10Z",
  "seq": 1388,
  "data": {
    "decision": {
      "$id": "https://api.sb/leaseline.app/decisions/decision_497",
      "choice": "iterate",
      "on": "the 60-day reminder test"
    },
    "week": 8,
    "log": {
      "$id": "https://api.sb/leaseline.app/contents/content_88"
    }
  }
}
Reproduce it in test
# each delivery is signed with the Watch’s secret
curl -X POST https://api.sb/leaseline.app/watches \
  -H "Authorization: Bearer $STARTUP_BUILDER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"selector":"Decision.recorded","url":"https://leaseline.app/hooks/sb"}'
npx startup-builder trigger Decision.recorded --like event_1388   # replays Friday’s Decision
npx startup-builder clock advance --by 3d   # runs the draft’s expiry if you never approved it

Who runs it

Content Lead · your agent (role:content-lead, worker_7) holds the Key Result and compiles each log. Every log comes to you (Dana Okafor, person, worker_1) to redact and approve, and posts on your own accounts go out only when you press them. Your number links to Did it work.

OptionRung todayEvidence (passed of n)Moves up atDrops back whenYou can
Publish this week’s log · Lead with a Customer’s resultYou approve each one, on every rung8 of 8 approved; 3 with a redactionnever—POST /{namespace}/approvals/{approval}/approve · POST /{namespace}/approvals/{approval}/refuse
Correct the numberIt does it1 of 1 within 1 h——POST /{namespace}/changes/{change}/revert
Ask a Customer for their result · Welcome the new readerIt does it, you can stop it2 of 2 · 61 of 61met on day 14a held Customer is asked anyway → You approve each onePOST /{namespace}/actions/{action}/cancel inside the hold
Skip this weekIt does it0 weeks skipped——POST /{namespace}/actions/{action}/redirect
Never: a typed number, revenue not from Stripe, a Customer named without their yes, a raise or a forecastTo a person, every time2 refused, 0 publishednever—POST /{namespace}/processes/{process}/pause

What it reads · What it may call. It reads your Decisions, Experiments, Changes, Incidents and Stripe, and every number in a log is a token that recomputes at render; no number is typed, and a Customer item without display Consent is redacted before you see it. Its tools are POST /{namespace}/content/draft, POST /{namespace}/content/{content}/publish, POST /{namespace}/messages/{message}/broadcast, PATCH /{namespace}/pages/{id}, and any other call becomes an Approval under the Role’s Mandate.

The brief (folded): compiled Agent-register Markdown · play:build-in-public@1 · each line names its record key
You hold the Content Lead Key Result: 120 contacts from the build log by 2026-12-31. ← worker, metric
Each Friday, compile the week’s Decision, test, kills and numbers into one log and say what leads it. ← triggers, chooses
Every number is a record token; revenue reads only from Stripe.                  ← number-token, stripe-revenue
Show subscribers, Customers and revenue as counts; no Customer by name without their yes. ← your file, line 5; display-consent
Posts on Dana’s accounts wait for her press.                                     ← your file, line 6

The slow loop, as a Change. decision_489 (2026-10-12, weekly) read contacts per log falling from a peak of 14 to 5 by log 7, under half for 3 logs, so change_31 moved the lead slot to a consented Customer result and Dana merged it. Replayed on past logs, POST /{namespace}/changes/{change}/review put contacts for a result-led log at 8.5; read back on log 8 at +7d it is 9, n 1 log, ■□□□ Too early to tell (Example, day 53).

Bring your own agent (folded) compiles beside the seated one; its log reaches you only if it passes the same number, leak and consent checks.

Terminal
npx startup-builder eval --scenario build-in-public-8 --agent "claude -p" --seat content-lead   # seated: 8.4 contacts a log, 0 typed numbers, 0 leaks · yours: 7.9, 1 typed number refused

What it can do next

OptionPicked whenDecided byRunsHands toIf it’s just you
Publish this week’s log publish-the-loga Decision, test read or kill this weekYour rule (line 4)POST /{namespace}/content/draft → POST /{namespace}/content/{content}/publish the page → POST /{namespace}/messages/{message}/broadcast the issueyou redact and approve, due Saturday 12:00 · Take this oneyou
Lead with a Customer’s result lead-with-an-outcomecontacts under half their peak for 3 logs, a consented resultAgent’s call, with its reasonPOST /{namespace}/content/draft with the result first → POST /{namespace}/content/{content}/publishyou approve it · Take this oneyou
Ask a Customer for their result ask-for-a-resulta verified result with no yes to show itAgent’s call, with its reasonoutcome-ask: one ask for a yesthe Customer’s answerthe same
Correct the number correct-the-numbera recompute differs from a published numberYour rule (the correction clock)PATCH /{namespace}/pages/{id} within 1 h; the next log leads with itnobodythe same
Welcome the new reader welcome-the-readera subscribe from a logYour rule (the Form)build-log-welcomenobodythe same
Skip this week not-nownothing recorded, or the draft expiredYour rule (founder-silence)nothing; 2 expired logs in a row pause the Playnobodythe same
When they write backFixture replyWhat happensWho answersBy whenCall
Interested“We have the same renewal mess. Can we try it?”instant-response answers with the trial and one next stepSDR · your agent5 minPOST /{namespace}/conversations/{conversation}/answer
Send me pricing“What does it cost for 120 units?”answers with the Pricing Page’s list Price and nothing it lacksSDR · your agent5 minPOST /{namespace}/conversations/{conversation}/answer
Not now“Enjoying the logs; not buying yet.”they stay on the log; nothing else sendsnobodyat oncePOST /{namespace}/leads/{lead}/nurture
Wrong person“Forwarding this to our ops lead.”replies with the log page they can forward; it never writes to a name from their textContent Lead · your agent1 business dayPOST /{namespace}/conversations/{conversation}/answer
Out of officean auto-reply to the issue, until Oct 27nothing; the next issue goes on its Fridaynobodythe next FridayPOST /{namespace}/messages/{message}/schedule
Question it can’t answer“What’s your monthly churn?”To you, the question in the Brief; it states only numbers the log showsyou1 business dayPOST /{namespace}/conversations/{conversation}/escalate
Stop“Unsubscribe me.”no log issue sends to them againnobodyat oncePOST /{namespace}/contacts/{contact}/unsubscribe

When it hands to you

Every log comes to you to redact and approve, and every post on your own accounts waits for your press. What lands in your queue, as captured: the Brief, then the Approval (brief, draft, route and checks are Not live yet, gaps #7).

What lands in each queue · sample
[
  {
    "$id": "https://api.sb/leaseline.app/tasks/task_305",
    "$type": "Task",
    "assignee": "worker_1",
    "due": "2026-10-17T12:00:00Z",
    "on": "content_88",
    "brief": {
      "what": "log 8: Decision iterate on the 60-day reminder test; 2 Versions shipped; 1 reverted Change",
      "chose": "lead-with-an-outcome",
      "why": "contacts under half their peak for 3 logs; Maple Court said yes to its result",
      "redacted": [
        "1 interview quote without Consent"
      ],
      "withheld": 0
    }
  },
  {
    "$id": "https://api.sb/leaseline.app/approvals/approval_90",
    "$type": "Approval",
    "verdict": null,
    "deadline": "2026-10-19T16:40:00Z",
    "draft": "Log 8. Maple Court sent all 31 renewal notices 60 days out in September, up from 19 in August…",
    "route": "lead-with-an-outcome",
    "checks": [
      {
        "check": "number-token",
        "status": "pass"
      },
      {
        "check": "display-consent",
        "status": "pass"
      },
      {
        "check": "leak-scan",
        "status": "pass"
      }
    ]
  }
]

You answer with POST /{namespace}/tasks/{task}/claim (Take this one), POST /{namespace}/tasks/{task}/return with a reason, POST /{namespace}/approvals/{approval}/approve or POST /{namespace}/approvals/{approval}/refuse, each a one-call window, and a log unanswered by its deadline expires and never publishes. Two expired logs in a row pause the Play (founder-silence).

On time: you, 8 of 8 logs approved by their deadline, median 1 h 40 min, ■■□□ Early sign, n 8. Your queue: counted by kind: each week, 1 log to redact and approve and 2 posts to press; about 2 replies a week.

The fixed line: POST /{namespace}/deals/{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)RefusesFixThen
owned-by-a-person (rule j; Not live yet, gaps #8)a welcome or ask to a Contact whose Lead or open Deal a person ownsLeave it to the person who owns it: the draft waits in their queue.POST /{namespace}/tasks/{task}/assign
keep-first (rule j; Not live yet, gaps #8)a result ask to a Customer who is at risk, past due or leavingLet their CSM reach them first, then ask again once they are healthy.POST /{namespace}/tasks/next
customer-ask-cap (rule j; Not live yet, gaps #3)a result ask to a Customer with another open askWait for the open ask to close; GET /{namespace}/decisions names the Play that holds them.GET /{namespace}/decisions
number-tokena quantity in a draft or your edit that is not a record tokenUse the record’s token, or cut the number.POST /{namespace}/content/draft
recomputea log where 2 tokens recompute differently at renderRe-render; the log waits for the records to settle.POST /{namespace}/metrics/query
stripe-revenuea revenue figure from anything but live-mode StripeRead it from Stripe, or cut it.POST /{namespace}/metrics/query
display-consenta Customer or Person item with no yes to show itIt is redacted; ask once with outcome-ask.POST /{namespace}/consents
leak-scanan email address, Customer domain or name from your records in the final renderRemove it; the scan reruns before publishing.POST /{namespace}/content/draft
cherry-picka log missing a public number without its Withheld countShow it, or print it as withheld.POST /{namespace}/content/draft
raiseany mention of a raise, a round, a valuation, an investment ask or a revenue forecastCut the line.POST /{namespace}/content/draft
spam-brakean issue past 3 times the trailing 7-day Message rate in an hourSend to a smaller List, or wait an hour for the rate to fall.POST /{namespace}/messages/{message}/broadcast
Stop (pauses the Play)Pauses whenThen
correctiona restated numberthe log page corrects within 1 h, and the next log leads with it
founder-silence2 expired logs in a rowthe Play pauses and you are told once
contact-floorfewer than 5 consented contacts across the first 8 logsthe Play run stops and a Decision goes to review
slop-notea Venue’s AI author note, or 2 public AI callouts in 30 daysYou approve each one, for 4 logs
venue-warninga restriction notice, CAPTCHA or feature limit on an accountthat placement pauses at once
kill-switchyou press itevery Tactic and the Mandate pause; corrections keep running

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

422RefusedLog 5 did not publish: your edit typed a number no record holds.
CheckFixThen
number-token failUse the record’s token, or cut the number.POST /{namespace}/content/draft
leak-scan pass—POST /{namespace}/content/draft
JSON
{
  "type": "https://startupbuilder.dev/errors/refused",
  "title": "Refused",
  "status": 422,
  "detail": "Log 5 did not publish: your edit typed a number no record holds.",
  "checks": [
    {
      "check": "number-token",
      "status": "fail",
      "observed": "“about 50 property managers” in line 1",
      "doc": "/plays/build-in-public#check-number-token",
      "fix": "Use the record’s token, or cut the number.",
      "then": "Content.draft"
    },
    {
      "check": "leak-scan",
      "status": "pass"
    }
  ]
}

It says no, says why, and names the fix.

The Stops that fired, as Campaign.paused reasons · sample
[
  {
    "reason": {
      "limit": "correction",
      "observed": "log 4 said 9 annual switches; a refund made it 8",
      "bound": "0 restated numbers uncorrected",
      "window": "1 h"
    },
    "doc": "/plays/build-in-public#stop-correction",
    "fixed": "2026-09-21T16:52:00Z"
  }
]

Did it work

One log a week cannot be split at random, so the basis is before-after (rule o): the columns read Now and Before, and nothing prints as Added. Decay reads against the first 4 logs. Each figure’s call is POST /{namespace}/metrics/query by node; every node above the leaf is Not live yet (gaps #4).

67 contacts in 8 logs against 11 before · before-after, not proof · n 8 logs · path → arr

Chose
lead-with-an-outcome
Why
logs 5 to 7 drew 7, 6 and 5 contacts against a peak of 14, under half, and Maple Court said yes to showing its renewal result
From
publish-the-log · lead-with-an-outcome · ask-for-a-result · correct-the-number · welcome-the-reader · not-now
Expects
7 or more contacts from log 8 within 7d
Reopens when
under 4 contacts from content_88 by day 7
By
worker_7
JSON
{
  "$id": "decisions/decision_499",
  "$type": "Decision",
  "choice": "lead-with-an-outcome",
  "reason": "logs 5 to 7 drew 7, 6 and 5 contacts against a peak of 14, under half, and Maple Court said yes to showing its renewal result",
  "stakes": "low",
  "options": [
    "publish-the-log",
    "lead-with-an-outcome",
    "ask-for-a-result",
    "correct-the-number",
    "welcome-the-reader",
    "not-now"
  ],
  "by": "worker_7",
  "on": "content_88",
  "expected": "7 or more contacts from log 8 within 7d",
  "reopensWhen": "under 4 contacts from content_88 by day 7"
}
NodeNow (8 logs)Before (the 90 days, per 56)AddedConfidenceBasis
build-log-contacts67 consented contacts: 10 a log for logs 1 to 4, 6.8 for logs 5 to 87 from your own posts— · Worth about +$180 a month—before-after, not proof · Other Startups’ results +4.1 a log, ■■□□ Early sign, n 640 logs
new-leads14 Leads from those contacts3——before-after
new-arr ↑$694, 2 Customers on Pro · Booked: credited, not all caused by this Play$347——before-after · 3 trials still deciding
net-new-arr · arrcarried from new-arrcarried——carried

The leaf counts consented contacts whose first Consent came through a log page, issue or post within 7 days; before against after reads 56 days against the 90 before, scaled. It is not proof, and Other Startups’ results print beside it.

How it moves it: each week’s Decision, test and Stripe numbers became one log on leaseline.app, in the issue to about 150 subscribers and on LinkedIn and X, where before the week left no public trace.

Guardrails: before against after: organic-share of visits, 32 % against 30 %, and unsubscribes per issue, 0.6 % against 0.5 %.

The Campaign card · sample
{
  "$id": "https://api.sb/leaseline.app/campaigns/build-in-public",
  "$type": "Campaign",
  "play": "play:build-in-public@1",
  "path": [
    "build-log-contacts",
    "new-leads",
    "new-arr",
    "net-new-arr",
    "arr"
  ],
  "experiment": {
    "metric": "build-log-contacts",
    "unit": "count",
    "heldBack": 0,
    "decision": null
  },
  "readout": {
    "value": null,
    "n": 8,
    "maturing": 0,
    "interval": null,
    "readability": "inadmissible",
    "basis": "before-after",
    "pooled": true
  },
  "ledger": {
    "spend": 0,
    "workerCost": 3,
    "hours": 3.2,
    "wonArr": 694,
    "projectedArr": 180,
    "paybackDays": 1
  }
}

Outside benchmark: posts about a real result drew about twice the replies of posts about progress in one 2026 study of founder accounts (a social-analytics firm, 12,000 posts).

Before it · After it. Before it: weekly-review records the week, and founder-voice writes the founder’s other posts. After it: owned-newsletter keeps the readers it brings, and instant-response answers anyone who writes in.

Example

Leaseline ran it from 2026-08-24 to 2026-10-19 (56 days): 8 logs, each compiled the Friday after office hours and approved by Dana. Revenue showed as counts from Stripe; Customers showed by name only with their yes. Read every captured log at ?threads=all; three follow the tables.

What happened, by optionDecisionsDoneContacts (what happened)
Publish this week’s log · Lead with a Customer’s result7 · 17 published · 1 published58 · 9
Ask a Customer for their result · Correct the number2 · 11 yes, 1 no · fixed in 12 min—
Welcome the new reader · Skip this week61 · 061 welcomed · ——
All 8 logs, against before8 logs8 approved, 3 with a redaction67 · before: 11 in 90 days
At 1 log a week (the Use it when volume)about 8 in 56 daysbefore-after, not proofOther Startups’ results: +4.1 contacts a log, ■■□□ Early sign, n 640 logs
DayEventWhat happenedYour time
0event_1123 Campaign.createdthe three steps; the dry run fails billing-connected20 min
0event_1124 Integration.connectedStripe connected in live mode5 min
4event_1146 Content.publishedlog 1: 6 contacts in 7 days15 min
25event_1262 Action.refusednumber-token held log 5 for a typed number; you cut it3 min
28event_1276 Campaign.pausedcorrection: a refund made log 4’s 9 an 8; corrected in 12 min—
53event_1381 Change.mergeda Customer’s result leads: replayed at 8.5 contacts, log 8 drew 95 min
1–56event_1147 Task.created, first of 8one log a week, from the POST /{namespace}/tasks/next log2.3 h · total 3.2 h measured, 3 h asked

Log 8, Oct 17 · Maple Court sent all 31 of September’s renewal notices 60 days out, up from 19 in August.¹ This week we decided to iterate on the reminder test: 41 of 188 managers opened it, under the 25 % our kill rule set.² Revenue: 41 paying Customers, from Stripe.³

Oct 17 to 24 · 9 contacts: 5 from Dana’s LinkedIn post, 3 from the log page’s Form, 1 forwarded issue; one started a trial on Oct 18.

Log 5, 2026-09-18 · number-token refused your edit, then the line that published
- About 50 property managers tried the reminder this week.
+ 47 property managers tried the reminder this week.

Log 6, 2026-10-02 16:40 · the draft went to Dana (task_279, due Saturday 12:00) with 1 interview quote already redacted, because the speaker gave no yes. Dana withheld the week’s agent costs, which printed as “1 number withheld this week”, approved at 09:15 Saturday, and pressed post on her LinkedIn at 09:20.⁴

¹ A Customer’s result, shown with their yes. ² Every number is a record token that recomputed at render. ³ Revenue reads only from live-mode Stripe. ⁴ A withheld number prints as withheld, never as gone.

Your settings

plays/build-in-public.mdx
---
revenue: counts   # show Customers and revenue as counts from Stripe
---
One log a week, from Friday office hours.
No Customer by name without their yes.
Posts on my accounts wait for my press.
Text
$ npx startup-builder show plays/build-in-public
revenue counts · weekly · the name rule · your press    your file  plays/build-in-public.mdx:2,4,5,6 (Ask 2026-08-24)
family demand · process content-marketing · worker role:content-lead, owns never · chooses 6 (4 rule, 2 read) · sequences build-log-welcome, outcome-ask · approval every-send · checks owned-by-a-person, keep-first, customer-ask-cap, number-token, recompute, stripe-revenue, display-consent, leak-scan, cherry-pick, raise, spam-brake · stops 6    catalog  play:build-in-public@1 (Not live yet, gaps #5)

Every key from the catalog

Key Id What it is
Tactic log-policy Log policy fixes, once, which Metrics a build log publishes, where it publishes, and the standing line, so no week chooses its own numbers. Starts at Sends nothing.
Tactic log-compile Log compile turns the week’s Decisions, Readouts, kills, Versions, Incidents and Metrics into a build log of fixed slots, with every quantity a record token. Starts at Sends nothing.
Tactic number-check Number check blocks any published quantity that is not a record token, reruns every token at render, and holds revenue to live Stripe queries. Starts at Sends nothing.
Tactic redaction-pass Redaction pass removes every unconsented Customer item and secret before render, applies the founder’s choices, and counts what it withholds. Starts at Sends nothing.
Tactic log-publish Log publish places the approved build log on the Site, the weekly issue and the Startup’s Handles, and hands founder Posts to the tray. Starts at You approve each one.
Tactic founder-send-tray Founder send tray shows each approved draft with its source trail; the founder presses post by hand or through the Venue’s own posting path. Starts at You approve each one.
Tactic lead-slot-switch Lead slot switch moves a build log’s lead slot to Customer outcomes once engagement stays under half its peak for 3 logs. Starts at Sends nothing.
Tactic outcome-consent-ask Outcome consent ask sends a Customer their own outcome account for a build log and publishes it only after they approve every word. Starts at You approve the first wave.
Tactic log-corrections Log corrections reruns every published number, corrects any change within an hour in place, and leads the next build log with it. Starts at Sends nothing.
Tactic build-log-contact-count Build log contact count credits each new consented Contact to the earliest build log it traces to within 7 days, and writes the monthly Readout. Starts at Sends nothing.
Trigger friday-decision Friday decision: event on Decision.recorded, window 3d.
Trigger weekly-test-read Weekly test read: event on Experiment.concluded, window 7d.
Trigger kill-recorded Kill recorded: event on Hypothesis.dropped, window 7d.
Trigger stripe-milestone Stripe milestone: threshold on Invoice.collected, window 7d.
Trigger agent-overrule Agent overrule: event on Decision.superseded, window 7d.
Trigger incident-resolved Incident resolved: event on Incident.resolved, window 7d.
Trigger outcome-consented Outcome consented: event on Consent.granted, window 30d.
Trigger number-restated Number restated: threshold on Metric.queried, window 1d.
Trigger log-reply Log reply: event on Prospect.engaged, window 2d.
Sequence build-log-welcome Build log welcome: 2 steps; it ends on An unsubscribe.; A reply, which opens a Conversation..
Sequence outcome-ask Outcome ask: 2 steps; it ends on An approval.; An edit, which returns for approval.; A decline.; Seven days of silence..

Compiled, not written: edit the Play record build-in-public and its Leaseline run, 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.