Betterment’s Flutter app runs on Ferry, and Ferry’s code generator couldn’t use sealed classes or pattern matching. With Betterment’s team, we rebuilt it from the ground up using Claude Code. Six epics shipped in three months, roughly twice what we estimated without it.
- faster tickets, days down to hours
- 10x
- project output, 6 epics in 3 months
- 2x
- faster builds, v2 vs. v1
- 38%
A Claude Code session working one ticket on Betterment’s GraphQL code generator, through the team’s loop: read the ticket, team briefing, Claude plans, Claude implements, a human engineer reviews, then commit and pull request. Claude reads AGENTS.md and the architecture docs, edits the generator, and runs build_runner and the golden tests before a human approves. The ticket finishes in hours against an estimate of two to four days, and a tracker fills toward six epics in three months. The session is an illustration.
01The ask
Fix the generator at the source, not around it.
Betterment’s Flutter code base, which VGV helped them adopt, talks to its API through Ferry, a GraphQL client for Dart. Ferry generates its models with the gql library, which predates Dart 3. The generated code couldn’t use sealed classes or exhaustive pattern matching, so there was no idiomatic way to handle polymorphic types.
That left four critical issues: fragments on interface types that silently never matched, polymorphic fragments that couldn’t be read back from the cache, a built_value dependency that broke on JSON fields, and no fragment reuse on polymorphic types. Betterment’s engineers had built workarounds for all four, and paid for them in duplicated code and extra tests.
02The build
A multi-pass pipeline, sealed classes, and thin serialization.
The v1 generator worked in a single pass, so the same fragment could produce a slightly different type in every operation that used it. The v2 generator splits the work into three passes. A schema visitor registers every type, a fragment visitor resolves each fragment once and in isolation, and an operation visitor assembles the Dart classes from both registries. Shared fragments now produce one type across the code base.
Polymorphic types became Dart 3 sealed classes, so the compiler checks that every variant is handled. Each hierarchy also carries an unknown subclass, so types added to the schema later don’t break existing clients. Custom fromJson and toJson methods replaced built_value. Builds got 38% faster, and generated files grew only 0.7%.
03The context
Documentation good enough to drive an agent.
Betterment had already diagnosed the four issues, designed the v2 architecture against the GraphQL spec, and written tickets with root-cause analysis, reproduction steps, and proposed fixes. They tested the docs with Claude Code until it could take a ticket, read the architecture, and write correct code on its own.
A root AGENTS.md gave Claude repository guidance, spec references, and review checklists. Package-level files and tickets pointed to the documentation for each task, so Claude worked with focused context instead of everything at once. Clear, unambiguous docs kept hallucinations rare.
The same context shortened our own ramp-up. Learning build_runner and both versions of the generator would have taken about a month. With Claude answering our questions, it took a few days.
04The workflow
Pair programming, with Claude Code in the pair.
Each ticket started with the owner briefing the team, then a plain prompt asking Claude to work on it. Claude read the docs and the ticket and proposed a plan. We refined the plan, Claude implemented it, and the team reviewed and iterated before committing. Golden tests for the generated code, full of edge cases and repetitive GraphQL mocks, went much faster with Claude.
Claude also reviewed pull requests with the same context it had during development. It caught structural issues and missed edge cases before a human opened the PR, so a distributed team spent less time waiting across time zones. Every PR was still reviewed and approved by a human engineer before merging.
- 2–4 hours per ticket, down from 2–4 days
- 6 epics delivered in 3 months
05What changed
GraphQL on Flutter, without the workarounds.
-
The code
All four critical issues resolved.
Interface fragments match, polymorphic fragments deserialize from the cache, JSON fields serialize, and fragments are reused across operations.
-
The build
38% faster builds.
Generated files grew only 0.7%, with simpler output and no built_value dependency.
-
The pace
Six epics in three months.
Tickets estimated at 2–4 days landed in 2–4 hours. Without Claude, we estimate the same time would have delivered about half the work.
-
The team
Product teams write GraphQL the idiomatic way.
Betterment’s engineers dropped the workarounds and write polymorphic GraphQL on Flutter the way they do on other platforms.
For the engineering detail, including the v1 casting bug, the pipeline design, and what we learned about Claude as a reviewer, read the full write-up on our blog.
06If you’re next
What we’d tell a team in Betterment’s seat.
- 01
Treat documentation as infrastructure. It’s done when an agent can work a ticket from it without asking questions.
- 02
Scope the context. A short root AGENTS.md plus package-level files that point to the docs for each task beats one large global document.
- 03
Connect the agent to your tracker. We copied Jira tickets into the repo and they drifted; a Jira MCP connection would have kept one source of truth.
- 04
Let Claude review pull requests, and keep a human approving them. It moves the mechanical checks out of the reviewer’s way.
Roll credits.
Who did what, and with which tools.
- Client
- Betterment
- Betterment’s part
- Root-cause analysisv2 architectureAgent-ready documentation
- Our part
- Code generator engineeringGolden testsAI-assisted delivery
- Built with
- Dart 3Ferry and gqlbuild_runnerClaude Code
- Timeline
- Six epicsThree months
Built by Very Good Ventures
Frequently asked questions.
01 How much faster was development with Claude Code?
Tickets that would have taken 2 to 4 days were finished in 2 to 4 hours. The team delivered 6 epics in 3 months, roughly twice the output we estimated for the same work without Claude. Ramp-up on build_runner and the v1 architecture went from weeks to days.
02 What did Betterment own, and what did VGV own?
Betterment did the root-cause analysis, designed the v2 architecture, and wrote documentation an agent could work from. VGV built the code generator, wrote the golden tests, and delivered the epics with Claude Code.
03 Why didn’t Betterment switch GraphQL clients instead?
Ferry’s generated models are Betterment’s domain models. Swapping clients would have meant rewriting the integration across the whole code base, so the new generator kept Ferry and fixed the code it generates.
04 Did a human review the code Claude Code wrote?
Yes. Claude reviewed pull requests with the same context it had during development, which caught mechanical issues early, but every pull request was reviewed and approved by a human engineer before merging.
More success stories
All success stories
Dow Jones cuts development cost 50% with Flutter
- lower development cost
- 50%
Nav doubles user growth with Flutter
- user growth
- 2x
- engagement increase
- 200%
Berkheimer’s tax e-filing app doubles its users
- user growth, year over year
- 2x
We’ll care about your success as much as you do.
Our unicorns read every message and reply, usually the same day.
Start the conversationA 30-minute call about what you’re building.






