JournalE.05October 9, 202611 min read

Building effective software faster with AI

Faster code doesn't mean better software. Product vision, data architecture, user experience, and the shape of the codebase stay human-owned.

d

Dipesh Chaulagain

AI-Native Fullstack Developer

ProductArchitecture

The bottleneck is moving

AI is making software development faster. But faster code doesn't necessarily mean better software.

Writing code used to consume a significant portion of engineering time. Today, AI agents can generate implementations, write tests, fix bugs, refactor code, and even work across an entire codebase with surprisingly little human intervention.

This changes how we should approach building software.

But it also introduces an important question: if AI can write most of the code, where should engineers spend their time?

I believe the answer lies in four areas that should remain fundamentally human-owned:

  1. Product vision. Understanding what we're building and why.
  2. Data architecture. Designing how information is represented, related, and evolves.
  3. User experience. Understanding how people interact with the product and what they actually need.
  4. Developer and agent experience. Shaping the project so a person, and an agent, can change it correctly.

AI should accelerate execution within these boundaries, not independently determine them.

This distinction becomes particularly important when building an MVP that we eventually want to turn into a real product.

Product vision

AI agents are increasingly good at turning requirements into working software.

Ask an agent to implement authentication, build a dashboard, or create a REST API, and it can often produce a reasonable implementation within minutes.

But knowing how to implement a feature is different from knowing whether that feature should exist.

Product vision requires understanding the problem, the target users, business constraints, and the direction in which the product should evolve.

Consider building a social media management platform.

An AI agent can implement a post creation form, a scheduling system, and integrations with different social networks.

But several decisions must come first:

  • Is the product primarily a publishing tool or an automation platform?
  • Should content exist independently of the social channels where it is published?
  • Are we designing for individual creators, teams, or organizations?
  • How should the product evolve when new social platforms or AI capabilities are introduced?

These decisions affect almost every part of the system.

If we don't answer them early, AI can help us build the wrong product remarkably quickly.

The role of the engineer is shifting from deciding how every line of code should be written to ensuring that the right problems are being solved.

This doesn't mean writing a massive product specification before starting development.

It means having enough clarity to establish the direction, identify the core domain, and define the boundaries within which AI can operate.

Data architecture

This is probably the area I consider most important when building AI-assisted software.

UI components can be redesigned. APIs can be refactored. Business logic can be rewritten.

But once users begin interacting with a product, data starts accumulating. That data introduces a form of permanence.

Imagine that we build an MVP for managing social media posts. The simplest possible implementation might look like this:

post.ts
export type Post = {
  id: string
  caption: string
  imageUrl?: string
  platform: "facebook" | "instagram"
  publishedAt?: Date
}

For an MVP, this might be enough.

But what happens when we introduce:

  • Multiple social channels for the same post?
  • Multiple captions for threads and follow-up comments?
  • Multiple media attachments?
  • Drafts, scheduling, and publishing states?
  • Organizations and collaborative workflows?
  • AI-generated content and editable design sources?

Suddenly, the initial data model becomes a constraint.

We may need to split entities, migrate existing records, rewrite queries, and change business logic throughout the application.

The problem isn't that we started small.

The problem is that we confused a small implementation with a simplified understanding of the domain.

A better approach is to identify the fundamental entities and their relationships early, even if we implement only a small subset of their capabilities.

For example, we might distinguish between a Post, its Content, its Media, and its eventual Publications.

We don't necessarily need to build every feature around those entities on day one. But recognizing their boundaries gives us a foundation that can evolve.

This is where human judgment matters.

AI can suggest schemas, generate migrations, and optimize queries. It can also challenge our assumptions and identify missing relationships.

However, engineers should own the conceptual model and evaluate its long-term implications.

We should ask: what shape will this data take after thousands of users have been using the application for a year?

Thinking about this before generating implementation code can save substantial rework later.

Of course, overengineering is also a risk. The goal isn't to predict every future requirement or introduce abstractions for hypothetical features.

The goal is to make deliberate decisions about the parts of the system that will become expensive to change once real data exists.

User experience

AI can generate visually impressive interfaces in seconds.

But a polished interface is not necessarily a good user experience.

A good user experience comes from understanding what users are trying to accomplish, the decisions they need to make, and the friction they encounter along the way.

Consider a user creating and publishing a social media post.

A straightforward workflow might be: create post, select platform, add media, publish.

But real users might need to save drafts, generate multiple variations, schedule content, request approval, or reuse previous designs.

There are also less obvious questions:

  • What happens if publishing succeeds on one platform but fails on another?
  • Can users safely retry without accidentally publishing duplicates?
  • What happens to unsaved changes?
  • How should the application communicate partial failures?

These questions aren't just UI details. They influence application state, backend workflows, data modeling, and system reliability.

This is why user experience should be defined before asking an AI agent to implement features. We need to understand both the happy path and the failure paths.

One useful exercise is to describe two workflows:

Before the user exists. How does the system behave when it has no users, organizations, resources, or historical data?

After users begin using the product. How does the system behave when it contains real content, relationships, permissions, scheduled operations, and historical records?

The second scenario is especially important because changes to the architecture can now affect existing users.

AI can help us explore edge cases, prototype alternative workflows, and identify usability problems.

But the final decisions should be grounded in actual user needs, observation, and product judgment.

Developer and agent experience

Vision, data, and workflows still have to land in a repository. How that repository is arranged now changes how the product gets built, because two audiences have to work in it: the engineers, and the agents we ask to implement the slices.

Developer experience is the cost of a person making a correct change. Can they find the domain, run the tests that matter, and see the boundary they are not supposed to cross?

