Engineering with AI · Engineering Process
Reading time · 7 min
Plan First: Why Our Coding Agents Don’t Start With Code
How StackLiberate uses a plan-first workflow with AI coding agents: repository discovery, invariants, blast-radius analysis, explicit failure semantics, bounded implementation, and human approval before code changes begin.

Coding agents are extremely good at starting.
Give one a feature request and access to a repository and, within seconds, it can find files, propose changes, write code, update tests, and keep moving.
That speed is useful.
It can also be the wrong optimization.
In a mature codebase, the difficult question is rarely:
Can we produce code that appears to implement this feature?
The harder questions are:
- Where does this behavior actually belong?
- Which existing contract already governs it?
- What state is authoritative?
- Which other paths depend on the same behavior?
- What should happen when the operation fails halfway through?
- Which existing guarantees must remain unchanged?
- How will we know the implementation is correct beyond the happy path?
For substantial changes to StackLiberate, we usually want those questions answered before an agent begins modifying production code.
We call that approach Plan First.
It is not a ceremony added to slow AI down.
It is how we make AI useful on software where local correctness is not enough.
Fast implementation can hide incomplete understanding
Modern coding agents are remarkably capable at navigating unfamiliar repositories.
The problem is that finding a plausible implementation point is not the same as understanding the system.
A feature may appear to belong in a React component while its real contract lives in project state. A backend change may look isolated while an export path relies on the same representation. A seemingly harmless mutation can work in the current session while bypassing version history, reload, or another lifecycle boundary.
The first implementation an agent finds can therefore be perfectly reasonable in isolation and still be wrong for the architecture.
This is particularly dangerous because the result often looks good.
It compiles.
The UI behaves correctly.
Tests may pass.
The diff may even be clean.
But the implementation can still introduce a second source of truth, duplicate an existing responsibility, weaken a failure boundary, or solve one projection of the product while leaving another behind.
The agent did not necessarily write bad code.
It solved a smaller problem than the system actually had.
Plan First is how we widen that problem before implementation begins.
Discovery comes before modification
For non-trivial work, our first request to an agent is often intentionally read-only.
We want it to inspect.
That can include tracing:
- The user interaction that begins the operation.
- The state or representation that carries it.
- Existing validation boundaries.
- Persistence and history.
- Reload or reconstruction behavior.
- Related rendering paths.
- Export or publication behavior.
- Existing tests and invariants.
The exact surfaces differ by feature. The principle does not.
Before deciding how to change a system, establish how the current system works.
This sounds obvious when humans describe engineering work. It becomes more important with agents because they can move from incomplete understanding to hundreds of lines of implementation with almost no friction.
Removing that friction is one of AI’s advantages.
Sometimes we deliberately put a little of it back.
We plan around contracts, not files
A weak implementation plan is a file list:
Modify component A.
Add helper B.
Update test C.That may be useful later, but it is not yet an architectural plan.
A stronger plan describes the contracts the change must preserve.
For example:
User intent
↓
Supported operation
↓
Authoritative state transition
↓
Persistence
↓
Reconstruction
↓
Visible resultOnce the contract is understood, files become implementation details.
This changes how the agent reasons.
Instead of asking:
Where can I put code that makes this work?
the plan asks:
Which existing boundary should own this behavior?
That distinction has prevented us from accepting implementations that were locally convenient but structurally wrong.
The goal is not theoretical purity. It is avoiding architecture that becomes harder to reason about every time a feature is added.
Invariants make the plan concrete
“Be careful” is not a useful engineering requirement.
Neither is “don’t break anything.”
Before implementation, we try to identify explicit invariants: properties that must remain true after the change.
Some are specific to a feature. Others describe broader product expectations.
An invariant might say that an existing source of truth must remain authoritative. Another might require an operation to survive reload. A change may need to preserve undo behavior, reject stale state, remain exportable, or leave unrelated routes completely untouched.
These statements give the agent boundaries.
They also give the reviewer something more useful than visual inspection.
Instead of asking whether a diff looks reasonable, we can ask:
Does the implementation preserve the invariants we agreed on before the code existed?
That turns review into a comparison against an explicit design, rather than a negotiation with whatever implementation the agent happened to produce.
Blast radius is part of the feature
One thing agents are very good at is making a change look contained.
Sometimes it actually is.
Sometimes the visible diff is small while the behavioral blast radius is much larger.
Before code begins, we ask the agent to identify what the feature could affect:
- Shared components.
- State transitions.
- Persistence.
- Existing data.
- Authentication or ownership boundaries.
- Rendering.
- Export.
- Public discovery.
- Other work currently in progress.
The objective is not to produce an exhaustive dependency graph for every button.
It is to discover surprising coupling before we modify it.
This becomes especially important when multiple branches or agents are working simultaneously. A technically correct change can still be unsafe if it casually rewrites a shared surface another feature depends on.
Sometimes the best plan is not the cleverest implementation.
It is the implementation with the smallest justified blast radius.
Failure behavior belongs in the plan
Happy paths are easy to describe.
A user performs an action. The request succeeds. The UI updates.
Real software spends plenty of time outside that sequence.
Requests arrive late. State has changed. A dependency is unavailable. Validation fails. A user navigates away. An operation succeeds in one place and fails in another.
So a plan should answer more than what happens when everything works.
We want to know:
- What is authoritative if two representations disagree?
- What is allowed to retry?
- What must fail closed?
- What remains unchanged after rejection?
- Can a partially completed operation leave durable state?
- How does the user recover?
Not every feature requires distributed-systems machinery.
Most do not.
But every feature has failure semantics, whether we design them or inherit them accidentally.
Planning forces those semantics to become visible before implementation makes them expensive to change.
The plan is reviewed before the code biases us
There is another reason to separate planning from implementation: code creates attachment.
Once an agent has produced a sophisticated solution, there is a natural temptation to repair it rather than question whether it should exist.
Humans experience this too.
A large working diff changes the conversation from:
What is the right architecture?
to:
How can we make this architecture acceptable?
Plan First keeps that decision earlier.
The agent proposes:
- Its understanding of the current system.
- The intended state transition.
- The contracts involved.
- The files likely to change.
- Failure behavior.
- Tests and validation.
- Blast radius.
- Rollback strategy.
Then a human can reject the direction while rejection is still cheap.
Sometimes the result is simply:
Approved. Implement it.
Sometimes one assumption is wrong and the plan changes.
Sometimes discovery reveals that the requested feature should use infrastructure that already exists.
And sometimes the correct decision is not to build the feature yet.
All four outcomes are valuable.
Approval does not mean blind execution
Once a plan is approved, the coding agent has substantially more freedom.
At that point we want speed.
It can navigate the relevant files, implement the bounded change, add tests, run validation, and report unexpected findings.
But approval is not a license to silently redesign the system.
If implementation reveals that an assumption in the plan was false, that is new information.
The right response is often to stop and update the plan rather than improvise a new architecture halfway through the task.
This creates a simple division:
Planning defines the intended change. Implementation discovers whether reality agrees.
Agents are particularly effective when those two phases are allowed to inform each other without becoming indistinguishable.
Plans should be proportional
Plan First does not mean writing an RFC for every copy change.
A typo does not need architectural discovery.
A small isolated styling fix may need little more than identifying the correct component and checking responsive behavior.
The depth of the plan should scale with uncertainty and blast radius.
We become more deliberate when work involves things such as:
- Persistent state.
- Shared contracts.
- Authentication.
- Concurrency.
- Version history.
- Structural editing.
- Data migrations.
- Publishing or export.
- Cross-boundary communication.
- Existing behavior with significant user impact.
The purpose of planning is to reduce expensive uncertainty.
When uncertainty is already low, the plan should remain small.
AI makes architecture more important, not less
One of the most interesting effects of coding agents is that implementation has become cheaper.
That changes where engineering effort is most valuable.
When producing another hundred lines of competent code is easy, the scarce skill becomes deciding:
- Which hundred lines should exist.
- Where they should live.
- Which state they are allowed to change.
- Which guarantees they must preserve.
- How they interact with everything already built.
In other words, faster implementation increases the leverage of architecture.
A poor decision can now be implemented beautifully and at extraordinary speed.
So can a good one.
The difference increasingly happens before the first edit.
The agent is part of the engineering loop
We do not treat coding agents as autocomplete, and we do not treat them as autonomous owners of the architecture.
They occupy a more useful position between those extremes.
An agent can investigate a repository, trace behavior across layers, challenge assumptions, enumerate failure modes, propose a bounded design, implement it, run validation, and explain what changed.
A human can provide product intent, establish priorities, reject unnecessary complexity, question shortcuts, approve architectural boundaries, and decide whether the final behavior is acceptable.
The result is not “AI writes the code while humans watch.”
It is an engineering loop in which different kinds of reasoning happen at different stages.
For us, that loop often looks like:
Intent
↓
Read-only discovery
↓
Plan
↓
Review and approval
↓
Bounded implementation
↓
Validation
↓
Engineering reviewThen the cycle repeats when new information appears.
Start slower to move faster
The fastest way to begin a feature is often to write code immediately.
The fastest way to finish a complicated feature reliably may be different.
A few minutes spent identifying state boundaries, existing contracts, failure behavior, and blast radius can prevent hours spent untangling an implementation that solved the wrong problem elegantly.
Coding agents make it possible to move through implementation at a speed that was unusual only a short time ago.
We want that speed.
But speed is most valuable after direction is established.
That is why our coding agents often begin with an unusual instruction:
Don’t change anything yet.
First, understand the system.
Then show us the plan.
Then we build.