Reading:
Planning Software When AI Writes It

Image

Planning Software When AI Writes It

A practical guide for founders and business owners building with generative AI – written for people who are not software engineers but now have to make engineering-shaped decisions.

Who this is for

Something has changed in the last two years, and it has changed for our clients rather than for us.

People who have never written a line of code are now building working prototypes. Founders are testing ideas over a weekend. Business owners are producing internal tools without a development team. It works, often surprisingly well, and we think it is a good thing.

But it means that a question which used to belong to software engineers has landed on the desks of people who never asked for it:

How do I set this process up properly? I am not a technical expert. Organisationally, what am I supposed to do?

That is the question this article answers. It is not about prompts, tools or which model to use – those change every few months. It is about how to structure what you want, so that a generative model can actually build it, and so that the work survives contact with reality afterwards.

The method described here is not new and was not invented for AI. It is how we have planned and estimated software at Software Planet Group for years, and its roots go back further than that, to Extreme Programming and the early agile movement. We are describing it now because something unexpected happened: a plan that can be estimated accurately turns out to be the same artefact as a plan a generative model can build first time. Both need the same thing – small, self-contained, fully-specified units of work with an explicit definition of “done”.

Nothing about good requirements has changed. What changed is that the cost of getting them wrong is now visible immediately.

Part 1. Why planning matters more when an AI writes the code

Rework is now more expensive than building

In conventional development, “we will build it now and fix it later” is a reasonable strategy. Fixing your own code usually costs about what writing it cost, and often less, because you understand it.

With a generative model that economy inverts. Reworking is systematically more expensive than the first attempt, for three mechanical reasons:

  • You have to reconstruct an intention you never formed. The decision was made by the model, and nobody can explain it.
  • The model does not remember why it did what it did. Each session starts from nothing.
  • A change quietly breaks something you did not ask about, and without automated checks you will not notice.

Aim for the result you want on the first attempt. Every round of rework costs more than the time you would have spent stating the task properly.

If you track one number while you work, track this one: what proportion of your requests are accepted without rework? It predicts the cost of the whole exercise better than anything else.

The plan is not for you

You have a picture of the system in your head. The trouble is that nobody can hold everything relevant to a complex system in their head at once – that is a limit of attention, not of ability.

And until the picture is written down, part of it does not exist as far as the model is concerned. It receives what you said, not what you meant. What you did not write is exactly what comes back wrong.

The plan is not for you. The plan is for the thing that cannot read your mind.

The loop we are describing

The cycle is simple and repeats for every requirement:

Take the next requirement  gather everything known about it  hand it over as one complete package  get the result.

We have automated this internally because we run it at volume. You do not need the automation. You need the same loop by hand, and you need each step to be possible – which is what the rest of this article is about.

Part 2. What counts as one requirement?

A customer requirement is an input to analysis, not a unit of work

“Reporting”. “Marketplace functionality”. “Workflow automation”. “Stripe integration”.

These are not tasks. They are raw input. Handed to a model in that form, they guarantee rework, because the model has to invent the boundaries, the data model and the scenarios on your behalf – and you will not notice until you see the result.

Before anything is handed over, work out what each requirement actually is:

  • a user-facing capability – close to a workable unit already;
  • an epic or whole domain – must be broken down further;
  • a cross-cutting subsystem – built once and reused everywhere;
  • an external integration – made visible as its own separate work;
  • a complexity factor – affects other work without being work itself;
  • an infrastructure requirement – not functional development at all;
  • something already covered – do not build it twice.

Classification comes before estimation. It is the first job, not a formality.

The format: what someone can do, start to finish

A requirement needs a title that describes a complete function from beginning to end. We use a deliberately compact form:

[User or role] can [perform an action]

with an optional third part when the point of the action is not obvious:

[User or role] can [perform an action] to [achieve an outcome]

The conventional agile phrasing is more verbose than it needs to be. The logical structure is what matters.

This format is not a convention for its own sake. It works for a mechanical reason:

The grammar physically prevents you from describing a technical layer.

Try expressing “create the database table” as “[someone] can [do something]”. You cannot, because it is not something anyone can do – it is a step inside something else. The format rejects the wrong shape without anyone having to police it.

