URL: /plays/build-in-public · Id: `play:build-in-public`

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

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

```text title="Set it up · Ask"
Be our Content Lead for a build log: every Friday after office hours, write up what we decided, tested, killed and earned from our records, with every number from the record and revenue only from Stripe. I approve and redact each one and press post on my own accounts. Show me what’s missing; don’t publish.
```

```text title="Run one · Ask"
Hand this week’s records to the Content Lead agent and show me what it chose and why. Don’t publish.
```

```ts title="Run one · SDK"
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 }
```

```json title="Run one · Response · sample"
{
  "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"
    ],
    "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"
  },
  "actions": [
    {
      "$id": "https://api.sb/leaseline.app/actions/action_1",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_7",
      "verb": "Metric.query",
      "checks": [
        {
          "check": "stripe-revenue",
          "status": "pass"
        },
        {
          "check": "recompute",
          "status": "pass"
        }
      ]
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_2",
      "$type": "Action",
      "status": "done",
      "mode": "try",
      "by": "worker_7",
      "verb": "Content.draft",
      "checks": [
        {
          "check": "number-token",
          "status": "pass"
        },
        {
          "check": "display-consent",
          "status": "pass"
        },
        {
          "check": "leak-scan",
          "status": "pass"
        }
      ]
    },
    {
      "$id": "https://api.sb/leaseline.app/actions/action_3",
      "$type": "Action",
      "status": "pending",
      "reason": "awaitingApproval",
      "mode": "try",
      "by": "worker_7",
      "verb": "Content.publish",
      "checks": [
        {
          "check": "owned-by-a-person",
          "status": "pass"
        },
        {
          "check": "cherry-pick",
          "status": "pass"
        },
        {
          "check": "raise",
          "status": "pass"
        }
      ]
    }
  ]
}
```

`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 week | Choice | Reason |
| --- | --- | --- |
| Week of Oct 12: contacts fell to 5, Maple Court’s result consented | `lead-with-an-outcome` | 3 logs under half the peak, and a result you may show |
| Week of Sep 7: Decision “iterate”, the reminder test read | `publish-the-log` | a Decision and a test read to report |
| Sep 21: a Stripe refund changed log 4’s 9 to 8 | `correct-the-number` | a recompute differs from the published number |
| Wrenfield: 14 renewals sent on time in September, no yes to show it | `ask-for-a-result` | a verified result with no display Consent |

| Worth | Your time | Spend | First signal | Use it when | Instead |
| --- | --- | --- | --- | --- | --- |
| ≈ +$180 ARR a month at 4 logs | 30 min week 1, then 20 a week | $0.40 per log, ~$2 a month | Week 1: first reader subscribes from a log | a weekly Decision and test read, Stripe for revenue | No weekly Decision: `weekly-review` · no account or Form: `owned-newsletter` |

**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
```bash
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"
```


```json title="The dry run’s 422 · 2026-08-24 · sample"
{
  "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"
    }
  ]
}
```

| Check | Fix (one call) | Your time | Wait |
| --- | --- | --- | --- |
| <a id="check-owned-by-a-person"></a>`owned-by-a-person` · <a id="check-keep-first"></a>`keep-first` · <a id="check-customer-ask-cap"></a>`customer-ask-cap` · pass · pass · pass (gaps #3, #8) | — | 0 min | none |
| <a id="check-use-when"></a>`use-when` · <a id="check-billing-connected"></a>`billing-connected` · pass: 6 Decisions in 6 weeks · fail | <Op verb="Integration.connect" /> with `system` recurring-billing (Stripe), live mode | 5 min | the first sync, about 10 min |
| <a id="check-baseline"></a>`baseline` · warn: 11 in 90 days | <Op verb="Metric.query" />; it fills Before in Your number | 0 min | none |
| <a id="check-role-unstaffed"></a>`role-unstaffed` · pass (gaps #8) | — | 0 min | none |

`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
| When | Trigger id | Starts on | Window | How it gets in |
| --- | --- | --- | --- | --- |
| Friday office hours record the week’s Decision | `friday-decision` | `event` on `Decision.recorded` where the weekly office hours' Decision: ship, kill, iterate, persevere or pivot | the draft within 1 h; expires after 3 d | <Op verb="Decision.create" /> in office hours |
| The weekly test reaches its read | `weekly-test-read` | `event` on `Experiment.concluded` where the weekly test's Readout recorded, or its read date reached | 7 d | <Op verb="Experiment.conclude" /> |
| A recompute changes a published number | `number-restated` | `threshold` on `Metric.queried` where a recompute differs from the published value | the correction within 1 h | the nightly <Op verb="Metric.query" /> recompute |
| + 6 more for kills, Stripe milestones, overrules, incidents, consented results and replies (`?lanes=all`) | | | | |

