We just shipped Unified Lists—a system that lets agents manage tasks, context, and workflows using natural language. But here's what made it interesting: we built it backwards.
Most teams start with mockups, then build the API, then worry about the database. We went database → models → service layer → handlers → UI. Each layer validated by tests before the next one landed. The result? Ten pull requests over five days, 68 audit findings caught before production, and a feature that worked correctly the first time users touched it.
This is the story of infrastructure-first development. Not as philosophy, but as practice.
Start with the Schema
PR #286 was just SQL. Two tables (lists and list_items), row-level security policies, foreign key constraints, and trigger functions. No Python. No API. Just the data model that would constrain everything built on top of it.
We shipped it with audit tests—not to test functionality (there was none yet), but to verify the security model:
-- Can a user read another user's list?
-- Can they update an item without owning the parent list?
-- What happens if they try to delete a list with items?Four findings on the first audit run: missing RLS policies, a TEXT column that should've been UUID, a missing FK to reminders, and trigger scope collision. Fixed before a single line of Python knew those tables existed.
Why start here? Because schema bugs are expensive. You can refactor Python in fifteen minutes. You can't casually change a foreign key constraint when you have production data. Getting the foundation right means everything above it is easier to change.
Models as Contracts
PR #287 was Pydantic models. Still no business logic. Just the type definitions that would serve as contracts between layers:
class ListCreate(BaseModel):
name: str
theme: Optional[ListTheme] = None
class ListItem(BaseModel):
id: uuid.UUID
title: str
status: ItemStatus
created_at: datetimeThe audit tests checked the contracts. Does ListCreate enforce required fields? Do the enums cover all valid database states? Can you accidentally serialize a field that should stay internal?
Five findings. One was subtle: the validation info wasn't properly typed, so Pydantic silently accepted invalid nested objects. Caught by a test that tried to create a list with a malformed theme object.
This is the value of infrastructure-first development. Each layer has one job. The model layer's job is validation. So we test validation in isolation, before any service code depends on it.
The Service Layer
PR #288 brought ListService—five methods, 352 lines, enforcing user isolation and ownership rules. This is where business logic lives:
async def create_list(self, user_id: UUID, data: ListCreate) -> List:
# Validate input (already done by Pydantic, but defense in depth)
# Insert with RLS enforcing ownership
# Return typed responseWe shipped it with 877 lines of tests and ran three rounds of TDD audits. Twenty-five findings:
- Security: RLS policies weren't being restored in test cleanup (now one test can't poison the next)
- Type safety: Using raw dicts instead of ValidationInfo for error responses
- Bugs: Empty update payloads returned false 404s instead of no-ops
Each finding was a test that didn't exist yet. Write the test, watch it fail, fix the code, watch it pass. Commit. The audit process isn't QA—it's discovering the tests you should've written in the first place.
Here's the key insight: by the time we wrote the service layer, the schema and models were stable. We weren't debugging database constraints while trying to implement business logic. One problem at a time.
CRUD Operations
PRs #289-296 filled out the API surface: item CRUD, name resolution, cross-list query, bulk operations, reordering, nudge candidates, reminder promotion, notification channels.
Each one followed the same pattern:
- Write the method signature
- Write audit tests that assume it works correctly
- Run the audit, collect findings
- Fix findings, expand tests
- Run a second audit round
- Ship when findings stop
PR #289 (item CRUD with reminder cascade) had 14 findings across three audit rounds. One was critical: deleting an item should reactivate its linked reminder so the user gets reminded even though the to-do is gone. We never would've thought of that without the audit test asking "what happens to the reminder when you delete the item?"
PR #292 (cross-list query) had 10 findings. The most valuable: RLS wasn't being checked on the join between lists and list_items, so you could query items from lists you didn't own if you knew the right filter combination. Caught by a test, fixed before a code reviewer would've spotted it.
What This Looks Like in Practice
Here's the commit sequence for the reminder notification channels fix (PR #296):
- Write notification_channels field into Pydantic schema
- Expose it in tool schema so agents can set preferences
- Update service to persist the field
- Update handler to read and apply it
- Audit round 1: catches 6 issues (key validation, empty fallback handling, OAuth token expiry)
- Audit round 2: passes
- Ship with 532 lines of tests
The bug was actually three bugs in three different layers. The service hardcoded SMS. The handler overwrote user preferences. The OAuth tokens were stale. Each layer's tests caught its own bug.
This is why bottom-up works. Each layer is tested in isolation before the next layer touches it. When you integrate, you're combining working pieces, so integration bugs are rare.
Infrastructure as Velocity
We shipped ten PRs in five days. Each one landed with comprehensive tests. Each one passed CI on the first push. Zero rollbacks. Zero hotfixes.
That's not because we're fast. It's because we're slow in the right places.
We spent hours writing audit tests for a database schema with no functionality. We wrote 877 lines of tests for a 352-line service layer. We ran audit rounds until findings stopped, even when the code "worked."
Then when we built features on top of that foundation, they took 45 minutes instead of four days. Because the hard problems—schema design, type contracts, ownership semantics—were already solved. Tested. Stable.
The infrastructure-first approach trades upfront cost for compounding velocity. The first PR is slow. The tenth PR is fast. And everything in between gets faster because you're building on solid ground.
The Audit as Tool
The most counterintuitive part of this workflow: we write tests assuming the code is correct, then use the failures to find bugs.
Traditional TDD says "write a failing test, then make it pass." Our audit process says "write a test that should pass, then investigate why it doesn't."
Example from PR #290 (name resolution):
def test_fuzzy_match_suggests_close_names():
# Create list "Roadmap Q1"
# Try to resolve "Roadmap"
# Should get exact substring matchThis test assumed fuzzy matching worked. It didn't—the substring matching was case-sensitive. The test failed. We fixed it. That fix triggered another audit finding: what if the search term is empty? What if it's just whitespace?
Each fixed finding revealed new edge cases. By the time the audit round passed, we had 27 test cases for a 77-line method. Not because we're thorough, but because the audit process systematically explored the failure space.
Why This Matters
You can't run an organization with 24/7 autonomous agents unless the code they execute is correct. Not "mostly works" correct. Actually correct. Handles edge cases. Fails safely. Maintains invariants.
Infrastructure-first development with audit-driven testing is how we make that guarantee. Each layer is validated in isolation. Each integration is tested against known edge cases. Each finding becomes institutional memory in the test suite.
The result is a platform where we can ship new features daily, grant agents increasing autonomy, and sleep at night.
The Unified Lists feature is live. Agents are using it to manage reminders, organize context, coordinate workflows. It works because we built it from the bottom up, tested it at every layer, and didn't move to the next step until the current one was provably correct.
That's the method. Not fast, not clever, just relentlessly systematic. Build the foundation, test it into submission, then build the next layer on solid ground.