The role does not have to be a person. Plenty of real systems are mostly systems talking to other systems, and there is no need to force that into a human-shaped wrapper. System is as valid a role as User or Admin.

The test for whether something is a real requirement

The usual phrasing – “it must have independent business value” – is hard to apply. This is the test we actually use:

Would you notice it as a gap if it silently stopped happening?

System can synchronise supplier records to the CRM – you would notice. That is a requirement.

System can retry a failed API call with a backoff delay – you would not notice it on its own. That is a mechanism inside whatever requirement depends on the call succeeding.

Note that the test says nothing about screens, and nothing about whether a human is involved. The only question is whether this is a self-contained end-to-end outcome or a step inside someone else’s flow.

Cut vertically, not in layers

A requirement should be a complete capability – “Supplier can add a product” – including the database change, the logic, the validation, the API and the interface.

Do not cut the work by technical layer: “build the table”, “build the API”, “build the front end”. Those are stages inside one piece of work, not separate pieces.

The reason this matters for AI work specifically:

A vertical slice has its own acceptance criteria, so it can be checked on its own. A layer has none.

Hand a model “build the table” and you get something nobody can judge as right or wrong until the other two layers exist. By the time you find out, three pieces of work have been built on the mistake.

There is one important exception. For list-and-record screens, each operation is its own requirement: view and filter the list, add a record, edit a record, delete a record. Four requirements, not one. “Manage users” is almost always a poor requirement – it hides several independent pieces of work behind one verb.

How this differs from a traditional specification

The classic format is a list of “the system shall…” statements organised by system function. It is not worse, and in some contexts it is mandatory. But for our purposes it has a specific defect: a statement like “the system shall accept Visa cards” is neither independently buildable nor independently checkable. It has no boundaries – there is no way to say where the work starts and stops. It cannot be handed over as a unit.

There is a second problem. A list of requirements is very hard to read without designing a solution in your head as you go, which means your attention is spent on the first few items and everything after is read through a decision you have already made. A requirement written as a complete outcome does not do that, because it describes a finished result rather than a fragment of a mechanism.

Part 3. How big should one requirement be?

Measure with a ruler, not in hours

You need a way to keep each requirement small, or none of the above holds. We do it by picking one requirement as a reference and holding it against every other one, like a ruler.

We call the unit XPU — Extreme Programming Units. The name is old and comes from the same tradition as the rest of this method.

Our reference is “User can log in to the system”. That is one unit.

It was chosen not because it is trivial but because it is not. A realistic login already contains several standard mechanisms — checking the credentials, the wrong-password error, locking the account after repeated failures, creating a session, expiring it, redirecting afterwards — while remaining compact and understood by absolutely everyone. A ruler has to be a familiar object, or two people cannot measure with it together.

The question at estimation time is therefore not “how many hours is this?” but “is this bigger or smaller than login, and by how much?” People answer the first question badly and the second one well. And holding up the same reference every time stops the scale drifting — inflating or compressing — over the life of a project.

Here is the illustration that makes the method obvious. “User can log in” and “User can register” look like the same form with the same button. Registration is three units. Logging in validates data that already exists; registration creates new state — a new entity, persistence, a migration, uniqueness rules, password rules, a set of independent validations. Identical screens, triple the complexity.

The ceiling is a safety catch

If a requirement is more complex than three reference units, split it.

Do not round it down. Do not stretch the estimate to cover it. Split it.

The ceiling does not exist to estimate things. It exists to force decomposition.

The signals that do the real work

The ceiling only works because a list of splitting signals holds it up. Treat any of these as a reason to split:

  • More than one list-or-record action on the same entity. View-with-filter, add, edit and delete each count separately.
  • More than one external system. Each integration is its own requirement, even when several fire from the same event — and even when both systems are yours. A legacy platform and its replacement are two requirements, not one.
  • More than one way in to the same outcome. Entering a code by hand and following a link containing a token are different things to validate, with different lookup and expiry rules.
  • More than one independently configurable area bundled under one heading. “Account settings” quietly covering discounts, notification preferences and report recipients is at least three.
  • More than about five substantial acceptance criteria.

Treat that last one strictly. Crossing the threshold is not a separate, softer rule to be weighed against the others — it is proof that one of the signals above was missed. Go back and apply the list again rather than keeping the long version because nothing individually looked disqualifying.

