The anxiety behind unlimited code
I recently asked on X what would happen if we had unlimited tokens to spend on LLMs. One reply captured a concern I suspect many developers already feel: AI might generate code faster than we can review it.
At first, this sounds like a practical problem of code review. More generated code means more surface area, more possible bugs, and more work for the people responsible for maintaining the system. But I think the anxiety points to something deeper than review capacity.
For many developers, understanding a system has traditionally been tied to direct contact with its implementation. We write the code, review the pull request, trace the conditionals, and gradually build confidence that we know how the system behaves.
AI-assisted programming weakens that relationship.
When large parts of the implementation can be produced by a model, the developer may no longer be the person who personally shaped every line. That can feel like a loss of control. But perhaps what is being lost is not control itself, but a particular kind of control: the kind that comes from manual authorship and line-by-line familiarity.
This distinction matters because software development may be entering a shift that other industries have gone through before. When production scales, control often moves away from direct manual operation and into process, structure, constraints, and supervision.
When production scales, control changes
The assembly line is a useful analogy here.
Before industrial manufacturing, building a complex object was more closely associated with craft. The person making the thing was close to the materials, the tools, and the individual decisions that shaped the final product. There was a direct relationship between the maker and the object.
As production became industrialized, that relationship changed. No single person needed to touch every part of every car for the car to be reliable. In fact, the reliability of the system increasingly depended on something else: standardized parts, repeatable processes, inspection points, quality controls, and clearly defined responsibilities.
From one perspective, this was a loss of direct control. From another, it was the creation of a different kind of control.
The craftsperson’s intimate knowledge of the object did not scale to mass production, so the system had to encode knowledge in other places. It had to make quality less dependent on one person’s constant attention and more dependent on the design of the production system itself.
Software has always had some version of this tension. We already rely on abstractions, frameworks, type systems, tests, linters, code review, documentation, deployment pipelines, and runtime monitoring to help us reason about systems that are too large for any one person to fully hold in their head.
AI does not introduce the problem of scale, but it accelerates it. It makes the production of implementation much cheaper, which forces us to ask where confidence should come from when the amount of code increases faster than our ability to read it.
A second analogy, although simpler, is the elevator operator.
Early elevators were not only operated by humans because the technology required it; the operator also served as a symbol of trust. A person inside the elevator made passengers feel that someone was in control.
When elevators became automatic, trust had to move from the visible human operator into the system itself: buttons, sensors, doors, brakes, emergency procedures, and constraints.
That shift feels relevant to AI-generated software. It is natural to feel safer when a human has written or reviewed every line. The human presence gives us confidence. But if machines increasingly generate the implementation, then confidence has to come from the structure around that implementation.
We need systems that are not merely written, but constrained, observable, testable, and understandable at the level of behavior.
Why implicit behavior becomes harder to trust
AI is very good at producing plausible local implementations. Sometimes it writes code faster than us. Sometimes it writes better local code than we would have written ourselves.
But software is not only local implementation. Software is behavior over time.
A system receives events, changes state, communicates with other parts of the system, handles failures, recovers, retries, cancels, completes, and sometimes reaches situations the original developer did not anticipate.
The hard questions are not always:
Can this function be written?
They are often:
What states can this system be in?
What transitions are allowed?
What should be impossible?
Who owns each responsibility?
What messages cross each boundary?
What happens when something fails?
Can I understand this system without reading every branch of every function?
The hardest bugs often do not come from the fact that a single function was impossible to write. They come from unclear behavior: states that were never named, transitions that were never explicitly allowed, responsibilities that leaked across boundaries, and assumptions that existed only in someone’s head.
This is already difficult when humans write all the code. It becomes more difficult when AI generates large parts of the implementation.
A model can produce plausible code that satisfies a local request while still making the global behavior of the system harder to understand. It may add a flag, create a shortcut, duplicate a condition, or couple two modules because doing so solves the immediate task.
None of those decisions may look obviously wrong in isolation. The problem is that the system’s behavior becomes increasingly implicit.
If behavior is hidden inside scattered conditionals, booleans, callbacks, effects, and service calls, then reviewing AI-generated code becomes difficult because the reviewer has to reconstruct the behavior from the implementation.
The code contains the answer, but it does not present the answer directly.
A human reviewer has to infer the real model of the system.
State machines as a supervision layer
This is where state machines become valuable.
I do not mean that every feature in every application needs to be modeled with perfect formal rigor. The point is broader than any specific tool or methodology. State machines teach a way of thinking that becomes more valuable when implementation becomes abundant.
They help us describe software in terms of explicit behavior.
Consider a relatively common example: a booking flow. At first, the logic can seem simple. A user selects a class, the system checks whether they are allowed to book it, and then it reserves a spot.
In code, that might begin as a few conditions:
if (user.membership && class.hasSpace) {
reserveSpot()
}But real software rarely stays that simple.
What if the membership expired? What if the class becomes full during the booking attempt? What if the user already has a reservation? What if the class starts too soon? What if payment fails? What if the user cancels? What if the system needs to mark the user as a no-show? What if an administrator overrides the status? What if a person checks in with or without a prior reservation?
Over time, the behavior expands beyond the original function.
If this behavior is represented only through scattered conditionals and flags, then understanding the system requires reconstructing the state machine in your head.
You might see variables like:
isLoading
isConfirmed
isCancelled
hasError
isExpiredBut those flags do not necessarily tell you what the system is allowed to do. They do not clearly tell you which combinations are valid, which transitions are impossible, or what should happen when something fails.
A state machine changes the shape of the problem.
Instead of treating the states of the system as an accidental result of many variables, we name them directly:
idle
checkingMembership
reservingSpot
confirmed
checkedIn
completed
cancelled
expired
noShowThen we define which transitions are allowed:
idle → checkingMembership
checkingMembership → reservingSpot
reservingSpot → confirmed
confirmed → checkedIn
confirmed → cancelled
confirmed → noShow
checkedIn → completedNow the behavior is no longer hidden across many implementation details. It is visible.
This changes how we can use AI.
Instead of asking a model to:
Generate the booking logic.
We can ask it to:
Implement this behavior. These are the states. These are the allowed transitions. These transitions are forbidden. These are the failure cases.
The difference is significant.
In the first case, the model is asked to infer the states, transitions, and edge cases from a natural-language request. In the second, the human has already designed the behavioral structure. The model is still useful, but it is operating within clearer constraints.
The human is no longer trying to supervise a pile of generated code from the bottom up. The human is supervising whether the implementation respects an explicit model.
That is a much better relationship with AI-generated code.
Actors as boundaries for AI-generated systems
State machines help with behavior over time. The actor model helps with ownership and boundaries.
As systems grow, especially when assisted by AI, there is a risk that everything starts talking to everything else. A function reaches into another module. A service knows too much about the internals of another service. A component owns state it should not own. A model adds a convenient shortcut because it satisfies the immediate prompt.
The code may look fine locally, but the system becomes harder to reason about globally.
Actors offer a different mental model.
An actor owns its own state. It receives messages. It sends messages. It has a defined responsibility. It does not need to reach into everyone else’s internals.
For example, instead of one large blob of booking logic, a system might have:
BookingActor
MembershipActor
ClassCapacityActor
CheckInActor
NotificationActorThe exact implementation does not matter as much as the separation of responsibilities.
The BookingActor coordinates the booking flow, but it does not need to own every rule for membership eligibility.
The MembershipActor answers whether a person is eligible.
The ClassCapacityActor owns capacity rules.
The CheckInActor owns check-in behavior.
The NotificationActor reacts to relevant events and sends messages.
Each part has a boundary, and communication happens through explicit messages.
This is valuable because supervision requires legibility.
If everything can call everything else, then both humans and machines have to understand the whole system before changing any part of it safely. But if the system is composed of well-defined actors with explicit messages, a change can be evaluated within a clearer context.
We can ask:
What messages does this actor receive?
What state does it own?
What messages can it send?
What invariants must it preserve?
What responsibilities should remain outside of it?
The point is not whether these actors are AI agents or not. That distinction does not matter here. The important thing is that they follow a model humans define.
Actors make systems easier to supervise because they make responsibility and communication explicit.
Code as an artifact
This is the sense in which code becomes an artifact.
Not an unimportant artifact, and not something we can ignore. Code still matters. Generated code can still be wrong, insecure, inefficient, brittle, or poorly structured.
But code becomes one output of a higher-level design process rather than the only place where the system is defined.
The behavioral model is an artifact.
The statechart is an artifact.
The tests are artifacts.
The documentation is an artifact.
The diagrams are artifacts.
The runtime traces are artifacts.
The prompts and specifications may also become artifacts.
The important thing is that all of these artifacts point back to an explicit understanding of the system.
In a world where AI can generate implementation quickly, the most important review may not always be whether every line is written exactly as we would have written it. The more important questions may be whether the implementation respects the model, whether the states are explicit, whether the transitions are valid, whether boundaries are preserved, whether failures are represented, and whether the system can be observed when something goes wrong.
This does not eliminate the need to review code. It changes what we should be reviewing for.
We should still care about security, performance, correctness, readability, and maintainability. But we also need to care about whether the code is an accurate expression of the system we intended to build.
The danger of AI-generated code is not only that it might be wrong. The danger is that it might be locally plausible while making the global behavior harder to understand.
Explicit models help reduce that risk.
A note for JavaScript developers
For JavaScript and TypeScript developers, this is one reason tools like XState are worth studying.
Not because every project must use XState. Not because every feature needs a statechart. Not because a library will magically make software correct.
The value is that state machines and statecharts change how you see software.
Instead of thinking only in terms of files, functions, components, API calls, and effects, you start thinking in terms of:
states
events
transitions
actors
effects
guards
failure modes
impossible states
That shift is useful even if you do not use a specific library everywhere.
Once you learn to see software this way, you start noticing how much complexity in everyday applications comes from behavior that was never made explicit. You also become better at giving AI the right kind of instructions.
Instead of asking for more code, you can ask for a clearer model.
Instead of asking the model to invent the flow, you can define the flow and ask the model to implement it.
Instead of reviewing only the implementation, you can review whether the implementation matches the behavior you designed.
That is a very different way of working.
From operators to supervisors
AI will not remove developers from the process. But it will change where developer judgment is applied.
When code was expensive to produce, direct authorship was a reasonable proxy for control. We wrote the code, shaped the implementation, reviewed the diff, and built confidence through proximity.
When code becomes cheaper to produce, proximity is no longer enough.
The developer’s role shifts toward defining the system that generated code must fit into. That means clearer states, clearer transitions, clearer boundaries, clearer messages, clearer invariants, better tests, and better feedback loops.
This is not less engineering.
It is more explicit engineering.
The mistake would be to respond to AI-generated code by trying to preserve the old feeling of control forever.
“I must read every line.”
“I must understand every implementation detail.”
“I must personally touch every part.”
There will always be critical areas where deep review is necessary. But as a general model for building software, this does not scale in a world where code generation becomes abundant.
The better response is to move control up a level.
From lines of code to behavior.
From implementation to architecture.
From manual operation to supervision.
From implicit control flow to explicit models.
The developers who adapt well to AI-assisted software development may not be the ones who try to personally inspect every generated line with the same expectations as before. They may be the ones who become better at defining systems that generated code can safely fit into.
Code is no longer the scarce resource. Understanding is. And the best way to preserve understanding is to make behavior explicit.
Do you like what you are reading? Subscribe to receive updates.
Unsubscribe anytime