Programming After Handwritten Code
In his Rails World keynote and follow-up essay, DHH argued that agents would soon write the vast majority of our code.
I don’t write any production code now. Yet programming feels more intellectually demanding than ever.
If I’m no longer writing the code, what exactly am I doing?
My days are full of investigations, experiments, and decisions about what a system should do. What agents still need from me becomes clearest when they get stuck. Why can an agent do something astonishingly difficult, then struggle with a change that seems straightforward?
I think part of the answer is that an implementation cannot reliably tell an agent what we mean the system to do, or how we need it to operate.
Capturing what we mean
When I design a system, I’m developing an understanding of the problem: which concepts matter, how they relate, and why the design should produce the behavior I want. That understanding lets me reason about new cases and decide how the system should change. The code embodies some of that understanding. A fresh agent may still need the reasoning behind the design to extend it.
That understanding also extends to operating the system. Measurements tell us how it behaves today. We still have to decide how much downtime is acceptable, what a recovery must preserve, and what it should cost to operate.
Design and operation inform each other. Data guarantees shape recovery requirements; operating constraints shape which designs are practical.
Some of this knowledge is buried in discussions or known only to maintainers and operators. Some decisions haven’t been made at all. Better access helps with the first problem. The second requires investigation and a decision.
I call a maintained account of this understanding structured intent. It records how we think the system should work, why, under which assumptions, and how we assess it.
Design that can speak back
My work on TanStack DB, a reactive data framework, has become an experiment in preserving that understanding across agent sessions. An investigation can take hours as we explore alternatives, resolve ambiguities, and decide what users should observe. I want the next agent to inherit both the decision and the reasoning behind it.
When we can express a design decision as a rule, we can give future agents a way to check whether their changes respect it. We do that with an oracle: a mechanism for judging whether software behaves as intended. Many of ours use a small, independent model of what should happen. We run generated sequences of operations through both the model and the production implementation, then compare the results.
Suppose a later agent proposes a change that violates the rule. The oracle supplies a counterexample. The agent can read the model and its explanation, understand the conflict, and revise its approach.
Design can speak back.
That understanding can also deepen. A session may expose a blind spot in the oracle, an assumption that no longer holds, or a question the existing design never answered. We strengthen the model, improve what it observes, or deliberately revise the promise. The next session inherits a more developed understanding.
Closing the operational loop
Running the system gives us another way to test that understanding. We can record reliability targets, recovery requirements, and resource budgets in code, alongside the measurements and tests that assess them. If the running system misses a target, that evidence can trigger an agent investigation.
This feedback between development and operation is already central to DevOps and site reliability engineering. Agents could take on more of the work within it: investigate the discrepancy, change the system, and gather evidence of the result. The remedy might involve application code, deployment configuration, capacity, or a recovery procedure.
The agent’s task is to meet the requirement and show us the evidence. A passing test tells us about behavior under the conditions it exercises; an ongoing production requirement needs continuing observation.
An investigation can also change what we think the system should do. An incident, like a bug report or feature request, can reveal something the current design never considered. Sometimes we need to fix the system; sometimes we need to reconsider what it should promise.
Developing the intent
Building software keeps exposing us to new possibilities. An experiment can reveal a useful behavior we hadn’t imagined. A benchmark can show that an ambitious guarantee is affordable. Actual use can reveal that we have optimized something people barely notice while neglecting what frustrates them.
In “On Desire”, Venkatesh Rao asks how we come to want the goals we pursue. He describes how experience and encounters with new possibilities can change those goals. As agents make software experiments cheaper, more possibilities become worth trying.
Turning a new possibility into a commitment requires deciding what its terms mean. What counts as a completed write? Does “completed” mean accepted, visible, or durable? What does “fast” mean for that write?
Making those distinctions usable is a smaller version of the conceptual work Rao describes in “Ontological Exhaustion”: learning together to use new categories. In a codebase, we can express “completed” in a model and tie “fast” to a benchmark with a workload and a threshold.
Once we can state an idea precisely, we still need to distinguish a possibility from a commitment. The agent needs to know which ideas to explore and which guarantees its changes must preserve.
What becomes source code
We could increasingly begin a change by revising the structured intent. We would develop the design model or adjust an operating requirement, then ask agents to produce the code and deployment changes needed to realize it. The models, tests, and production measurements we maintain would help us assess the result.
Structured intent would then take on some of the role source code plays today. Production code becomes one output we generate and revise to satisfy it; deployment configuration and operating policies are others. That is what I mean by code becoming a compilation target: we work on a higher-level description of the system, while tools produce more of its implementation.
After deployment, we keep checking the running system as workloads change and new failures appear.
Building implementations from higher-level descriptions has a long history. The original FORTRAN proposal described translating concise mathematical formulations into efficient machine programs. Codd’s argument for data independence sought to protect applications from changes in how data was represented internally. Both helped establish descriptions that could survive changes in machinery beneath them.
More directly, model-driven architecture proposed generating implementations from models, using performance and other operational requirements to guide the transformation. The same ambition appears in Kiro’s approach to agent-assisted development, which centers the work on specifications.
Agents may make this approach practical for more software by lowering the cost of both implementing a design and building the instruments that assess it. A different algorithm, architecture, or deployment becomes cheaper to try against the understanding and requirements we have preserved.
Existing code and operating practices may still embody decisions we have not captured. Our checks may omit important cases or measure the wrong thing. Production experience can expose those omissions.
My programming work already revolves around understanding the system, making that understanding usable by agents, and judging the evidence they bring back. I’m interested in how much of building and operating software we can organize around that work.
Each session can leave the next agent with a deeper understanding of the system and better ways to test it. That agent can challenge and extend what we have learned. The running system becomes one realization of that understanding.
v2