Take a Step Back
When a coding agent gets buried in an implementation, we tell it to take a step back and reconsider the requirement. Apply that same reset to the application itself. Agents have changed how we produce software, while much of our thinking about what software should exist still follows the architecture we're accustomed to building. The same product could be constructed differently today, and that possibility deserves attention beyond the next feature.
I've spent the last couple of years working on agent context and restrictions, including the ideas in Intelligence Contracts and The Programmable Engineer. That work explored themes now familiar in Markdown instructions and software factories. This essay extends that reflection to the system itself: the choices we've inherited, the behavior we need, and the construction those choices require us to maintain.
The Choices We Inherited
An application's early choices usually serve recognizable needs. The team knows a language, a framework offers a convenient starting point, and an interface accommodates several consumers. As the application develops, those choices accumulate conventions and supporting code. Eventually, new work begins inside that structure, and improving the structure means finding a better implementation of something we've already decided to build.
A choice becomes harder to reconsider once it supplies the vocabulary for the problem. A feature needs another controller because features have controllers. A page needs a query wrapper because pages use query wrappers. Each addition fits the existing pattern, while the pattern itself becomes an assumed requirement. Its familiarity makes it easier to reproduce, including through an agent, without establishing whether it remains the most useful construction.
Flexibility and reuse can reinforce this. General interfaces and interchangeable layers preserve possibilities that future work might need. Meanwhile, ordinary implementation must discover which possibility applies, which pattern owns the decision, and which representations must stay consistent. We spend considerable effort restricting an agent's choices while maintaining architectures that offer it more of them.
The original decisions may still be appropriate. Your experience with the system helps explain their purpose and the obligations they protect. But the conditions under which you produce software have changed, and making those decisions easier for an agent to follow doesn't evaluate them again. To do that, you need a description of the product that can stand independently of its current construction.
The Behavior We’re Trying to Deliver
An ordinary booking restriction provides a useful example. At Vally, we support participant types such as adults, children, and ride-alongs. A trip could allow twenty people overall but no more than fifteen adults. The customer should be prevented from selecting an invalid combination, and the server should reject a request that bypasses the interface. A booking with eighteen adults and two ride-alongs fits the total capacity while violating the adult limit. That describes the required behavior before naming a component, controller, or data shape.
I expected a change of roughly 150 lines. The agent returned a PR of roughly 1,200, adding an unnecessary component and API layers while missing existing shared rules. Four or five corrections later, the result was around 150 insertions. Restarting with better instructions probably would have been preferable. The diff size prompted investigation; the unnecessary implementation was what made the change wrong. This familiar correction loop illustrates the construction surrounding a small requirement.
The interaction itself also deserves examination. Imagine a booking page receiving participant types, business configuration, and activity settings through separate queries for reuse and caching. The client combines those results to determine its permitted selections. We have delivered reusable data while leaving the intended behavior to be assembled downstream. A response organized around that interaction could instead supply the relevant restrictions, with the server still enforcing the actual booking.
This is where different kinds of reuse matter. Reusing an authoritative cap rule avoids another implementation of a decision. Providing general data can leave several consumers to interpret that decision separately. Both may have a purpose, but evaluating them requires following the behavior through the complete interaction rather than counting reusable pieces.
In our API, services hold the business logic. Repositories, controllers, module interfaces, gateways, and models surround them with connections and shape conventions, while the client adds its fetching code. Once the required behavior and its authoritative decisions are clear, that surrounding construction becomes something we can examine on its own terms.
The Construction We Can Generate
Repeated shapes and settled conventions provide an opportunity to derive implementation from explicit sources. If a connection follows a definition, an ordinary feature shouldn't need an agent to rediscover how that connection is constructed. It can change the source when necessary and reproduce the consequences. This reduces the number of independent interpretations involved in expressing a decision.
We already do this for part of our application. Our controllers and schemas produce an OpenAPI description, and Hey API generates a TypeScript SDK with TanStack Query helpers. Client request definitions follow the server's description instead of being maintained separately. That is a concrete reduction in repeated authorship, and a starting point for examining the rest of the API and its client connections. Hey API, TanStack Query generation.
My experiments with generation have included a semantics language that became over-engineered. Describing the existing system created another description and its exceptions to maintain. The ambition now is to derive most of our API and its corresponding client connections, perhaps ninety percent, from definitions. Application-specific query directories, repeated contracts, and backend abstractions become candidates for removal or generation, with service decisions and the customer's interaction defining what the remaining construction must accomplish.
Protobuf offers message and service definitions. Connect RPC is a separate family of libraries using those definitions for HTTP APIs and typed clients, including browser clients. Connect-Query integrates operations with TanStack Query, and a generation plugin makes individual operations directly importable in components. Those tools offer an existing foundation for connecting defined services to a web interface. Protobuf overview, Connect introduction, Connect-Query generation.
That broader ambition remains an experiment. The participant rule needs an implementation, its configured limit remains runtime data, and behavior involving permissions, failures, or meaningful transformations needs an explicit owner. Existing tooling supplies part of the construction; further generation would be needed elsewhere. The generator and its definitions are maintained responsibilities too. Their value depends on reducing independently authored interpretations rather than preserving every old abstraction in another syntax.
With that boundary established, the agent can change definitions and behavioral code, regenerate the connections, and inspect output when debugging. Enforcing that generated files follow their sources limits where independent variations can appear. The opportunity is to make ordinary feature work concentrate on the deliberate change, with more of its surrounding implementation following mechanically.
The System We Would Choose Today
Take one ordinary interaction in your application and describe it without using the names of its files, frameworks, or architectural layers. Establish what the customer does, which business decisions govern the outcome, and what evidence would demonstrate it. That description gives you a starting point for considering how you would build the interaction today.
Extend that consideration across the system. Evaluate a language by the work of expressing those decisions and constraining their construction, including the work performed by agents. Evaluate a framework by the recurring implementation it removes and the conventions it introduces. Consider whether the interaction pattern expresses the product's intended actions directly, and whether the hosting arrangement supports its actual operational needs. Compare those choices together, because a convenience in one part can create coordination elsewhere.
A simpler system makes required behavior easier to explain, change, and verify through fewer independently maintained interpretations. A smaller file count alone doesn't establish that, and a generated version of the same architecture may leave the work largely intact. The final handwritten participant-cap change was already small; a better construction would make that appropriate result easier to reach repeatedly with less reconstruction and correction.
My favorite key is the backspace key because the best software is the smallest software.
The outcome of this reassessment might be a different stack, a more specific interface, a generation experiment, or renewed confidence in choices that still serve the product. It gives you a basis for deciding where change would help. Your existing knowledge remains valuable because it identifies obligations a fresh design might overlook; it also allows you to separate those obligations from the particular machinery that currently fulfills them.
Compare the system you would deliberately choose today with the one you keep modifying. Each difference points to a choice worth examining, whether it concerns a language, an interaction, or a repeated layer of connective code. Give those choices the same scrutiny you give an agent's proposed implementation. Your product's behavior can remain the commitment while its construction becomes open to consideration again.