And the principle that makes all of it work:

Leaving things merged requires a stated reason. Silence is not a reason.

The default is to split. Justification is needed for not splitting, never for splitting.

A practical check you can run yourself: for each draft requirement, list mechanically the proper nouns (named systems, providers, platforms), the distinct trigger mechanisms, the distinct list-or-record verbs, and the distinct independently configurable areas that appear in its own description. More than one in any of those four categories is a candidate for splitting. It is a checklist, not a judgement call, which is why it works the same way for everybody.

The estimate must not depend on the order of work

A subtlety without which the whole thing falls apart: the relative complexity of a requirement does not change depending on when you build it. “Admin can log in” is one unit even if it happens to be the first thing that creates the storage for administrators. Actual hours depend on order; relative complexity does not.

For planning, this is essential — otherwise estimates would move every time you resequenced, and planning would be impossible. For AI work it has a direct consequence:

Requirements stay assignable in any order. Independence of the estimate and independence of the task are the same property seen from two sides.

Why the ruler also predicts first-attempt success

This is the part worth pausing on.

We were not measuring “can a model do this”. We were measuring implementation complexity. But what makes a requirement bigger than the reference is exactly one thing: how many independent things have to be got right at the same time — mechanisms, entities, side effects, external dependencies, validation rules.

And that is precisely what drives down the chance of getting it right first time.

A ruler built for accurate estimates turns out to measure whether a task fits in a single attempt. Not by design — because it is the same quantity.

One caveat about hours

The reference is calibrated against a clean project. Real projects arrive with existing code, and that changes the actual hours. The unit measures inherent functional complexity; converting it into hours is a separate factor, specific to each project. Please do not read “one unit equals half a day” as a promise.


Part 4. How should you record requirements?

A document is convenient for the eye

Everyone pictures a project plan as a document: a list of points, sections, indentation. That is the right format — for a person. You glance at it, take in the overall shape, find what you need.

But the plan now has two readers, and it is optimised for only one of them.

The problem is not that a model “reads documents badly”. It is that in a document written for a person, part of the meaning lives around the item rather than in it:

  • Meaning carried by position. “Same as above, but for suppliers.” Cut that item out and hand it over on its own, and nothing is left.
  • Cross-references. “See section 3”, “as described earlier” — these break silently when the item is extracted.
  • Prose that mixes levels. Requirement, rationale, background and decoration arrive in one paragraph, and the model cannot tell which parts are binding. It guesses, and sometimes guesses wrong.
  • Layout that carries meaning. Indentation means hierarchy, bold means important. To a person that is information; on extraction it is lost.

And the least obvious point: a good human document does not repeat itself. We factor out the common parts to avoid duplication — and that is exactly what leaves each individual item unable to stand alone.

In a format meant to be read by a model, redundancy is a feature rather than a fault. Every requirement has to make sense on its own, even at the cost of repetition. That is the opposite of what good documentation teaches.

The test: cut out any requirement and show it to someone who has not read the rest. If they cannot start work from it, it is not a requirement yet — it is a note.

The format is yours to choose

We are not recommending a tool, and we are certainly not suggesting you automate anything. Files in a repository, a spreadsheet, Notion, a database — anything works, provided it gives you four properties:

  1. One record is one unit of work. Not a section, not a theme, not an epic.
  2. The record is self-contained. It can be lifted out and handed over alone.
  3. Fields are kept apart. Title, detail, acceptance criteria and checks are different things, not one block of text.
  4. Human views are generated from it, not maintained alongside it. The moment a spreadsheet starts being edited separately from the requirements, divergence is a matter of weeks.

Two further rules save a great deal of pain:

Store only the current truth. No “version 2”, no “was/now”, no change log inside the requirement itself. A new fact that contradicts an old one replaces it. Let version control or the document’s own history carry the past — but not the requirement text, or in a month you will not be able to tell what still applies. That said, omission is not deletion: a fact is removed only when a new requirement explicitly cancels it.

Keep requirements where the model can reach them. If they sit alongside the code, the agent reads them as context, and the plan stops being a document leading a separate life.

Acceptance criteria: short reminders, not test cases

