Regardless of the pace of AI innovation, the fundamental way companies build systems to deliver value to users hasn’t really changed. Less of the business logic is written by hand. But there are still internal services, mobile apps, and frontends consuming backend APIs, and teams still build systems that need to work together.
That was the appeal of Protobuf long before agents. Teams could use whatever language, framework, or stack they wanted as long as they had well-defined contracts between systems, so everyone within an org could build, deploy, and evolve software independently.
The same principles apply with AI. Teams can use whatever agent, model, or language they want, as long as the code follows the contract. Protobuf is still what makes sure the pieces fit together.
Agents are good at Protobuf
Protobuf files are much denser in actual meaning than formats like OpenAPI or bespoke handler code. That means less time wasted and fewer input tokens. And AI is amazing at reading and writing Protobuf.
The economics of generated code are still the same, even after AI. Instead of developer time, it costs tokens to create boilerplate code, and Protobuf reduces the amount that agents have to write, so they can focus on business logic.
A virtuous cycle
In the name of consistency, a helpful agent or developer might rename a field from user_id to account_id. Inside that one repo, the update looks great. Every reference gets updated, unit tests get fixed, and the PR goes up with green CI. The problem is that another team’s service in a totally separate repo is still expecting the field to be called user_id. We updated our contract in an incompatible way, so if you merge the PR you will break their client.
Writing “do not break existing clients” in CLAUDE.md or AGENTS.md will probably work “ok” most of the time, but the agent is likely missing context on which fields are used by other clients, so it may still take a leap of faith right off a cliff, taking your production API with it. We think you need a check that compares the .proto file against the version already running in prod. The Buf CLI does this out of the box with buf breaking (alongside buf lint for style rules). When the agent tries renaming that field, buf breaking returns a non-zero exit code along with this text:
$ buf breaking --against .git#branch=main
proto/user/v1/user.proto:6:3:Field "1" with name "account_id" on message "User" changed option "json_name" from "userId" to "accountId".
proto/user/v1/user.proto:6:10:Field "1" on message "User" changed name from "user_id" to "account_id".There are two errors here because they are telling us two different things: the field name will break generated code and changing the implicit json_name from userId to accountId would break clients that use JSON with your APIs. If you know you don’t have JSON clients and if you are okay with needing to update the client code at some point, buf breaking has options to tune which breakages count.
Renaming a field is one of 64 rules buf breaking checks. It also catches:
- Deleting messages, enums, services, RPCs, or whole files
- Deleting a field or enum value without reserving its number
- Changing a field’s type, or moving it between singular,
repeated,map, and a oneof - Renaming enum values, or changing a field’s
json_name - Changing an RPC’s request type, response type, or streaming mode
- Changing a file’s package, or language options like
go_packageandjava_package
See the breaking rules reference for the full list.
buf lint does the same for style, with 45 built-in rules. It flags:
- Names that break convention, like a
camelCasefield or asnake_casemessage - Enum values missing the enum’s prefix, or a zero value that doesn’t end in
_UNSPECIFIED - Packages without a version suffix like
v1orv1beta1 - RPCs that share request or response messages instead of having their own
- Unused imports and import cycles
- Invalid protovalidate rules
See the lint rules reference for the full list.
- user_id renamed to account_id
- buf breaking: field renamed
- user_id restored, account_id added
- buf breaking: passed
These kinds of deterministic checks matter even more when the agent is writing unit tests and reviewing PRs. If an agent writes buggy code and then writes matching tests and then reviews its own code, you can have a fully green CI pipeline and completely happy agents while the PR is about to break every single client. buf lint and buf breaking don’t care what the agent thought it was doing or about rationalizations that were made along the way. If there is a breaking change to the contract, buf breaking will fail.
Scaling to organizations
Running buf breaking locally can catch breaking API changes while the agent is still working on it (fast iteration), and running it in CI will catch whatever slips through the cracks. That’s good enough for one repo. For organizations at scale, this is likely still insufficient. When you have dozens to hundreds of repos owned by many different teams, each with their own CI setup, anyone with access can push a new version of a schema to the BSR and skip CI entirely.
The Buf Schema Registry (BSR) prevents this by running those same checks on the server side when schemas are pushed. You can turn on breaking-change governance so the BSR rejects any push to the default label that breaks the contract. Of course, sometimes you actually need to ship a breaking change:
- Removing a field that has been deprecated for months and confirmed unused
- Retiring a legacy service entirely
- Iterating quickly on an API that’s still in alpha
That’s where review flow comes in. Instead of rejecting the push, the BSR holds the breaking commit in a pending queue until an administrator signs off on it, while clients on the default label keep pulling the previous version (unless they specifically pin that new commit or branch label).
- buf push: display_name added
- Compatible. Default label advances.
- buf push: user_id renamed
- Breaking. Held for review.
- Admin approves. Default label advances.
Most organizations also have conventions that no built-in rule covers. Buf check plugins add custom lint and breaking rules that run alongside the built-in ones, and policies bundle rules and plugins together so a platform team can define them once and enforce them across every repository.
Agents do better with constraints
AI agents do much better work when they have hard constraints. Without a schema, an agent has to read through a bunch of handler code to figure out what the API looks like. The .proto file describes the entire contract in one place in a pretty concise format. That saves on tokens and speeds up the time-to-understanding for both humans and agents.
If you want to try this out in your own repos, check out our docs on breaking change detection, linting, the Buf GitHub Action, and the BSR’s breaking change and review flow checks. And if you’re starting a project from scratch, the modern Protobuf workflow walks through how we set up code generation, RPC, validation, and editor tooling.