// HACKER NEWS — CYBERSECURITY
Domain-Driven Agents
I've been using LLMs heavily in the last years in coding, or more generally, in software
engineering. I watched many times what productivity boost I could gain from it, and I used LLMs in more and more of my projects. It works well in greenfield projects,
and small ones. The reality is that in day to day work we need to introduce agents into legacy codebases with heavy dependency trees, strong coupling, and a tech debt backlog full of everything we never got to. We quickly notice that the quality of work LLMs can deliver drops sharply.
The failure has a specific shape. Ask for a "job offer status" field in a
greenfield repo and you get one. Ask for it in a system that has been shipping for four years
and the model invents a fourth spelling of a concept that already exists three times, because
the codebase itself never decided which one was real. It writes an adapter where a call was
fine, or calls straight through where an adapter was the whole point. Every one of those is a
question about the system that the system does not answer anywhere. The model guesses, and often guesses wrong.
So brownfield projects are deep, and technical depth is only the first layer. Underneath sits a
second one: confusion, missing meaning, and no shared language to resolve it in. That is
the layer the model falls into. The model is not what needs upgrading. The code is not
ready, and readiness is something we can build. Incrementally. Piece by piece.
Let me show you how I do it.
At the beginning of software engineering there was the one and only: tech debt. It's a natural
consequence of what we, as devs, are trying to achieve. We're not ready for business decisions
from the future shifting our current view of the code. We need to deliver, and deliver fast,
paying some tradeoffs. As a consequence, code smell grows bigger and bigger. The usual answer
is to spend part of the engineering budget on cleanups: earmark 10-20% of the technology budget for
resolving tech debt. In theory... In the next quarter...
A fifth of the budget is the toll on deciding what should change and then typing it out, and those two halves have never had the same price. Deciding stayed about as expensive as it was. Typing it out collapsed. An LLM will do the mechanical half of a cleanup (the extracted module, a refactor across two packages, more test coverage)
at a cost that no longer resembles 2020. Paying tech debt still takes time. It takes
significantly less of it, and what is left for me is the deciding part.
I split the work in two, and I'll borrow the words from John Ousterhout's A Philosophy of
Software Design while being honest that I'm bending them. He uses tactical and
strategic for two attitudes you can hold while coding: tactical programming is
getting-it-working-now, strategic programming is investing in the design as you go. I use the
same pair for a split of authorship, because the economics above cut along that line.
Strategic work is deciding: reading the system, working out what has to change and why, and
whether the change actually serves the feature. Tactical work is carrying that decision
into the files. The first is the part that needs the system in your head. The second is the
part that got cheap.
In the first one I'm fully involved and
in the second one I'm rather a reviewer than an implementer. In the first path I analyze the
codebase in a more generic way, assessing the changes that need to be implemented and their
alignment to the features I want to deliver. The effect of those approaches is GitHub issues I
create in each repository.
The issues are then addressed by my AI system based on skills and sub-agents. A skill
is a written procedure: a markdown file of instructions the model loads when the task matches
it, so "address an issue" or "regenerate the context map" runs the same way every time instead
of the way I happened to phrase it that morning. A sub-agent is a separate model session with
its own fresh context and its own narrow job (im