Our use of the term differs from the industry norm, deliberately. Acceptance criteria are not test cases and they are not Scrum-style criteria. They are closer to the Extreme Programming approach: short statements acting as reminders of what must not be forgotten.

For “User can log in to the system”, they might be, in full:

Incorrect password error; Account lockout; Session expiration; Successful login redirect

That is the entire thing. “Account lockout” means the account locks after a set number of failed attempts. The detail is intentionally not spelled out — each phrase is a pointer to an expected part of the functionality.

There is a consistency argument here. If the title of a requirement is a handful of words, it makes no sense for the criteria underneath it to be a sprawling document.

And compactness does real work rather than decorative work:

If you cannot keep the acceptance criteria compact, that is the signal the requirement is too big and needs splitting.

[SCREENSHOT: example of acceptance criteria as we record them]

Behaviour tests: what the system should do

The specification weight is carried by a second layer. Behaviour tests are validation scenarios written before implementation, describing what the system should do. The code is then developed until they all pass.

Each one begins with the word Should. For “User can log in to the system”:

Should authenticate a user with a valid email and password

Should reject authentication when the email is incorrect

Should reject authentication when the password is incorrect

Should return the same error for an unknown email and an incorrect password

The last one is worth noticing. It prevents an attacker working out whether an email address exists in the system. It is not a happy-path check — it is a statement about what the system must not reveal.

Why “Should” rather than “check that…” — the word is chosen for what it does to the person writing. “Check that…” makes you describe implementation: what to call, what to pass in, where to look. “Should” makes you describe observable behaviour — what the system does, regardless of how it works inside. The vocabulary changes, and the resulting lists are different. The approach is close to what is generally called behaviour-driven development.

Our behaviour tests work at the level of the code rather than the interface: one check per behaviour, potentially spanning several classes and a complete business flow, but never clicking around a screen. That deliberately distinguishes them from functional tests.

For working with a model there is one more, purely practical advantage:

A “Should” statement maps one-to-one onto a test. There is no translation step between the requirement and the check — and translation is where intent usually leaks away.

The requirements document is the test plan.

[SCREENSHOT: example of behaviour tests as we record them]

What is usually missing from the list

The claim behind this method is strong: if you describe everything the system should do, and those checks pass, that is the function you wanted.

It holds exactly as far as the list is complete. The gap between “everything is green” and “it works correctly” is made of the statements you did not write.

And the ones people systematically fail to write are the negative ones. Should not. A supplier should not be able to edit another supplier’s product. A user should not see somebody else’s order. A balance should not go negative. This is where holes in permissions, data isolation and integrity live, and they are invisible if you are thinking about the happy path.

A good list contains both. The second kind is often the more important.

Who writes the list

This is the one division of responsibility to get right.

The list of behaviours is the specification. If a model writes it, the model is setting its own exam and marking it. Every automatic check then verifies not what you wanted, but what the model understood.

Ask the model to extend the list, to find edge cases you missed, to propose the “should not” statements you did not think of — that is genuinely one of its strengths. But the final answer to “what does correct mean” stays with you. It is the part of this work that cannot be compressed, and as far as we can tell, the last part that will be.


Part 5. What must be decided before the AI writes anything?

Models are built to do the work, not to ask how

Generative models are trained to be helpful, and in practice helpfulness has come to mean producing output. Asking a clarifying question looks like a failure to help.

So when a model meets a gap, it does not stop. It picks the most plausible option and carries on.

The model does not fail to notice the gap. It closes it silently.

That is worse than an error. An error is visible. Here what disappears is the very fact that a decision was made — the result looks confident and complete. You find out two weeks later, when the decision is load-bearing, and putting it right means reverse-engineering an intention you never formed and never saw being formed.

What it will decide for you

Take an interface. Ask for one more screen and the model will choose the spacing, the typography, the behaviour of empty states, the look of errors, the wording of buttons. It will choose again next time. Each session is locally sensible; together they are a patchwork.

This is a loss of conceptual integrity — a property that has always required a single design held in a single mind. Instead of one mind you have many sessions with no shared memory, and integrity is the first thing to go.

From which the general criterion follows. What has to be settled before a model writes any code is:

Everything where local decisions are required to agree globally.

That is precisely what a model without memory cannot do — not through weakness, but by construction.

