Title: Dozen — MidnightDev
URL: https://midnightdev.dev/work/dozen
Description: The model writes captions. It does not own the irreversible edges.
Generated from the live page on 2026-10-22. Do not edit by hand; run `pnpm twins`.

---

case study

# Dozen

The model writes captions. It does not own the irreversible edges.

[open Dozen](https://dozen.midnightdev.dev/) · [launch essay](https://midnightdev.dev/writing/the-model-cannot-own-the-money.md) · dozen.midnightdev.dev

![Dozen landing page with the headline Don't buy one influencer. Agents run a dozen, and a twelve-node campaign graph whose accept node is still running.](https://midnightdev.dev/screenshots/dozen.png)

01 the problem

Influencer marketplaces still leave the work after “I found someone” on humans: briefs, FTC, posting, and payouts. Agent demos paper over that hole by auto-closing accept, post, and pay so a single handler can look finished.

02 the solution

Built Dozen as a twelve-node graph with machine-checkable verifiers. Accept stays idle until Stripe Checkout records the brand budget, then stays open until at least 60% of offered seats accept and at least six seats fill — or the brand explicitly refills from the bench. Post re-checks FTC disclosure fail-closed. Measure can fail without closing pay: anomalous engagement holds that seat. The model only writes captions.

03 decisions & tradeoffs

### The model cannot own accept, post, or pay

Those edges spend reputation and money. Auto-closing them so a demo finishes in one handler is a product lie. Humans, policy, or an independent verifier close them.

### Measure is independent of pay

A 3.2× engagement spike or a 0.15× collapse is a hold, not a reason to mark the campaign paid. The pay node stays running until measure is clean.

### Closed sample, not a fake finished campaign

The public /demo is labeled as a finished sample against the seed bench. Views are derived from baseline ER, not a live platform API.

### Keep the product repository private

The public artifact is this case study. Shipping a public repo before Stripe escrow and live post confirmation would over-claim the product.

04 tech stack

### Closed-loop agent graph

intake → safety → assemble → offer → accept → draft → verify → revise → review → post → measure → pay. The script owns branching; the model does not own accept, post, or pay.

### Production-imported tests

Tests import the live matching, loop, and verifier modules — not stubs; 113 green as of September 1, 2026. A companion graph linter fails any topology that lets the model close an irreversible edge.

### TanStack Start + Postgres

React 19 product surface with Better Auth and a PGLite/Postgres dual-mode store so the graph is durable, not a chat transcript.

### Fail-closed policy

Banned verticals, FTC #ad, fake-risk ≥ 70 bps, and 1k–50k follower bounds reject before score. Anomalous ER (3.2× spike or 0.15× collapse) holds payout.

05 results

- 113 tests green as of September 1, 2026, production-imported, including a graph linter that fails if the model owns accept, post, or pay
- Accept gate is dual: ≥60% and at least six seats; rejected seats stay in the offered denominator
- Accept stays closed until Stripe Checkout is paid at or above the campaign budget
- Fake-risk and out-of-market seats are rejected at assemble and shown on the closed sample
- Anomalous ER holds that seat's payout; pay stays running until measure is clean and escrow is funded
- Stripe Checkout is wired fail-closed and creator Stripe Express onboarding is wired. Post requires a public TikTok or Instagram permalink plus disclosure; platform confirmation is not fetched. Payouts queue to a ledger row — no transfer has fired and no GMV is claimed

Next step

## Want something like this?

Book a 30-minute call. I will tell you plainly whether I can help and what I would test first.

[Book a call](https://calendly.com/alex-midnightdev/30min?utm_source=midnightdev&utm_medium=site&utm_campaign=case-dozen) [or email alex@midnightdev.dev](mailto:alex@midnightdev.dev)
