A while ago, I tried building my own coding agent.
My goal was not to compete with Claude Code or other mature coding agents. I simply wanted to reach what I personally considered a level where the agent was reliable enough to handle real development tasks.
After a lot of work, it eventually reached the point where it could complete many ordinary tasks.
Then I started running benchmarks.
That was when I realized that getting an agent to work and getting an agent to work reliably are two very different problems.
The deeper I went, the more convinced I became of one idea:
The model determines the capability ceiling of an agent, but the runtime determines how much of that capability is actually realized.
Eventually, I started thinking about agents in another way:
An agent is a bounded optimization process.
Here is how I arrived at that conclusion.
1. From a Phrase-Driven Loop to a Task-Driven Runtime
My first implementation was relatively simple.
Conceptually, the loop looked something like this:
Model says what it wants to do
↓
Runtime executes it
↓
Model sees the result
↓
Runtime follows the next instruction
I think of this as a phrase-driven agent.
The runtime is largely following what the model says from one turn to the next.
This can work surprisingly well for simple tasks.
But anyone who has used LLMs extensively has probably seen the failure modes:
- the model drifts away from the original task,
- repeats something it has already done,
- forgets earlier state,
- believes an operation succeeded when it did not,
- or declares completion too early.
I later changed the architecture toward something more task-driven:
What is the current task state?
↓
What is still missing?
↓
Execute the next step
↓
Verify the result
↓
Update task state
↓
Continue
The important difference is that the runtime no longer advances purely because the model produced another instruction.
It advances according to the state of the task.
That change noticeably improved stability.
But it also made me realize how much engineering sits between an LLM and a reliable coding agent.
Eventually, I stopped trying to build the whole thing myself.
Not because the model was incapable of writing code, but because the runtime itself had become a serious systems-engineering problem.
2. What Building With AI Taught Me About Software Engineering
Modern coding models make it possible for an individual developer to build things that would previously have required far more time or a larger team.
If your objective is:
“Get something running first.”
AI is extremely effective.
A website, a prototype, a demo application, or an early version of a product can often be built much faster than before.
But the situation changes when the goal becomes:
Stable, production-ready, and maintainable for years.
The larger the codebase becomes, the more important architecture, constraints, review and verification become.
AI continues to help, but raw code generation stops being the main bottleneck.
I repeatedly ran into three problems.
2.1 AI Can Generate Technical Debt Extremely Quickly
AI is very good at solving the immediate problem in front of it.
A bug appears here:
Add another condition.
A state mismatch appears somewhere else:
Add a fallback.
Another integration breaks:
Add a compatibility path.
Each individual change can look reasonable.
But after dozens of iterations, the accumulated result can look very different:
- duplicated logic,
- temporary state that became permanent,
- overlapping compatibility layers,
- excessive fallbacks,
- abstractions wrapping other abstractions,
- and code whose original design intent is increasingly difficult to recover.
This happens partly because understanding a large repository is still a retrieval problem.
An agent normally sees only a subset of the codebase at any particular moment. It searches, reads files, follows symbols, retrieves additional context and progressively constructs a picture of the system.
That picture is necessarily incomplete.
Without deliberate refactoring and architectural control, local fixes accumulate.
One of the strongest lessons I took away from this was:
AI dramatically increases the speed of code generation, but if architecture, review and refactoring do not keep pace, it can generate technical debt at roughly the same speed.
2.2 Your Ability to Evaluate the Result Still Matters
This became increasingly obvious as my project became more complex.
Suppose the problem requires knowledge beyond your own current ability.
The AI produces a solution.
Now you still have to answer:
- Is the architecture actually reasonable?
- Did it really fix the bug?
- Did it merely work around the bug?
- Is the test meaningful?
- Is the system now more fragile?
- Is there a better abstraction?
- What should be done next?
If you cannot evaluate those questions, development becomes increasingly difficult.
A common response is:
“Just use a stronger coding model.”
I spent thousands of RMB experimenting with different frontier models.
There were absolutely differences between them.
Some planned better. Some followed instructions more reliably. Some handled large refactors better. Some were more consistent with tools.
But none of them eliminated the fundamental problem:
The complexity of the project you can safely build is still influenced by your ability to understand and evaluate what is being built.
A stronger model expands that boundary.
It does not make evaluation unnecessary.
2.3 Complex Software Depends More on Constraints Than Cleverness
Large systems usually do not remain reliable because one function is exceptionally intelligent.
They remain reliable because they have structure:
- clear module boundaries,
- stable interfaces,
- explicit state transitions,
- controlled dependencies,
- type constraints,
- tests,
- invariants,
- and layers that compose in relatively predictable ways.
If the entire architecture is continuously delegated to AI without strong constraints, something interesting happens.
In one context, the model may conclude that architecture A is best.
Later, with a slightly different context, architecture B appears more attractive.
Several iterations later, architecture C gets introduced to solve problems created by the previous two.
Add incomplete context, model variation and occasional hallucination, and architectural drift becomes easy.
This is why I increasingly think the important question is not:
“How much code can the model generate?”
but:
“How tightly can we constrain the system so that generated changes remain coherent?”
3. Why the Same Model Behaves So Differently Across Agents
This was the question that became much more interesting to me after building an agent myself.
People often assume that if two products use the same underlying model, their coding ability should be roughly equivalent.
For example:
Same Claude model
or
Same GPT model
or
Same Gemini model
So perhaps the only meaningful differences are the UI and a few extra tools.
In practice, that is nowhere near the whole story.
While testing my own Rust-based agent runtime, I encountered problems in almost every part of the execution pipeline.
3.1 Tool Calling
Tool calling sounds straightforward until you build it.
You have to deal with things such as:
- argument parsing failures,
- schema differences,
- provider-specific behavior,
- inconsistent tool outputs,
- malformed responses,
- partial execution,
- retries,
- and determining whether a tool actually succeeded.
If the runtime records a failed operation as successful, the model starts reasoning from a false state.
Everything after that can be wrong.
3.2 Task State
A coding agent may contain conceptual stages such as:
Planning
↓
Execution
↓
Verification
↓
Completion
If one transition is incorrect, the agent can stop even though obvious work remains.
Or it can continue executing after the task is already complete.
The model itself may be perfectly capable of performing the next step.
The runtime simply never gives it the opportunity.
3.3 Repeated Execution and Infinite Loops
If previous actions are not represented correctly in state, an agent may:
- read the same file repeatedly,
- apply the same modification repeatedly,
- rediscover the same plan,
- retry an impossible action,
- or cycle between two states.
Loop detection sounds like a small runtime detail.
During long tasks, it becomes essential.
3.4 Completion Detection
When exactly is an agent finished?
If the completion condition is too permissive:
Model: Looks good. Done.
the task may stop before the code is actually working.
If it is too strict, the opposite happens.
The build passes, tests pass and the requested change is complete, but the runtime continues looking for more work.
Reliable stopping conditions are surprisingly difficult.
3.5 Context Pollution
Long-running coding tasks generate enormous amounts of information:
- terminal output,
- compiler errors,
- logs,
- tool results,
- plans,
- failed attempts,
- code,
- diffs,
- previous messages.
Keeping everything is not a solution.
Even very large context windows are finite, and more context does not automatically mean better context.
Eventually, irrelevant information starts competing with important information.
3.6 Error Recovery
Suppose:
- the model stream disconnects,
- a provider returns an error,
- a tool fails,
- a file write partially succeeds,
- or the environment changes unexpectedly.
What should happen?
Should the agent:
- retry?
- continue from the existing task state?
- roll back?
- re-plan?
- retrieve the environment again?
- restart the entire task?
Each strategy has trade-offs.
And every recovery strategy changes the information available to the next model call.
3.7 Retrieval Changes Intelligence
Consider two coding agents using exactly the same model.
Agent A can:
- read precise file ranges,
- search symbols,
- inspect references,
- retrieve repository structure,
- make targeted edits,
- inspect diffs.
Agent B has only a few coarse tools and sometimes returns inconsistent results.
On paper, both agents have the same “brain.”
In practice, they are reasoning about different worlds.
The difference becomes even clearer with code retrieval.
One agent may effectively rely on:
grep -R "something" .
Another might combine:
- repository maps,
- symbol indexes,
- reference graphs,
- LSP information,
- semantic retrieval,
- dependency relationships.
The second agent can often place the same code inside a much more accurate structural context.
The underlying model has not changed.
The evidence provided to it has.
And that changes its reasoning.
3.8 Small Differences Compound Over Long Tasks
This is probably one of the most important observations I made.
Imagine two agents starting from the same model and the same task.
Agent A retrieves slightly better information during step one.
That means its decision during step two is slightly better.
That changes which file it reads during step three.
That changes what it edits during step four.
That produces a different compiler result during step five.
Now the two agents are operating from different states.
Run this loop for dozens of iterations and the trajectories can diverge dramatically.
So the phenomenon:
“This model feels amazing inside Agent A but strangely stupid inside Agent B.”
is not surprising at all.
The model may be identical.
The execution trajectory is not.
3.9 Context Engineering
After experimenting with this problem, context engineering became one of the areas I consider most important in agent design.
A long-running agent must simultaneously make the model remember important things and prevent the context from becoming polluted.
That requires constant decisions:
What should remain permanently?
Which history is no longer relevant?
What can be compressed into one sentence?
Should failed reasoning remain in context?
Should a tool result be stored verbatim or summarized?
Which pieces of code should stay available?
Which ones should be retrieved again when necessary?
Every decision changes future model behavior.
Again, small differences accumulate.
Over a task containing dozens or hundreds of interactions, context policy can significantly change the final result.
3.10 Verification Changes Everything
There is a huge difference between an agent that stops after:
“I believe the issue is fixed.”
and one whose loop looks more like:
Modify
↓
Inspect diff
↓
Build
↓
Run tests
↓
Inspect failures
↓
Fix again
↓
Verify
The second agent may appear significantly more intelligent.
But part of that apparent intelligence comes from the runtime forcing the model to collect evidence.
This applies to many other runtime components as well:
- search budgets,
- task-state management,
- tool design,
- loop detection,
- permission systems,
- recovery policies,
- model-specific prompts,
- verification requirements.
The visible “intelligence” of an agent is an emergent result of the whole system.
4. An Agent Is a Bounded Optimization Process
This eventually led me to a mental model that I now find useful.
At every step, an agent is effectively trying to choose a better next action based on the state it currently understands.
It might decide to:
search the repository
read another file
inspect a symbol
call a tool
modify some code
run the compiler
execute tests
collect more evidence
stop
But the agent never has perfect knowledge of the environment.
Its repository view is incomplete.
Tool results may contain noise.
Context is limited.
Search costs time and tokens.
Execution has side effects.
And the current state may itself contain incorrect assumptions.
So the agent is not solving a fully observable global optimization problem.
It is repeatedly making local decisions with incomplete information under constraints.
That is what I mean by:
Agent = bounded optimization.
Once you look at agents this way, many runtime features start to look different.
A search budget is not merely an implementation detail.
It defines how much evidence the optimizer is allowed to gather.
Loop detection prevents the optimization process from getting trapped in cycles.
Verification changes the effective objective from:
produce a plausible answer
to something closer to:
produce an answer that survives external evidence
Context management determines which approximation of the current state the optimizer gets to see.
And stopping conditions define when the optimization process is considered sufficiently converged.
Seen from this perspective, much of agent engineering is really about defining the boundaries of that optimization process.
5. Stronger Reasoning Does Not Automatically Mean Better Convergence
There is another interesting consequence.
A more capable reasoning model does not automatically produce a more reliable agent.
Without appropriate constraints, a stronger model may:
- search more,
- explore more branches,
- call more tools,
- generate more hypotheses,
- and continue reasoning for longer.
That can be extremely useful.
But without search budgets, stage boundaries, loop detection and stopping conditions, it can also make convergence harder.
In other words:
Better exploration is useful only if the system can eventually turn exploration into convergence.
This is why agent engineering cannot be reduced to simply inserting a stronger model into an existing loop.
A stronger model gives the agent a larger and potentially better search space.
The runtime still has to guide that search toward a useful result.
6. The Agent Is the Whole System
After building one myself, I no longer think of a coding agent as simply:
“An LLM with tools.”
The actual product is closer to:
Model
+ Context Engineering
+ Retrieval
+ Tools
+ Task State
+ Execution Loop
+ Verification
+ Error Recovery
+ Permissions
+ Model Adaptation
+ Stopping Conditions
All of these components shape the model's future observations and actions.
Together, they determine whether the system eventually reaches a useful result.
So my current summary is:
The model determines the capability ceiling. The runtime determines how much of that capability can be realized reliably.
Or, from the optimization perspective:
The model provides the intelligence to search the solution space. Agent engineering defines the boundaries that make that search converge.
That is why the exact same model can feel dramatically different depending on the agent around it.
The brain matters.
But so does the rest of the system.
Top comments (0)