```json title="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"
    }
  }
}
```

```bash title="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.

| Option | Rung today | Evidence (passed of n) | Moves up at | Drops back when | You can |
| --- | --- | --- | --- | --- | --- |
| Publish this week’s log · Lead with a Customer’s result | You approve each one, on every rung | 8 of 8 approved; 3 with a redaction | never | — | <Op verb="Approval.approve" /> · <Op verb="Approval.refuse" /> |
| Correct the number | It does it | 1 of 1 within 1 h | — | — | <Op verb="Change.revert" /> |
| Ask a Customer for their result · Welcome the new reader | It does it, you can stop it | 2 of 2 · 61 of 61 | met on day 14 | a held Customer is asked anyway → You approve each one | <Op verb="Action.cancel" /> inside the hold |
| Skip this week | It does it | 0 weeks skipped | — | — | <Op verb="Action.redirect" /> |
| Never: a typed number, revenue not from Stripe, a Customer named without their yes, a raise or a forecast | To a person, every time | 2 refused, 0 published | never | — | <Op verb="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 <Op verb="Content.draft" />, <Op verb="Content.publish" />, <Op verb="Message.broadcast" />, <Op verb="Page.update" />, and any other call becomes an Approval under the Role’s Mandate.

```md title="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**, <Op verb="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.