A checklist to work through before you start:

  • a design system — components, spacing, typography, and the empty, loading and error states. Reviewed and approved by you, then used as the template for every subsequent part of the product;
  • the data model and naming conventions;
  • how errors and validation are surfaced to the user;
  • the permissions model;
  • API conventions — naming, paging, the shape of an error;
  • the vocabulary of your domain — one agreed set of terms;
  • the stack and libraries, or session three will bring in a second date library;
  • the folder structure;
  • what “done” means: tests, linting, types.

This is settled once per project.

Deciding the approach before stating the task

Some requirements cannot be stated sensibly until the approach is chosen.

“Generate a PDF” can mean a fixed server-rendered document, a conversion from HTML, editable templates, generation by an external service, a streamed download, or a stored file. These are radically different amounts of work.

If the approach is not chosen, the model will choose it for you. That is the most expensive category of rework there is, because by the time you notice, the decision is load-bearing — other work is standing on it, and changing it means changing everything that leaned on it.

If the method of implementation materially changes the amount of work, choose the method before writing the requirement, and write the choice down.

One line in the plan; weeks saved.

Pull the shared parts out first

In conventional development, failing to identify shared functionality causes duplicated code. With a model it causes something worse.

If email, files, notifications, document generation, payments and permissions are not identified explicitly, then every requirement that needs them will get its own implementation. Five requirements, five different ways of sending email, mutually incompatible and configured differently. The model is not refusing to reuse anything — it simply does not know the other sessions happened.

So these belong in the plan separately and early:

“System can send transactional emails through an external service” is its own requirement. “User can register” then uses it, rather than building it again.

The usual candidates: email, notifications, file handling, document generation, payments, authentication, permissions, reporting infrastructure, integrations.

When you do not know how many

A common situation: “multi-step onboarding”, “reports”, “document types”, “forms”, “integrations” — with no indication of how many.

Do not invent a number. Plan it as shared functionality + N × a typical element. The shared machinery — navigation, saving, state — is built once and deserves attention. The repeating elements are then the work that genuinely goes well in batches, because they are alike and the framework already shapes them.

This is also, incidentally, the one place where running several sessions in parallel pays off properly: similar items on top of an existing frame. Not assorted work in an empty project.


Part 6. Filling in the detail

A title is a promise of a conversation — and there will be no conversation

This is the most underrated reason things go wrong, so it is worth being precise.

In agile practice a requirement has three parts: the card, the conversation, and the confirmation. The card is a promise of a conversation — a reminder, nothing more. The conversation is the heart of it, where the details are worked out. The confirmation is the set of checks showing that what was built is what was wanted. The format was designed to be social rather than documentary. Brevity was the aim, not a compromise.

In other words, the requirement title was always a deferral mechanism. Its whole point was not to specify now, because the details would emerge in conversation when the work came up.

With a generative model there is no conversation.

The model will not come and find you on Tuesday to ask what you meant. It has no continuity between sessions. And, most importantly, it will not notice that information is missing — it will fill the gap with something plausible and move on.

The format survived. The deferral mechanism it was built around did not.

Hence the most common mistake we see: using an artefact designed for deferred specification as though it were a specification. The format invites under-description, because that is exactly what it was created to do.

Notice what this means for the three parts. The card is still there — it is your title. The confirmation is still there — those are your acceptance criteria and behaviour tests. It is the conversation in the middle that has to be replaced by something. Most of this article is about what.

Two timescales

None of this means writing essays into your plan. The title level is the right level for managing the plan: it lets you see the whole project, resequence it, cut it and size it without drowning in detail. The problem is not that titles are short. The problem is when there is nothing else.

So:

  • Plan level — title and compact acceptance criteria. Stable, cheap to rearrange, does not go stale.
  • Build level — the full description, added at the moment you pick the requirement up.

Why not write it all up front? Because detail goes off and titles do not. A description written three months before implementation rests on assumptions that will have changed. While you build the first ten items you learn things that change the fortieth. And detail written for requirements that get cut is pure waste. Titles keep; details do not.

The exception matters, and the line is clear:

Decide structure early. Describe detail late.

Architecture, the data model and the shared subsystems are load-bearing, and discovering them late devalues the plan. The detail of an individual requirement can wait. And if describing a requirement reveals that it is actually five requirements, then that was one you should have described early — those are the risky ones, and risky things get examined first.

