On October 29 we shipped a new version of nau!, an app that helps people understand their mortgage and find ways to pay less. It wasn’t a new product. There were customers, registered mortgages and a history we had to keep.
The release covered a redesign of the app, a new public website, changes to the bank catalog and the opportunity engine, new customer flows and an evolution of the data model. Between the previous deploy on October 15 and launch day, we merged 58 pull requests, wrote 38 database migrations and touched 13 Edge Functions.
There were four of us, with part-time help from a fifth person in the last few days, working with Claude Code and Codex. Based on how we built products before agents, we estimate that an equivalent delivery would have taken at least three to four months, several sprints and a team twice our size.
Agents compressed the build. They didn’t compress deciding what was right, or checking that it was.
This is how we organized the work, and what we learned about the part that didn’t get faster.
Four specialties, many workstreams
The team brought together mortgage expertise, product, design, marketing and engineering. Each of us worked from our own specialty, using agents to research, propose solutions, implement changes and prepare checks.
While one workstream moved the bank catalog forward, another could be designing a screen, adjusting the emails or wiring the new data into the financial engine. Agents let those tasks run at the same time and get further before they needed the team again.
Parallelism, though, isn’t just opening more conversations. Every task needs to understand the product and know its limits. A design task has to know what information to show and how it fits the customer journey. A catalog change has to account for the engine that consumes it. A task that touches the database has to preserve the data and the flows that already depend on it.
With that context, agents could work autonomously up to a reviewable change. Then the team connected the pieces, settled the open decisions and checked the result.
Context is part of the product
For several tasks to move in parallel, the knowledge they needed had to live somewhere other than a single conversation. So we put it in the repository:
- A canonical
AGENTS.mdthat Claude Code and Codex both read (CLAUDE.mdjust points to it), with the commands every change must pass before a pull request (tests, typecheck, build and lint) and the rules that aren’t up for negotiation. - A short snapshot of the project’s current state, updated in the same pull request that makes it obsolete.
- Reference documents per area: data architecture, financial rules, design and environments. Each task reads only the ones that affect it.
One of those rules mattered more than the rest: a refactor must not change a financial result. If a monthly payment, an APR, a savings figure or a ranking changes, we need to understand why and treat it as a change in behavior, covered by tests. Written down next to the code, an agent respects it. Said once in a chat, it’s lost by the third task.
Keeping rules close to the code gives the team and the agents a shared baseline. It also makes review easier: we don’t just see what changed, but which rules it should still satisfy.
That shared context also lets people without an engineering background work with agents more autonomously. A product person knows the customer’s needs, the business rules and the details that make an experience make sense. Before, turning that knowledge into a feature meant handing it to engineering. The task competed with the rest of the backlog and could wait for the next sprint. If it needed refining once it was built, the adjustment went back into the queue. Every round trip added time, even when the change itself was small.
With agents and clear rules in the repository, the person who needs the change can work on it directly, see a first version and refine it without waiting for a slot in the engineering backlog. Guardrails, tests and team review make sure the result does what’s needed and preserves the integrity of the product.
One environment per pull request
Parallel work creates conflicts that stay invisible until integration. To see them earlier, every pull request spins up its own environment: a Supabase branch with its own Postgres database and Edge Functions, and a Vercel Preview connected only to that branch. No preview falls back to the shared development database. The pipeline deploys migrations first, then functions, then the frontend, so a backend failure blocks the web deploy instead of hiding behind it.
Before merging, every pull request passes unit tests, typecheck and build, database tests with pgTAP, and a migration check that runs two rehearsals: replaying every migration on an empty database, and applying the new ones as an upgrade over existing data while verifying that data is preserved.
The second rehearsal is the one that matters. A migration that works on an empty database proves nothing about yours. When two branches added migrations at the same time, their order mattered. And a migration that has been merged is immutable: any fix is a new, additive migration. That’s how you end up with 38 of them in two weeks.
The new data model needed to represent mortgage holders, homes, bank products and opportunities better. We introduced it in stages: new structures first, then moving the data that belonged there, then gradually adapting the flows that read and write it. The old structures stayed alive for as long as anything needed them.
From the catalog to an opportunity people can understand
The nau! engine compares a person’s current mortgage with products available on the market. A lower interest rate alone doesn’t make a useful opportunity. Spanish banks usually discount the rate if you take out other products with them, such as insurance, a payroll account or a card, and those products cost money too. A bigger rate discount doesn’t always mean bigger net savings.
We connected the renewed catalog to the engine so it evaluates each discount together with its cost and picks the best bundle for every opportunity. From there, users can explore other combinations and see how each one changes the result. When they decide to move forward, the conditions of that offer are saved.
That work cut across disciplines: mortgage expertise to define the rules, engineering to implement them and design to make the result understandable. Agents helped turn each decision into a version we could check, and iterate on it fast.
Preparing the launch while the product kept moving
We applied the same way of working to integration and deployment. While the last adjustments were closing, we prepared the migrations, reviewed the launch sequence and rehearsed the checks.
Before shipping, we refreshed the backup and verified that it could be restored; we had already rehearsed restoring it into an isolated copy. During the launch we paused the app and the scheduled jobs, applied the migrations, deployed the backend and the web and imported the approved catalog. Old opportunities and alerts were retired, while customers’ requests, reviews and history were kept.
The release went through on the first attempt. The maintenance window lasted only as long as it took to run the launch and check the system before reopening it to users. Afterwards we compared the data against the backup to confirm that nothing had been lost.
What we learned
Not everything went smoothly. These are the lessons we’d carry into the next project.
What you show has to be what you saved. During testing we found a design flaw: the app calculated opportunities in the browser and showed them even when saving them in the backend had failed. Users could see figures the system didn’t have. We split the two. The backend calculates and stores each opportunity with its optimal bundle, its results and a snapshot of every input (catalog, mortgage, profile, Euribor and date). The app shows only what was stored, and the browser runs the shared engine only when the user explores a different bundle. It’s the kind of bug that every check passes: each piece worked; the contract between them didn’t.
A validator can’t share the engine’s assumptions. We had a validator, but over time it had ended up calling the engine itself: if the engine was wrong, the validator was wrong in exactly the same way. We rewrote it with its own formulas and a test that fails if it imports any engine module. It recalculates an opportunity in 12 explained steps and compares 20 figures against the engine and the database, down to bank-style rounding to the cent. The reference cases were worked out by hand with our mortgage expert. It doesn’t cover every eligibility rule. It’s there to explain a specific difference, not to certify the product.
Green checks are necessary, not sufficient. Agents implemented, debugged and wrote tests, and we still had to redo some implementations. A pull request with every check green could store something different from what it displayed. Signing off a flow meant opening the app, walking through it with real product cases and checking what ended up in the database.
Decisions didn’t compress. How to compare monthly payments between two different mortgages. How to explain what a discount costs. What to ask for to complete a profile, and what to show while data is missing. Each of those questions went through several iterations, and they took as long as they always have. What changed was the cost of trying an idea: when turning a decision into a working version takes an afternoon instead of a sprint, you try more decisions and drop the bad ones sooner.
What changed in time and cost
To compare, we start from the same team and the same existing product.
| With agents | Estimate without agents | |
|---|---|---|
| Duration | 2 weeks | 12–16 weeks |
| Team | 4 people + part-time help | The same + 2 developers, 1 QA, 1 designer |
| Core team time | 240–320 h | Not included |
| Additional cost | €160 in Claude and Codex subscriptions | €86,000–127,000 in external capacity (12 weeks) |
The estimate without agents assumes 12 weeks at 40 hours per person and external rates of €50–75/h for development, €35–50/h for QA and €45–65/h for design. Over 16 weeks, the range rises to €115,000–170,000. Hiring internally would have been another option, at a cost that depends on the terms.
Even adding our own time at market rates, between €12,000 and €22,000, the delivery cost four to ten times less and arrived six to eight times sooner. The comparison is conservative: the scenario without agents doesn’t include the core team’s time over those three or four months.
This is an estimate, not measured savings. We didn’t build the same release twice. What we did measure is what shipped: 58 pull requests, 38 migrations and a live app that kept working for its users.
A different way to build
Agents shortened the time between a decision and a version we could test. That let us explore more options, adjust the product and connect changes from different areas in a much shorter time.
Mortgage expertise, design, engineering and product decisions stayed essential. Agents expanded what the team could do with that knowledge: research several alternatives, build in parallel and test every piece in its own environment. The result was a broad release on a real app, integrated and shipped by a small team, and a way of working that lets us take on bigger projects without the team having to grow at the same pace.
Try this on your next project
Before you run agents in parallel, write down in the repository the three rules no task is allowed to break. Then ask yourself which of your own checks no green build can replace. That’s the work that won’t get faster, so plan for it.
Agents multiply what you can build. The judgment is still yours.
Related on the Lab: Our job was never translating requests into code · Attention Is All You Have · Case Study: Rewriting What Parsing Can Do