```bash
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
| Option | Picked when | Decided by | Runs | Hands to | If it’s just you |
| --- | --- | --- | --- | --- | --- |
| Publish this week’s log `publish-the-log` | a Decision, test read or kill this week | Your rule (line 4) | <Op verb="Content.draft" /> → <Op verb="Content.publish" /> the page → <Op verb="Message.broadcast" /> the issue | you redact and approve, due Saturday 12:00 · Take this one | you |
| Lead with a Customer’s result `lead-with-an-outcome` | contacts under half their peak for 3 logs, a consented result | Agent’s call, with its reason | <Op verb="Content.draft" /> with the result first → <Op verb="Content.publish" /> | you approve it · Take this one | you |
| Ask a Customer for their result `ask-for-a-result` | a verified result with no yes to show it | Agent’s call, with its reason | `outcome-ask`: one ask for a yes | the Customer’s answer | the same |
| Correct the number `correct-the-number` | a recompute differs from a published number | Your rule (the correction clock) | <Op verb="Page.update" /> within 1 h; the next log leads with it | nobody | the same |
| Welcome the new reader `welcome-the-reader` | a subscribe from a log | Your rule (the Form) | `build-log-welcome` | nobody | the same |
| Skip this week `not-now` | nothing recorded, or the draft expired | Your rule (`founder-silence`) | nothing; 2 expired logs in a row pause the Play | nobody | the same |

| When they write back | Fixture reply | What happens | Who answers | By when | Call |
| --- | --- | --- | --- | --- | --- |
| Interested | “We have the same renewal mess. Can we try it?” | `instant-response` answers with the trial and one next step | SDR · your agent | 5 min | <Op verb="Conversation.answer" /> |
| Send me pricing | “What does it cost for 120 units?” | answers with the Pricing Page’s list Price and nothing it lacks | SDR · your agent | 5 min | <Op verb="Conversation.answer" /> |
| Not now | “Enjoying the logs; not buying yet.” | they stay on the log; nothing else sends | nobody | at once | <Op verb="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 text | Content Lead · your agent | 1 business day | <Op verb="Conversation.answer" /> |
| Out of office | an auto-reply to the issue, until Oct 27 | nothing; the next issue goes on its Friday | nobody | the next Friday | <Op verb="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 shows | you | 1 business day | <Op verb="Conversation.escalate" /> |
| Stop | “Unsubscribe me.” | no log issue sends to them again | nobody | at once | <Op verb="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).

```json title="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 <Op verb="Task.claim" /> (Take this one), <Op verb="Task.return" /> with a reason, <Op verb="Approval.approve" /> or <Op verb="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:** <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 |
| --- | --- | --- | --- |
| `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 owns | Leave it to the person who owns it: the draft waits in their queue. | <Op verb="Task.assign" /> |
| `keep-first` (rule j; Not live yet, gaps #8) | a result ask to a Customer who is at risk, past due or leaving | Let their CSM reach them first, then ask again once they are healthy. | <Op verb="Task.next" /> |
| `customer-ask-cap` (rule j; Not live yet, gaps #3) | a result ask to a Customer with another open ask | Wait for the open ask to close; <Op verb="Decision.list" /> names the Play that holds them. | <Op verb="Decision.list" /> |
| <a id="check-number-token"></a>`number-token` | a quantity in a draft or your edit that is not a record token | Use the record’s token, or cut the number. | <Op verb="Content.draft" /> |
| <a id="check-recompute"></a>`recompute` | a log where 2 tokens recompute differently at render | Re-render; the log waits for the records to settle. | <Op verb="Metric.query" /> |
| <a id="check-stripe-revenue"></a>`stripe-revenue` | a revenue figure from anything but live-mode Stripe | Read it from Stripe, or cut it. | <Op verb="Metric.query" /> |
| <a id="check-display-consent"></a>`display-consent` | a Customer or Person item with no yes to show it | It is redacted; ask once with `outcome-ask`. | <Op verb="Consent.create" /> |
| <a id="check-leak-scan"></a>`leak-scan` | an email address, Customer domain or name from your records in the final render | Remove it; the scan reruns before publishing. | <Op verb="Content.draft" /> |
| <a id="check-cherry-pick"></a>`cherry-pick` | a log missing a public number without its Withheld count | Show it, or print it as withheld. | <Op verb="Content.draft" /> |
| <a id="check-raise"></a>`raise` | any mention of a raise, a round, a valuation, an investment ask or a revenue forecast | Cut the line. | <Op verb="Content.draft" /> |
| <a id="check-spam-brake"></a>`spam-brake` | an issue past 3 times the trailing 7-day Message rate in an hour | Send to a smaller List, or wait an hour for the rate to fall. | <Op verb="Message.broadcast" /> |

| Stop (pauses the Play) | Pauses when | Then |
| --- | --- | --- |
| <a id="stop-correction"></a>`correction` | a restated number | the log page corrects within 1 h, and the next log leads with it |
| <a id="stop-founder-silence"></a>`founder-silence` | 2 expired logs in a row | the Play pauses and you are told once |
| <a id="stop-contact-floor"></a>`contact-floor` | fewer than 5 consented contacts across the first 8 logs | the Play run stops and a Decision goes to review |
| <a id="stop-slop-note"></a>`slop-note` | a Venue’s AI author note, or 2 public AI callouts in 30 days | You approve each one, for 4 logs |
| <a id="stop-venue-warning"></a>`venue-warning` | a restriction notice, CAPTCHA or feature limit on an account | that placement pauses at once |
| <a id="stop-kill-switch"></a>`kill-switch` | you press it | every 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.

```json title="The captured refusal · sample"
{
  "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"
    }
  ]
}
```


```json title="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 <Op verb="Metric.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`

| Node | Now (8 logs) | Before (the 90 days, per 56) | Added | Confidence | Basis |
| --- | --- | --- | --- | --- | --- |
| `build-log-contacts` | 67 consented contacts: 10 a log for logs 1 to 4, 6.8 for logs 5 to 8 | 7 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-leads` | 14 Leads from those contacts | 3 | — | — | 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` · `arr` | carried from `new-arr` | carried | — | — | 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 %.

```json title="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 option | Decisions | Done | Contacts (what happened) |
| --- | --- | --- | --- |
| Publish this week’s log · Lead with a Customer’s result | 7 · 1 | 7 published · 1 published | 58 · 9 |
| Ask a Customer for their result · Correct the number | 2 · 1 | 1 yes, 1 no · fixed in 12 min | — |
| Welcome the new reader · Skip this week | 61 · 0 | 61 welcomed · — | — |
| **All 8 logs**, against before | **8** logs | **8** approved, 3 with a redaction | **67** · before: 11 in 90 days |
| At 1 log a week (the Use it when volume) | about 8 in 56 days | before-after, not proof | Other Startups’ results: +4.1 contacts a log, ■■□□ Early sign, n 640 logs |