Dictate the detail

When you pick a requirement up, the practical advice is: turn on a microphone and talk.

The bottleneck is getting what is in your head out of it. Typing is slower than thinking, and while typing you edit yourself — you skip the things that seem obvious. What seems obvious to you is precisely what comes back built wrong. Speech runs at something closer to the speed of thought and drags along the incidental context you would never have typed: “and obviously the filter should still be there when you come back.”

Volume is an advantage here, not a problem. You are producing raw material, not a document. Turning a rambling monologue into structured requirements is something a model does extremely well — arguably better than anything else you will ask of it.

The loop is: dictate → the model structures it → you read it back and confirm. That last step cannot be delegated, for the same reason the behaviour list cannot.

On “how it should look” — prose is poor at conveying visual expectations. A screenshot, a sketch, an annotation over an existing interface, or “like this bit here” all work better than a paragraph.

Make the model interview you

Since a model will not ask by default, it has to be made to. It is worth putting real effort into building a prompt — including researching how to build it — that makes the model work as an interviewer: looking at the specific requirement and at your project, and asking exactly the questions that need answering for a first-time-right result.

What such a prompt needs:

  • An explicit ban on writing code until the questions are answered. Without it the model asks two questions and starts building.
  • Questions in batches, not one at a time. One at a time is far too slow; five to seven at once is comfortable.
  • And the technique that aims directly at the mechanism described above: ask the model to list the decisions it would otherwise make on its own, and to mark which it can infer safely and which need you.

That last one is more useful than open questions. An open question shows what the model recognised as unclear. A list of decisions surfaces the silent choices you would never otherwise have seen — which is where the rework actually comes from.

Know when to stop. Interviewing has diminishing returns, and can itself turn into the up-front design paralysis you were trying to avoid. Do it when you pick the requirement up, not for the whole plan at once, and put a clock on it. Once the questions start touching things that are cheaper to discover by building, the interview is over.


Part 7. Running the loop

If the first attempt misses, throw it away

This is the least intuitive advice here and it saves the most time.

If a requirement was not built correctly in one attempt, roll everything back and run it again with a better description. Do not repair it.

You can try to patch it. But if it is not converging within five or ten minutes, roll back and do not mourn the loss. One attempt costs very little. A repair spiral costs a great deal.

Why this is arithmetic rather than waste. When you repair, you improve the code. When you roll back and restate, you improve the description.

The difference is in what happens next. A better description works on this requirement, on the next one, and on every similar one after that — you have understood what was missing and you will not omit it again. A repair fixes this instance only and leaves nothing behind.

The mechanics of repairing are against you as well. You are editing on top of a state that is already wrong, in a session where the model is holding its own failed decisions in context. With each round the chance of missing again rises rather than falls, and the model starts breaking things that previously worked. A fresh attempt with a better description starts from a clean state, and the odds move the other way.

This is why “let me just try one more fix” is the most expensive sentence in AI-assisted development.

Use the time you save. Before restarting, answer one question: what was missing from the description? A failed attempt is not a loss, it is a diagnosis — it has shown you exactly where your description was incomplete or ambiguous. In our experience the three most common answers are: an edge case was never named; nothing was said about how it should look; or an architectural decision was taken that you had not specified. Write it in, then run it again.

Why this is even possible. Notice that this advice only works because the earlier advice worked. You can throw away something small. If your unit of work is three weeks long, you cannot roll it back, and you are condemned to repair it.

Small requirements are what make throwing work away cheap, and throwing work away is what stops you getting stuck. Neither works without the other.

Make rolling back instant and free — one command to restore the previous state. If a rollback feels frightening, you will repair things out of fear rather than judgement.

The real productivity killer is reading the code by hand

When models made frequent mistakes, you got something other than what you expected, went into the code, found where it went wrong and corrected it — yourself, or by steering the model. You were the driver.

Pair programming is the useful comparison here, and it is a practice we have used for years. A pair has two roles: the driver types, and the navigator holds the whole picture, decides the order of work and makes the technical decisions. In the early days of working with models, the human ended up worse off than a navigator: holding the picture and re-reading every line. A navigator in a real pair does not do that. They watch the direction, not the keyboard.

