• Financial Services
  • Dart 3
  • GraphQL
  • Claude Code

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%.

The v2 GraphQL code generation pipeline in three steps, a SchemaVisitor filling a SchemaRegistry, a FragmentVisitor resolving fragments into a FragmentRegistry, and an OperationVisitor generating Dart classes, replacing v1’s single pass

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
The AI-assisted engineering loop used on the Betterment project, read ticket, team briefing, Claude planning mode, AI-guided implementation, human review and iteration, then commit and PR

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.

  1. 01

    Treat documentation as infrastructure. It’s done when an agent can work a ticket from it without asking questions.

  2. 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.

  3. 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.

  4. 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.

Laura Hughes Lekhi Director of Business Development
Greg Laird Senior Business Development Executive
Kyle Crouse Director of Concept Technology
Andrew King Director of Solutions Architecture
Jorge Coca VP of Engineering
Shannon Campbell Product Lead

We’ll care about your success as much as you do.

Our unicorns read every message and reply, usually the same day.

Start the conversation

A 30-minute call about what you’re building.