In some of my projects, millions of tokens are spent on research, analysis and documentation before a single line of production code is written. From the outside, that can look excessive. If AI can generate software so quickly, why not just describe the idea and ask it to start building?
Because the quality of AI-assisted software development is decided long before the first line of code appears. Before increasing the speed of development, I want to establish what should be built, what should not change, which standards the project must follow, and how we will judge whether the result is actually correct.
I use ChatGPT and models such as Astra throughout research, system design, implementation, testing and refinement. My process does not start with "build this application for me." It starts with defining the environment in which the AI is allowed to build: coding standards, writing standards, infrastructure stability rules, UI and UX principles, SEO workflows, Git conventions, testing standards, reusable skills, project boundaries and decision-making rules. Only after that foundation exists do we define the actual product. AI does a significant amount of the work. Responsibility for the result stays mine.
I design the development process before I design the product
My background is in civil engineering, which shapes how I think about software. Before constructing something, you need to understand what the structure must support, what constraints exist, and where failure is most likely to occur. I approach software the same way: a screen that looks good, or a feature that appears to work, is not enough. I want to know how the data is stored, what happens when another part of the system changes, and which parts must stay stable while a feature is modified.
Because of this, I split the work into two layers. The first defines how we build: coding conventions, documentation rules, UI and UX standards, testing expectations, infrastructure stability rules, Git and change-management practices, SEO research processes, AI permissions and limitations, and reusable skills for recurring workflows. The second is project-specific: the business problem, the users, functional requirements, boundaries, constraints, data models, acceptance criteria and architecture decisions.
Reusable standards stop us from reinventing the development process on every project. Project-specific decisions stop us from forcing the same solution onto every problem. I set out this operating model, and my own background, on Sina Esfahani — about the founder.
"Write good code" is not a standard
A common weakness in AI-assisted development is vague instruction: "write clean code," "follow best practices," "make it scalable." These sound reasonable but are not precise enough to consistently control AI-generated work. A useful standard has to be specific enough that the output can later be evaluated against it: separation of responsibilities, error-handling expectations, dependency rules, acceptable complexity, module boundaries, backward compatibility, and the conditions under which refactoring is allowed. Code that works today but forces half the application to be rewritten for a small feature tomorrow is not high-quality code, in my view.
Documentation follows the same principle. I do not want documentation that repeats what the code already says. Good documentation captures the reasoning: why this architecture, what assumptions were made, what alternatives were considered, and what remains uncertain. These explanations matter more once AI is involved, because it can generate code far faster than a person can build a mental model of the system.
Infrastructure stability needs its own rules
AI models are often very willing to improve things — sometimes too willing. Ask for a small feature and the model may reorganise a directory, replace a dependency, rewrite a component or modernise part of the architecture. Some of that may even be an improvement, but improvement is not the only objective; stability matters too. A request to modify a feature is not automatic permission to redesign the system. Changes to deployment configuration, core dependencies, APIs, data contracts, authentication logic or project structure need a clear reason. "This is newer" is not enough, and "this architecture is cleaner" is not necessarily enough either. The real question is whether the change improves the project enough to justify the added risk and complexity.
UI, UX and testing standards describe behaviour, not just appearance
Interface standards should not stop at colour, spacing, fonts and components. A good interface is also behavioural: what happens while data is loading, what a user sees when nothing exists yet, what happens after an action succeeds or fails, and whether the workflow still makes sense on mobile. These questions are part of what "finished" means, not something to postpone until the end.
Testing standards work the same way. Correct behaviour needs to be defined before the code, not after: what must work, what must fail safely, which edge cases and failure conditions matter. Writing tests is not the same as running them, and a passing suite only proves that the scenarios it covers are passing — never that the scenarios nobody thought to test are correct. When a test fails, the right response is to understand why, not to weaken or delete it to make the build green.
A skill is a reusable operating procedure, not a vague prompt
One of the most important parts of my workflow is defining reusable skills — not a vague prompt like "you are an SEO expert," but something closer to an operating procedure: when it should trigger, what information it needs, which tools it may use, which checks it must perform, what the output must contain, and what the AI is not allowed to assume. I do not want quality to depend on whether I happened to mention the right instruction earlier in a conversation. Important standards and validated decisions need to live in persistent project resources the AI can refer back to, and the output still has to be evaluated against the skill afterwards — a standard that exists only in a document but never influences a decision is just another unused file.
SEO is a good example. I do not create pages first and ask afterwards what they should rank for. Before recommending a content structure or writing copy, the AI should check whether there is enough evidence about keywords, intent and search volume for the market and language in question. If not, it should obtain that evidence rather than invent a strategy or present an assumption as measured data. That research becomes a reusable brief that later content refers back to, not a one-off exercise repeated for every page. And keyword research should never become keyword stuffing: the point is understanding what a reader is actually trying to accomplish, not counting how many terms fit on a page.
Research should converge on decisions, not run forever
By the time implementation starts, an enormous amount of AI work may already have happened — on large projects, the investigation and documentation alone can run into millions of tokens. But token count is not a quality metric, and I do not spend them simply because more analysis sounds thorough. The objective is to remove meaningful uncertainty, not to reach perfect knowledge. What are we building, for whom, on what architecture, accepting what risk, with which assumptions still needing validation? Once enough of that is answered, implementation should start.
This is also where scope gets defined honestly. Scope is not just a list of features to build — it is also a list of things that should not be built yet. A feature can be useful and still not belong in the current version. The ability to build something is never, by itself, a reason to build it. And acceptance criteria have to exist before implementation, not after: what happens on a duplicate submission, a failed network call, a missing field, a failed authorisation. If those questions are not settled until the code already exists, the AI will make assumptions — sometimes right, sometimes quietly wrong, and either way undecided by anyone in particular.
The problem chooses the technology, and the database is a human decision
It is easy to become attached to tools, and AI models can show the same tendency because certain patterns are common in their training data. I prefer to start from what the system actually needs to do, how often it will change, and who will maintain it — then choose the technical structure. Sometimes the right answer is sophisticated. Often it is extremely simple. Complexity should be earned.
Database design gets particular caution from me. AI is genuinely useful here: it can propose structures, identify relationships, point out edge cases, and ask questions a person might have forgotten. But a human has to bring business reality into the design — what happens to a historical order when a customer's address changes, whether records should disappear or be anonymised on account deletion, which changes must stay auditable. These are business decisions before they are database decisions, and reverting a destructive migration is a fundamentally different operation from reverting application code, which is one more reason schema changes get their own level of scrutiny.
After the tests pass, I read the code line by line
Once implementation and automated testing are done, I read the generated code myself, line by line — not only checking for syntax errors, but asking why a condition exists, where a value comes from, what happens when a function fails, and whether I would still understand this in six months. I sometimes bring AI back in during review, to criticise the implementation or search for edge cases, but AI reviewing AI does not remove the need for human judgement: a convincing explanation is not proof, and the code still has to match the requirement, the architecture and the real-world behaviour we expect. If I am going to maintain the code, I need to be able to understand it, explain it and change it myself.
The greatest value of AI in my process is not that it types faster than I do. It is that it lets me investigate more possibilities: research faster, compare approaches, challenge assumptions, find edge cases, and inspect large codebases. That is a real productivity gain — but it becomes far more valuable inside a disciplined system, because without one, AI can generate complexity, technical debt and incorrect assumptions just as efficiently as it generates working features.
So I do not think the future of software development is "tell AI what you want and let it build everything." I think the more useful model is to design an engineering environment in which AI can operate effectively, then give it increasing autonomy inside well-defined boundaries: standards before coding, research turned into reusable project knowledge, assumptions made explicit, critical data structures designed collaboratively, behaviour tested against a definition agreed in advance, the scope of every change controlled, what already works preserved, and the result still reviewed with human judgement.
That does not guarantee bug-free software, prevent requirements from changing, or remove uncertainty. What it does is make decisions easier to understand, changes easier to audit, and mistakes easier to catch before they are embedded in the product. For me, a project is not finished when the AI stops generating code. It is finished when the result matches the requirement, the important assumptions have been checked, and I am comfortable taking responsibility for what we built — which is also the standard I bring to custom software and workflow projects at OmniTechs, and to the wider process behind AI document processing.
I use AI to build faster. I use engineering discipline to make sure we build the right thing. How I learned to put models inside real systems — APIs, memory, latency and failure — is in I Was Building Around AI Before AI Was Ready.
