Intentional Engineering · #1 of 2

Driving Software from Intent

Using coding agents today involves a major trade-off between output volume and control over that output. I believe that focusing on intention can help us resolve that trade-off.

Software factories: getting volume with control

AI opens the possibility of software engineering moving from an era of craftsmanship into its industrial era. I don’t believe we are there yet, for two reasons.

Coding agents today can offer control or volume, not both

The first is an analogy to physical manufacturing. A car factory produces cars in large quantities while keeping very tight control over cost and output quality. In other words, it gives you volume and control. When using AI in software, you have to trade one for the other: use a large model and let it work on a long-horizon task to get volume (at the cost of control over the output); or babysit your AI to get control (at the cost of output volume: you’ll move more slowly).

The second is that the control you want is control at the level of a team, not an individual. I have talked to many CTOs in the past few months and heard the same story time and time again. They have a few star AI users who really move fast, but whatever those stars are doing is not diffusing to the rest of the team. AI agents remain an individual tool, which makes shared ownership of software very challenging.

Intention is the source of truth

I see software engineering as a constant iteration cycle over three phases: intention (spec, ADR, design, task), implementation (code, code review, deployment), and evaluation (monitoring, bug reports, user feedback, company metrics).

The software engineering loop

Coding agents have changed the intention-to-implementation link most of all. Give Claude Code a well-expressed intention, and it will translate it into code with very good results. This moves the question of control back onto the intention. How do you produce intention at industrial scale? What does it mean to express it well? How do you separate the decisions the AI took from the ones a human took?

Code is the source of truth for what your software does; it is never the source of truth for what it should do. The cleanest manifestation of that is the question every engineer asks when a bug report lands: is this a bug, or is it a feature? The source of truth is the intention behind the code.

I posit that in order to build a proper AI software factory that gives us both volume and control, intention should be the primary product of that factory, with code generated only as a byproduct. This is what I call intentional engineering.

Intention should be the primary product of a software factory, with code generated only as a byproduct.

The weight of history

I strongly believe that the hard part of software engineering has never been writing new code, but steering complexity over the long term. I have held the driver’s seat refactoring the backend of a 300-person scale-up, and the question I asked myself most often was: why? Why did we write this piece of logic? If I know the reason we wrote it originally no longer applies, then I can remove it with confidence.

You know you are starting to lose control of your system when each change brings a set of production issues, or worse, when the fixes themselves regularly create new problems. It means you are constantly optimizing for local maxima at the expense of the global one.

This is one of the major risks with AI, and it is heightened with smarter models, because those models can undertake larger bodies of work in which they make more decisions. That makes it harder to distinguish the actual intent of your team from the drift the AI introduced over time.

The answer, in my view, is establishing clear decision lineage: the ability to link every line in your code back to the original intention behind it, the reason one implementation approach was chosen over another one. The context of past decisions is what accelerates future ones.

Intentional engineering implies mapping every line of code to its original intent

Driving software through intention enables establishing that lineage, but is not enough. The natural way coding agents look into the past is through git history. That requires discovery by walking the commit graph, which is slow and can easily fill up context. Even in a medium-sized code base, being able to retrieve that decision lineage in reasonable time and with limited context consumption will require compressing the software’s past history into a dedicated data structure.

How does this work in practice?

Over the past few months I’ve iterated on a workflow underpinned by the above intuitions. Here are some key elements I have today:

  • No agent conversation is started with a prompt typed into a chat box. Every action I take is a skill applied to a document; agent conversations are treated as ephemeral, any decision or conclusion from a conversation is persisted back in the target document.

  • As a result, skills become a unit of workflow; I choose the skill that I am applying and I never need the agent to choose a skill for me. This means that skill definitions don’t need to live with the agent (see second post in series for more).

  • I have a sequencing skill that allows me to turn a tech design into a series of tasks. This allows me to do long-horizon work without being reliant on the black box to-do list of a single agent conversation. That to-do list is individual, whereas the tasks are shared with the team, so sequencing is an essential link in building a team-level AI workflow.

  • I have a concept of a “project”; a project is essentially a dossier of context materialized as a list of links to relevant documents. This is so that any new agents spun up for that particular project can easily load those documents into context rather than having to explore a knowledge base and discover them again.

  • I have two classes of agents: control agents that live entirely in the intention layer, operate at the level of a project and are used for things such as designing, sequencing, or bug triaging, and implementation agents that take a unit of intent (a task) and turn it into a ready-to-merge PR. Control agents are shared by the team, whereas an implementation agent could run anywhere (Claude Code on laptop, Cursor Cloud, custom sandbox etc…).

  • Because the project I’m working on is small enough, I don’t use a dedicated data structure for past history. Instead I rely on using squash-merge and linking to my intent document in the per-PR commit message; and the git history is my natural data structure.

This workflow is still very much in flux; I will write subsequent posts digging into each of those elements.