Agent experience is the same question for a model that does not remember yesterday's conversation. An agent does not inherit the team's oral history. It inherits the repository. When the structure is unclear, it guesses, and it guesses across every file it can see.

Monorepo and polyrepo are the sharp form of this choice. The choice changes the unit of a change.

A polyrepo splits the product into separately owned repositories: the web app, the API, the publishing workers, the shared types. That fits when those parts ship on different cadences and a change in one should not force a release of the others. It is a costly default for an early product built by a small group and a coding agent. The agent editing the post composer cannot see the publication worker. Shared types drift. One vertical slice becomes three pull requests, and the time the agent saved comes back as coordination.

A monorepo keeps the product in one workspace: the application, the domain, and the packages that cross them. A slice can change the draft editor, the post schema, and the publisher in one diff, with one test command. That is what makes the loop in the next section cheap. The cost is discipline. Without package boundaries, a monorepo is a large folder, and an agent will import the database client into a UI package because nothing in the tree says it must not.

The layout should follow the domain we already decided to protect. For the social platform, that might look like this:

  • Domain owns Post, Content, Media, and Publication. It imports no framework.
  • App owns the workflow a person actually performs.
  • Publishing owns channel-specific integrations, so Facebook does not leak into the core.
  • A shared package exists only when two of those need the same contract.

Whether those live in one repository or several matters less than whether the boundaries are real. One repository with those packages lets an agent finish a slice without leaving the workspace. Separate repositories earn their place later, when a boundary has its own team, its own release, and a contract that has stopped moving. Starting there, while the domain is still being learned, multiplies the places an agent can be wrong.

On a cold start, the project should make a few things obvious to a person or an agent:

  • Where the domain lives, and what it must not import.
  • How to test one slice, rather than the entire company.
  • Which package owns a given change.
  • The commands, conventions, and examples that are already true.

The experience we are designing is a place where the next change, human or agent, lands inside the boundaries we meant.

The human-controlled agent loop

Once product vision, data architecture, user workflows, and the shape of the repository are reasonably clear, AI agents become significantly more useful.

Instead of asking an agent to “build a social media platform,” we can divide the problem into vertical slices.

A vertical slice delivers a small but complete piece of functionality across the necessary layers of the application.

For example, the first slice might be: a user can register, create an organization, and save their first draft post.

This involves authentication, authorization, database entities, API endpoints, validation, and a usable interface.

We can give the agent a clearly defined objective along with architectural constraints and acceptance criteria.

The agent can write code, run tests, identify failures, and iterate.

The human remains responsible for ensuring the implementation respects the architecture and serves the intended user workflow.

This is a much more effective way to use AI than either manually controlling every implementation detail or giving an agent unrestricted responsibility for an entire product.

It also changes how we should think about code review.

Reviewing AI-generated code isn't just about checking syntax or finding bugs.

We need to ask whether the implementation introduces unnecessary abstractions, violates domain boundaries, creates hidden coupling, or makes future changes harder.

The objective is not to maximize the amount of code an agent generates. It's to maximize the amount of useful, maintainable software we can deliver.

An MVP that can grow

An MVP should help us validate assumptions with the smallest reasonable investment.

It doesn't need every feature, perfect scalability, or enterprise-grade infrastructure.

But that doesn't mean we should treat the entire implementation as disposable.

There is an important difference between building a minimal product and building a product without architectural direction.

For example, our MVP may initially support only one social platform. We don't need to implement integrations for five different platforms. But we can still avoid embedding platform-specific assumptions throughout the core domain.

Similarly, we may start with a single organization per user without designing a complete enterprise permission system. However, understanding that organizations own resources can influence how we model data from the beginning.

These decisions don't necessarily add much implementation time, especially when AI handles much of the repetitive work. They simply require thinking before execution.

A practical approach is to:

  1. Define the product's core purpose and the assumptions we need to validate.
  2. Identify the fundamental domain entities and relationships.
  3. Map the primary user workflow, including important failure scenarios.
  4. Place the domain, the application, and the integrations where a slice can change them together, and keep those boundaries real.
  5. Divide implementation into small, testable vertical slices.
  6. Use AI agents to implement, test, and refine each slice.
  7. Validate the product with real users and evolve the architecture based on evidence.

The key is to distinguish between what needs to be built now and what needs to be understood now.

We can defer implementation without ignoring architectural consequences.

This doesn't guarantee that we will never need major refactoring or a rewrite. Some assumptions will inevitably be wrong.

But a coherent foundation improves our chances of extending the MVP rather than replacing it.

Judgment gets more valuable

As the cost of generating code decreases, the relative importance of engineering decisions increases.

A poorly chosen abstraction can now be propagated across dozens of files in minutes. A repository with no boundaries lets that same mistake cross every package an agent can see.

An incorrect data model can quickly become deeply integrated into an application.

An unclear product requirement can produce a complete, working feature that nobody actually needs.

AI accelerates both good and bad decisions.

This is why engineers should spend more time understanding the domain, designing boundaries, defining workflows, and evaluating trade-offs.

AI should take on more of the mechanical execution: implementing endpoints, writing tests, creating UI components, fixing bugs, and handling routine refactoring.

The result isn't software development without engineers.

It's software development where engineers can focus more of their attention on the decisions that determine whether the software will succeed.

Final thoughts

The future of software development isn't about choosing between human engineering and AI-generated code.

It's about defining a better division of responsibility.

Humans own the product vision, data architecture, user experience, and the structure developers and agents work inside. AI accelerates the implementation, testing, and iteration.

The goal isn't just to ship an MVP in a few days.

It's to ship an MVP quickly, learn from real users, and retain a foundation that can grow into a reliable product.

Because building software faster is useful.

Building the right software faster, without having to start from scratch every time, is far more valuable.