A client approached us with a mobile application that was already in production and had an active user base and paying customers. A significant part of its code had been created with AI, while the AI chat logic depended on a single prompt of roughly 500 lines.

The product was doing its job, but developing it further was becoming increasingly difficult. Unused fragments remained in the codebase, similar logic appeared in several places, and a new instruction in the prompt could unexpectedly affect a scenario that had already been working.

This became particularly clear during what initially looked like a small task. The team needed to change a single rule in the application. Before making the change, however, the developer had to determine which of several similar implementations was actually in use, why another version of the same logic was still present, and what would happen if the old methods were removed.

The change itself took less time than preparing for it.

During the subsequent review, the team identified two related problems: the consequences of repeated AI iterations within the codebase and the monolithic structure of the AI logic, which made it difficult to change the chat’s behavior predictably.

In this article
  • when local AI changes start interfering with each other;
  • a prompt where everything depended on everything else;
  • how we split the AI logic;
  • what changed after the refactor;
  • what we did with the application itself.

When local AI changes start interfering with each other

Vibe coding works well within the scope of a specific request. The model receives a task, changes the relevant part of the code, and produces a result.

By the next iteration, however, the context may already be different. AI can create a new implementation without recognizing that the previous one is no longer needed.

About the anonymized examples.
Entity names and user scenarios have been changed to avoid revealing the product’s internal logic. The types of problems described, however, reflect what the team encountered during the project.

During one iteration, the model generated a new way of handling data. The application began using it, but the models and helper methods associated with the previous version remained in the project. Outdated caching logic was left behind as well.

Nothing crashed. Users had no way of noticing the problem.

During the next update, however, the old and new code looked like two valid implementations of the same process. Both appeared in search results, referenced neighboring components, and made it difficult to understand which part of the system could safely be changed.

A similar situation occurred with validation. Instead of reusing an existing function, AI solved the same task again for several different screens. The validation rule was duplicated, and the resulting versions differed slightly from one another.

A single change now required work across several areas. The team first had to find every version of the logic and determine which ones were actually being called. Only then could they check how the change affected the UI state.

This is how technical debt accumulated without causing any immediate failures. Each AI iteration solved its own task, while the overall structure of the application gradually became less clear.

A prompt where everything depended on everything else

A similar problem appeared inside the AI chat.

Roughly 500 lines of prompt text contained request-processing rules, restrictions, formatting requirements, and instructions for individual scenarios. While the logic was still limited, this approach made it possible to add new behavior quickly.

Over time, the prompt became fragile.

How the conflict occurred
Base instruction

Return the result using a strictly defined JSON structure.

Scenario-specific rule

Maintain a natural conversation and explain the actions to the user in detail.

Result:
the model could add regular text outside the JSON or return a rigid data structure where a complete conversational response was expected.

Both instructions appeared correct when considered separately. Together, they produced inconsistent results.

Changing a single phrase did not guarantee that the problem would disappear. The response depended on a combination of rules located in different parts of the prompt. A new instruction could improve one scenario while quietly changing another.

As a result, every update to the chat required tracing the relationships between dozens of conditions that existed in the same document but controlled different types of behavior.

How we split the AI logic

The team moved away from the idea that one prompt should understand the request, select the scenario, retrieve the required data, and generate the final response at the same time.

These responsibilities were distributed among several agents.

STAGE 01

Router

Determines the type of request and selects the route for further processing.

STAGE 02

Retriever

Retrieves the context and rules required for the selected scenario.

STAGE 03

Generator

Produces the final response using the data prepared during the previous stages.

Each component now works with a limited context and handles one specific stage instead of processing the entire product logic at once.

Splitting the prompt alone, however, did not solve the whole problem. The agents now had to exchange information, and those connections also needed to be controlled.

The components therefore pass their results in a structured format.

Example of a contract between components
{
  "intent": "...",
  "confidence": 0.91,
  "entities": [...]
}

Before the data reaches the next stage, the system checks that all required fields are present and that the result matches the expected format.

If one agent returns an invalid structure, the error is stopped at that stage instead of propagating through the rest of the pipeline.

What changed after the refactor

This architecture also simplified testing. The QA team could test request classification, context retrieval, and response generation separately. A small change no longer required reviewing the entire 500-line prompt again.

Before

The chat could break the required format, ignore a restriction, or change its behavior in one scenario after another scenario was updated.

After

These deviations occurred less frequently, and the chat behaved more consistently across repeated test cases.

The QA team can now change and test an individual part of the AI logic within the scope of a specific task without analyzing the entire prompt.

What we did with the application itself

A complete rewrite was not necessary.

The team first reconstructed the actual structure of the project. They identified the implementations that were still in use, separated them from outdated code, and removed duplication where it was obstructing further changes.

The working parts of the application were preserved. Problematic areas were refactored gradually, without pausing product development to rebuild the entire system.

Result

The application remained in production, while the team gained a codebase and AI architecture that could be developed further. New tasks no longer began with trying to determine which of several similar fragments was current or which rule inside a huge prompt might affect the response.