| Day | Event | What happened | Your time |
| --- | --- | --- | --- |
| 0 | `event_1123` Campaign.created | the three steps; the dry run fails `billing-connected` | 20 min |
| 0 | `event_1124` Integration.connected | Stripe connected in live mode | 5 min |
| 4 | `event_1146` Content.published | log 1: 6 contacts in 7 days | 15 min |
| 25 | `event_1262` Action.refused | `number-token` held log 5 for a typed number; you cut it | 3 min |
| 28 | `event_1276` Campaign.paused | `correction`: a refund made log 4’s 9 an 8; corrected in 12 min | — |
| 53 | `event_1381` Change.merged | a Customer’s result leads: replayed at 8.5 contacts, log 8 drew 9 | 5 min |
| 1–56 | `event_1147` Task.created, first of 8 | one log a week, from the <Op verb="Task.next" /> log | 2.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.

```diff title="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
```mdx title="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 | <a id="tactic-log-policy"></a>`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 | <a id="tactic-log-compile"></a>`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 | <a id="tactic-number-check"></a>`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 | <a id="tactic-redaction-pass"></a>`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 | <a id="tactic-log-publish"></a>`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 | <a id="tactic-founder-send-tray"></a>`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 | <a id="tactic-lead-slot-switch"></a>`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 | <a id="tactic-outcome-consent-ask"></a>`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 | <a id="tactic-log-corrections"></a>`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 | <a id="tactic-build-log-contact-count"></a>`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 | <a id="trigger-friday-decision"></a>`friday-decision` | Friday decision: `event` on `Decision.recorded`, window 3d. |
| Trigger | <a id="trigger-weekly-test-read"></a>`weekly-test-read` | Weekly test read: `event` on `Experiment.concluded`, window 7d. |
| Trigger | <a id="trigger-kill-recorded"></a>`kill-recorded` | Kill recorded: `event` on `Hypothesis.dropped`, window 7d. |
| Trigger | <a id="trigger-stripe-milestone"></a>`stripe-milestone` | Stripe milestone: `threshold` on `Invoice.collected`, window 7d. |
| Trigger | <a id="trigger-agent-overrule"></a>`agent-overrule` | Agent overrule: `event` on `Decision.superseded`, window 7d. |
| Trigger | <a id="trigger-incident-resolved"></a>`incident-resolved` | Incident resolved: `event` on `Incident.resolved`, window 7d. |
| Trigger | <a id="trigger-outcome-consented"></a>`outcome-consented` | Outcome consented: `event` on `Consent.granted`, window 30d. |
| Trigger | <a id="trigger-number-restated"></a>`number-restated` | Number restated: `threshold` on `Metric.queried`, window 1d. |
| Trigger | <a id="trigger-log-reply"></a>`log-reply` | Log reply: `event` on `Prospect.engaged`, window 2d. |
| Sequence | <a id="sequence-build-log-welcome"></a>`build-log-welcome` | Build log welcome: 2 steps; it ends on An unsubscribe.; A reply, which opens a Conversation.. |
| Sequence | <a id="sequence-outcome-ask"></a>`outcome-ask` | Outcome ask: 2 steps; it ends on An approval.; An edit, which returns for approval.; A decline.; Seven days of silence.. |