Modern models increasingly let you not look at the code. The comparison people find intuitive is translation. With the first models you re-read everything they produced. Now you do not, and checking every sentence feels like wasted time. Experienced developers are arriving at the same conclusion about code.

But this is not “trust the model”. The control moves; it does not disappear.

You can stop reading exactly as much code as something else is reading for you.

Not reading and having no automated checks is not an advanced practice — it is accumulating unknown liabilities at machine speed. This advice cannot be taken half way; half is worse than none.

The translation comparison has a limit worth naming, too. A bad translation is visible at the moment of use, and the damage is one sentence wide. Bad code is not visible at the moment of use — it runs — and the damage is the whole system and every future change. In translation your own comprehension does the checking, instantly and free. In code, the equivalent has to be built deliberately.

Boundaries narrow what the model can do wrong: types, database constraints, linting and static analysis, mechanically enforced architectural rules about what may import what, a narrow requirement (a small requirement is itself a boundary), and explicit no-go areas.

Checks prove the result is the one you wanted: the behaviour tests written before the work; a continuous integration gate that must pass rather than “we’ll look later”; a regression suite; browser automation and screenshots for interfaces; and the size of the change as a cheap signal — forty files touched for one small requirement is worth a look even without reading them.

One thing to be clear about: our behaviour tests are deliberately not visual and do not touch the interface. That is right for verifying business behaviour, but it means the gate does not cover whether the interface is correct. If you are building something interactive, that is exactly where you will be looking — so expect a class of work where you still have to use your eyes.

Review does not disappear, it moves up

You stop checking the code and start checking the result: does the system do what it should, were the right decisions taken, are we going in the right direction. That is the navigator’s role from pair programming — the one that was always the valuable half.

And a category of change will always need human eyes. Where that line falls is a judgement about risk, not a tooling setting: a typo in a stylesheet and a database migration are not the same thing, and no amount of automation will weigh that difference for you.

This is also where the acceptance criteria in your plan stop looking like bureaucracy:

Test coverage was never about catching bugs. It is about how much you can afford to stop looking at.


Part 8. What to put in the plan

A checklist you can apply immediately.

  1. The goal — and separately, what would make the project pointless.
  2. Boundaries — what is in, and what is deliberately out.
  3. The actors. Often discovered along the way: if there are orders, somebody places them.
  4. Entities and their relationships. The data model is the most expensive thing to change later, and the thing a model will most happily invent for you.
  5. Chosen approaches wherever the method materially changes the work.
  6. Shared subsystems, listed separately from business requirements.
  7. The requirements themselves: vertical slices, each with acceptance criteria, no more than about five substantial ones each.
  8. What is explicitly not being built yet, as an actual list.
  9. The open questions you could not answer.

Point nine is not a weakness. Uncertainty that has been named is cheaper than uncertainty that has not: you can work with it, whereas the invisible kind surfaces later as rework.


A closing note on cost

The method described here was not built for working with generative models. It was built to estimate complex systems honestly from incomplete requirements, long before coding agents existed.

It turned out to be the same discipline. A plan you can estimate accurately and a plan a model can build first time are shaped identically: small independent units, each with a clear purpose and a clear definition of done.

It is worth being straightforward about what this does to a budget, because expectations here are usually wrong. Writing code is a minority of the engineering hours on any non-trivial project. The rest is understanding the problem, designing, specifying, checking, integrating and operating. So automating the writing moves the total by percentages, not multiples.

Uncertainty, on the other hand, moves it by multiples. A well-structured plan does not save you money because a model types faster. It saves you money because there is far less to get wrong, and because the things that do go wrong are caught by a machine rather than discovered in production. The model did not change what a good plan requires. It only made the cost of a bad one visible immediately.


Software Planet Group has been building complex SaaS platforms and web products under high uncertainty since 2015. If you are planning a product and want an independent view on how to structure the work – including whether it needs building at all – we are happy to talk..

Related Stories

Agile_for_Larger_Projects_Img
January 28, 2019

Agile for Larger Projects

August 24, 2022

Happy Ukrainian Independence Day!

Making Valuable Predictions with Linear Regression Img
July 7, 2019

Making Valuable Predictions with Linear Regression

If you’ve not been living under a rock, you’ve probably already heard about machine learning (ML) technology.