Sprint 35 — knowing when to nag
The plan for Sprint 35 was to slow down. After two jumbo sprints I wrote “testing, bug fixes, a few smaller improvements” in the planning note and meant it. Forty-six PRs later, it was the opposite — the sprint where Domi started running the money side of the house.
It started honestly enough. A six-dimension code audit I’d run closed out — all three High findings fixed (two cross-tenant dev routes deleted, a DST drift that rendered recurring events a day early, an obligation reminder firing a day too soon), plus the medium and low stuff. Then email ingestion, which had been half-working for a while, got finished: attachments now flow through the same vision pipeline as uploads, email bodies get extracted and embedded, and a brand allowlist I’d been embarrassed by got replaced with a real LLM relevance gate. Hardening. As planned.
Then I started on obligations, and the sprint changed character.
A contract is not a reminder
The old model treated every obligation the same: one due date, one reminder, lead time of a month. That’s wrong in a way you only feel once you’re living in the app. A car-tax notice isn’t one payment — it’s six dated installments. A yearly insurance policy doesn’t want a “pay” nudge — it wants a cancel-by heads-up before it auto-renews. And a monthly Netflix subscription that pays itself off a card wants nothing at all. Reminding me to “pay Netflix” every month is exactly the kind of noise that makes people close the tab.
So obligations got rebuilt around how contracts actually behave. A payment-method field (auto-card, auto-bank, manual, unknown). A dated installment schedule for the tax-notice case. A typed cadence with an anchor date and an optional season (the gardening contract that only runs May–September). And then the reminder engine split into two streams that gate independently:
- Payment reminders — only for the obligations you actually pay by hand, sourced from the next unpaid installment or the cadence, and skipped entirely when something pays itself.
- Renewal / cancel reminders — only for annual-or-longer auto-renewing contracts, fired at the cancellation deadline. Independent of who pays it — an auto-paid yearly policy still gets the cancel-by warning, even though it never gets a pay nudge.
That last distinction is the whole point. The intelligence isn’t “remind me about my obligations.” It’s knowing which obligations deserve which kind of reminder, and which deserve silence.
Marking it paid
If Domi tells you to pay a bill, the natural next thing is to tell it you did. So payment-reminder tasks lost their generic “Mark complete” button and got a green Paid one. It opens a little popup — the date (today by default), the amount (prefilled from the installment), and a free-text note where I paste the bank confirmation number — and on confirm it records a transaction, marks that installment paid so it never nags again, completes the task, and links the two. Paying a bill now leaves a financial trail in the ledger instead of just checking a box.
And because the household shouldn’t only learn about a contract when I type it in, the ingestion side closed the loop: when a document or an ingested email looks like a bill, policy, or tax notice, Domi reads it and proposes the obligation — as a confirm card on the document and in the daily briefing, never a silent write. Upload the insurance PDF, get the obligation back with its term and cancellation window already filled in.
The wave that followed
Once obligations were done the dam broke, and a week of dogfood papercuts shipped at once. A universal Area / Asset / Member filter bar across all eight list pages. A contacts taxonomy — twenty real vendor types (banks, insurers, pharmacies, schools, utilities…), three new life areas to file them under, and a one-click LLM reclassify that proposes a better type for every old “Vendor” contact and lets me approve them in a review list. Converting a task into a calendar event. A proper task list inside each project. All the small things you only discover by using the thing every day.
The sprint closed with housekeeping that earns its keep: Tailwind v4 (a deliberate major migration, theme preserved token-by-token), and a roadmap reconcile that re-pointed the “do next” list at what actually matters now.
What I’m taking from it
Three sprints running, the same lesson keeps landing: the dogfood writes the backlog. I didn’t plan the Paid button or the two-stream reminders — I found them by paying a real bill in my own app and feeling the friction. The roadmap’s next front is no longer features. It’s the foundation for letting other people in: a staging tier so I stop deploying straight to prod, real household membership so the four roles that exist actually get enforced, and the WorkOS auth migration behind it. That’s Sprint 36. This time I might even mean it.